# Laravel Pluginable Laravel Pluginable is an extensive plugin system for Laravel that enables modular application development through automatic registration of routes, controllers, services, views, and configurations. This package transforms Laravel applications into plugin-based architectures, allowing developers to create self-contained, reusable modules that integrate seamlessly with the host application. The system supports both Livewire Volt and traditional Blade views, automatic dependency injection, middleware registration, event/listener patterns, and comprehensive configuration management. The package provides a complete ecosystem for building modular Laravel applications with features including automatic plugin discovery, service provider registration, Blade hook injection points, performance caching, translation support, and database migrations. It follows Laravel conventions while adding powerful abstractions for plugin lifecycle management, making it ideal for applications requiring extensibility, multi-tenant systems, or marketplace-style plugin architectures. ## Installation and Configuration Install the package via Composer ```bash composer require saeedvir/laravel-pluginable ``` Publish configuration file and customize plugin settings ```bash php artisan vendor:publish --tag=laravel-pluginable-config ``` ```php // config/laravel-pluginable.php return [ 'plugins_path' => app_path('Plugins'), 'plugin_namespace' => 'App\\Plugins', 'use_plugins_prefix_in_routes' => false, 'default_view_type' => 'volt', 'enable_volt_support' => true, ]; ``` ## Creating a Plugin with Artisan Command Create a complete plugin structure with controllers, services, and views ```bash # Create plugin with Volt views (default) php artisan make:plugin Analytics # Create plugin with Blade views php artisan make:plugin Analytics --view-type=blade # Create plugin with multiple components php artisan make:plugin Analytics \ --command=ProcessDataCommand \ --controller=ApiController \ --event=DataProcessedEvent \ --listener=SendNotificationListener \ --middleware=AdminMiddleware \ --enum=Status \ --trait=Cacheable \ --lang=messages \ --route ``` This creates the following structure: ``` app/Plugins/Analytics/ ├── config.php ├── routes.php ├── AnalyticsProvider.php ├── Controllers/ │ ├── AnalyticsController.php │ └── ApiController.php ├── Services/ │ ├── AnalyticsService.php │ └── AnalyticsServiceInterface.php ├── Commands/ │ └── ProcessDataCommand.php ├── Events/ │ └── DataProcessedEvent.php ├── Listeners/ │ └── SendNotificationListener.php ├── Middleware/ │ └── AdminMiddleware.php ├── Enums/ │ └── Status.php ├── Concerns/ │ └── Cacheable.php ├── Lang/ │ └── messages.php └── Views/ └── index.blade.php ``` ## Service Injection with Interface Binding Automatically registered services available via dependency injection ```php // app/Plugins/Analytics/Services/AnalyticsServiceInterface.php namespace App\Plugins\Analytics\Services; interface AnalyticsServiceInterface { public function handle(): array; } // app/Plugins/Analytics/Services/AnalyticsService.php namespace App\Plugins\Analytics\Services; class AnalyticsService implements AnalyticsServiceInterface { public function handle(): array { return [ 'message' => 'Analytics service is working!', 'timestamp' => now()->toISOString(), 'visitors' => 1542, 'pageviews' => 8734, ]; } } // Usage in any controller use App\Plugins\Analytics\Services\AnalyticsServiceInterface; class DashboardController extends Controller { public function __construct( private AnalyticsServiceInterface $analytics ) {} public function index() { $stats = $this->analytics->handle(); return response()->json($stats); } } // Expected output: // { // "message": "Analytics service is working!", // "timestamp": "2025-12-28T10:30:00.000000Z", // "visitors": 1542, // "pageviews": 8734 // } ``` ## Plugin Routes with Automatic Registration Define routes that are automatically prefixed and named ```php // app/Plugins/Analytics/routes.php use Illuminate\Support\Facades\Route; use App\Plugins\Analytics\Controllers\AnalyticsController; Route::controller(AnalyticsController::class)->group(function () { Route::get('/', 'index')->name('analytics.index'); Route::get('/reports', 'reports')->name('analytics.reports'); Route::post('/track', 'track')->name('analytics.track'); }); // Accessible at: // http://your-app.com/analytics/ // http://your-app.com/analytics/reports // http://your-app.com/analytics/track // Or with prefix enabled (use_plugins_prefix_in_routes => true): // http://your-app.com/plugins/analytics/ // http://your-app.com/plugins/analytics/reports // Using named routes in Blade: // {{ route('plugins.analytics.index') }} // {{ route('plugins.analytics.reports') }} ``` ## Controller with RESTful Operations Full CRUD controller automatically bound to service container ```php // app/Plugins/Analytics/Controllers/ApiController.php namespace App\Plugins\Analytics\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; class ApiController extends Controller { public function index(): JsonResponse { return response()->json([ 'success' => true, 'data' => [ 'visitors' => 1542, 'pageviews' => 8734, 'bounce_rate' => 42.3, ] ]); } public function store(Request $request): JsonResponse { $validated = $request->validate([ 'event_name' => 'required|string', 'properties' => 'array', ]); return response()->json([ 'success' => true, 'message' => 'Event tracked successfully', 'data' => $validated ], 201); } public function show($id): JsonResponse { return response()->json([ 'success' => true, 'data' => [ 'id' => $id, 'event' => 'page_view', 'timestamp' => now(), 'user_id' => 123, ] ]); } } // Usage with cURL: // curl -X POST http://your-app.com/analytics/track \ // -H "Content-Type: application/json" \ // -d '{"event_name":"button_click","properties":{"button_id":"signup"}}' ``` ## Event and Listener System Automatically registered event-listener pairs with queue support ```php // app/Plugins/Analytics/Events/DataProcessedEvent.php namespace App\Plugins\Analytics\Events; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class DataProcessedEvent { use Dispatchable, InteractsWithSockets, SerializesModels; public function __construct( public array $data = [] ) {} public function getData(): array { return $this->data; } } // app/Plugins/Analytics/Listeners/SendNotificationListener.php namespace App\Plugins\Analytics\Listeners; use App\Plugins\Analytics\Events\DataProcessedEvent; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class SendNotificationListener implements ShouldQueue { use InteractsWithQueue; public function handle(DataProcessedEvent $event): void { $data = $event->getData(); logger()->info('Analytics data processed', [ 'event' => get_class($event), 'data' => $data, 'timestamp' => now() ]); // Send notification logic here } public function failed(DataProcessedEvent $event, $exception): void { logger()->error('Notification failed', [ 'exception' => $exception->getMessage() ]); } } // Usage: Dispatch event from anywhere use App\Plugins\Analytics\Events\DataProcessedEvent; DataProcessedEvent::dispatch([ 'report_type' => 'daily', 'records_processed' => 5000, 'duration_seconds' => 23 ]); ``` ## Custom Artisan Commands Plugin commands automatically registered and available ```php // app/Plugins/Analytics/Commands/ProcessDataCommand.php namespace App\Plugins\Analytics\Commands; use Illuminate\Console\Command; class ProcessDataCommand extends Command { protected $signature = 'analytics:process {--date= : Date to process}'; protected $description = 'Process analytics data for specified date'; public function handle() { $date = $this->option('date') ?? now()->toDateString(); $this->info("Processing analytics for {$date}"); // Processing logic here $processed = 5000; $this->info("Processed {$processed} records successfully"); return self::SUCCESS; } } // Usage from terminal: // php artisan analytics:process // php artisan analytics:process --date=2025-12-28 ``` ## Middleware Registration and Usage Automatically aliased middleware for route protection ```php // app/Plugins/Analytics/Middleware/AdminMiddleware.php namespace App\Plugins\Analytics\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class AdminMiddleware { public function handle(Request $request, Closure $next): Response { if (!auth()->user()?->isAdmin()) { abort(403, 'Unauthorized access to analytics'); } return $next($request); } } // Usage in routes (automatically aliased as 'analytics.adminmiddleware'): Route::get('/admin/reports', 'reports') ->middleware('analytics.adminmiddleware'); // Or in controller constructor: public function __construct() { $this->middleware('analytics.adminmiddleware') ->only(['reports', 'export']); } ``` ## Enums with Helper Methods Type-safe enumerations with utility functions ```php // app/Plugins/Analytics/Enums/Status.php namespace App\Plugins\Analytics\Enums; enum Status: string { case ACTIVE = 'active'; case INACTIVE = 'inactive'; case PENDING = 'pending'; case DISABLED = 'disabled'; public function label(): string { return match($this) { self::ACTIVE => 'Active', self::INACTIVE => 'Inactive', self::PENDING => 'Pending', self::DISABLED => 'Disabled', }; } public function color(): string { return match($this) { self::ACTIVE => 'green', self::INACTIVE => 'gray', self::PENDING => 'yellow', self::DISABLED => 'red', }; } } // Usage: use App\Plugins\Analytics\Enums\Status; $status = Status::ACTIVE; echo $status->label(); // "Active" echo $status->color(); // "green" // In Blade: {{ $status->label() }} ``` ## Traits for Shared Functionality Reusable concerns across plugin components ```php // app/Plugins/Analytics/Concerns/Cacheable.php namespace App\Plugins\Analytics\Concerns; trait Cacheable { protected int $cacheTimeout = 3600; public function getCacheKey(string $suffix = ''): string { return static::class . ($suffix ? ":{$suffix}" : ''); } public function clearCache(string $suffix = ''): void { cache()->forget($this->getCacheKey($suffix)); } public function remember(string $key, callable $callback, ?int $ttl = null) { return cache()->remember( $this->getCacheKey($key), $ttl ?? $this->cacheTimeout, $callback ); } } // Usage in any class: use App\Plugins\Analytics\Concerns\Cacheable; class ReportService { use Cacheable; public function getDailyStats() { return $this->remember('daily_stats', function() { // Expensive database query return DB::table('analytics')->whereDate('created_at', today())->get(); }); } } ``` ## Plugin Facade for Programmatic Access Convenient facade for plugin management operations ```php use SaeedVir\LaravelPluginable\Facades\Plugin; // Get all registered plugins $plugins = Plugin::all(); foreach ($plugins as $name => $plugin) { echo "Plugin: {$name} at {$plugin['path']}\n"; } // Find specific plugin $analytics = Plugin::find('Analytics'); if ($analytics) { print_r($analytics['components']); } // Check if plugin is enabled if (Plugin::enabled('Analytics')) { echo "Analytics plugin is active"; } // Check manifest source (cached or scanned) $source = Plugin::getManifestSource(); echo "Manifest loaded from: {$source}"; // "Cached" or "Scanned" ``` ## Blade Hooks for Content Injection Inject plugin content into application views ```php // In your layout file (resources/views/layouts/app.blade.php): @yield('title') @pluginHook('head_scripts') @pluginHook('body_start') @yield('content') @pluginHook('body_end') // Register hooks in plugin provider: // app/Plugins/Analytics/AnalyticsProvider.php namespace App\Plugins\Analytics; use Illuminate\Support\ServiceProvider; use SaeedVir\LaravelPluginable\Facades\Plugin; class AnalyticsProvider extends ServiceProvider { public function boot(): void { Plugin::registerHook('head_scripts', view('analytics::tracking-script')); Plugin::registerHook('body_end', function() { return ''; }); Plugin::registerHook('body_start', '
🔍 Analytics Active
'); } } ``` ## Translations and Localization Namespaced translation files automatically loaded ```php // app/Plugins/Analytics/Lang/messages.php return [ 'welcome' => 'Welcome to Analytics plugin', 'failed' => 'Analytics tracking failed', 'success' => 'Data tracked successfully', 'reports' => [ 'daily' => 'Daily Report', 'weekly' => 'Weekly Report', ], ]; // Usage in code: echo trans('Analytics::messages.welcome'); echo trans('Analytics::messages.reports.daily'); // Usage in Blade: @lang('Analytics::messages.welcome') {{ __('Analytics::messages.reports.daily') }} // With replacements: trans('Analytics::messages.success', ['count' => 100]) ``` ## Performance Caching for Production Cache plugin manifest to avoid filesystem scans ```bash # Cache all plugin metadata php artisan plugin:cache # Clear plugin cache php artisan plugin:clear # List all plugins with status php artisan plugin:list ``` ```php // The plugin:cache command creates bootstrap/cache/plugins.php: return [ 'Analytics' => [ 'name' => 'Analytics', 'path' => '/var/www/app/Plugins/Analytics', 'components' => [ 'commands' => ['ProcessDataCommand'], 'controllers' => ['AnalyticsController', 'ApiController'], 'events' => ['DataProcessedEvent'], 'listeners' => ['SendNotificationListener'], 'middleware' => ['AdminMiddleware'], 'services' => ['AnalyticsService'], 'migrations' => '/var/www/app/Plugins/Analytics/database/migrations', 'provider' => true, 'config' => true, 'routes' => true, 'views' => '/var/www/app/Plugins/Analytics/Views', 'lang' => '/var/www/app/Plugins/Analytics/Lang', ] ] ]; ``` ## Plugin Configuration Management Per-plugin configuration files automatically loaded ```php // app/Plugins/Analytics/config.php return [ 'name' => 'Analytics', 'version' => '2.1.0', 'description' => 'Advanced analytics tracking', 'enabled' => true, 'api_key' => env('ANALYTICS_API_KEY'), 'tracking' => [ 'pageviews' => true, 'events' => true, 'errors' => false, ], 'retention_days' => 90, ]; // Access anywhere in application: $apiKey = config('Analytics.api_key'); $enabled = config('Analytics.enabled'); $retention = config('Analytics.retention_days'); // Check if tracking is enabled: if (config('Analytics.tracking.pageviews')) { // Track pageview } ``` ## Adding Components to Existing Plugins Extend plugins without recreating entire structure ```bash # Add new controller to existing plugin php artisan make:plugin Analytics --controller=ReportController # Add multiple components at once php artisan make:plugin Analytics \ --event=ReportGeneratedEvent \ --listener=EmailReportListener \ --middleware=RateLimitMiddleware # Duplicate detection (component already exists): php artisan make:plugin Analytics --command=ProcessDataCommand # Output: Command file 'ProcessDataCommand.php' already exists in plugin 'Analytics'. Skipping... ``` ## Livewire Volt Views Interactive component-based views with automatic routing ```php // app/Plugins/Analytics/Views/dashboard.blade.php stats = [ 'visitors' => 1542, 'pageviews' => 8734, 'bounce_rate' => 42.3, ]; } public function refresh(): void { $this->stats['visitors'] += rand(1, 10); $this->stats['pageviews'] += rand(5, 50); } } ?>

