# 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):