{{ $message }}

Visitors {{ number_format($stats['visitors']) }}
Pageviews {{ number_format($stats['pageviews']) }}
Bounce Rate {{ $stats['bounce_rate'] }}%
// Route automatically registered via Volt: // Volt::route('/', 'index'); // Accessible at: http://your-app.com/analytics/ ``` ## Database Migrations Support Plugin-specific migrations automatically discovered ```php // app/Plugins/Analytics/database/migrations/2025_12_28_000001_create_analytics_events_table.php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('analytics_events', function (Blueprint $table) { $table->id(); $table->string('event_name'); $table->string('session_id')->index(); $table->json('properties')->nullable(); $table->ipAddress('ip_address')->nullable(); $table->string('user_agent')->nullable(); $table->timestamps(); $table->index(['event_name', 'created_at']); }); } public function down(): void { Schema::dropIfExists('analytics_events'); } }; // Migrations automatically discovered and run with: // php artisan migrate ``` Laravel Pluginable provides a robust foundation for building modular Laravel applications with complete separation of concerns. The package excels in scenarios requiring multi-tenant customization, marketplace-style plugin ecosystems, or microservice-oriented architectures within monolithic applications. Each plugin operates as an independent module with its own routes, controllers, services, middleware, and views, yet integrates seamlessly with the host application through automatic service provider registration and dependency injection. Integration patterns include service injection for cross-plugin communication, event-driven architecture for loose coupling, Blade hooks for UI extensibility, and configuration management for runtime behavior control. The caching system ensures production performance while maintaining development flexibility, and the Artisan command suite provides comprehensive tooling for plugin creation and management. This makes Laravel Pluginable ideal for SaaS platforms, CMS systems, e-commerce marketplaces, or any application requiring extensibility without core codebase modification.