# Creating Custom Events Learn how to create custom lifecycle events for extensibility in your modules. ## Why Custom Events? Custom lifecycle events allow you to: - Create extension points in your modules - Enable third-party integrations - Decouple module components - Follow the framework's event-driven pattern ## Basic Custom Event ### Step 1: Create Event Class ```php gateways[$name] = $class; } public function getGateways(): array { return $this->gateways; } public function version(): string { return '1.0.0'; } } ``` ### Step 2: Fire Event ```php 'onFrameworkBooted', ]; public function onFrameworkBooted(FrameworkBooted $event): void { // Fire custom event $gatewayEvent = new PaymentGatewaysRegistering(); event($gatewayEvent); // Register all collected gateways foreach ($gatewayEvent->getGateways() as $name => $class) { app('payment.gateways')->register($name, $class); } } } ``` ### Step 3: Listen to Event ```php 'onPaymentGateways', ]; public function onPaymentGateways(PaymentGatewaysRegistering $event): void { $event->gateway('stripe', StripeGateway::class); } } ``` ## Event with Multiple Methods Provide different registration methods: ```php types[$name] = $model; } public function renderer(string $type, string $class): void { $this->renderers[$type] = $class; } public function validator(string $type, array $rules): void { $this->validators[$type] = $rules; } public function getTypes(): array { return $this->types; } public function getRenderers(): array { return $this->renderers; } public function getValidators(): array { return $this->validators; } } ``` **Usage:** ```php public function onContentTypes(ContentTypesRegistering $event): void { $event->type('video', Video::class); $event->renderer('video', VideoRenderer::class); $event->validator('video', [ 'url' => 'required|url', 'duration' => 'required|integer', ]); } ``` ## Event with Configuration Pass configuration to listeners: ```php providers[$name] = [ 'class' => $class, 'config' => array_merge($this->config[$name] ?? [], $config), ]; } public function getProviders(): array { return $this->providers; } } ``` **Fire with Config:** ```php $event = new AnalyticsProvidersRegistering( config('analytics.providers') ); event($event); ``` ## Event Versioning Track event versions for backward compatibility: ```php endpoints[] = compact('path', 'controller', 'options'); } // v1 compatibility method (deprecated) public function route(string $path, string $controller): void { $this->endpoint($path, $controller, ['deprecated' => true]); } } ``` **Check Version in Listener:** ```php public function onApiEndpoints(ApiEndpointsRegistering $event): void { if (version_compare($event->version(), '2.0.0', '>=')) { // Use v2 API $event->endpoint('/posts', PostController::class, [ 'middleware' => ['auth:sanctum'], ]); } else { // Use v1 API (deprecated) $event->route('/posts', PostController::class); } } ``` ## Event Priority Control listener execution order: ```php themes[] = compact('name', 'class', 'priority'); } public function getThemes(): array { // Sort by priority (higher first) usort($this->themes, fn($a, $b) => $b['priority'] <=> $a['priority']); return $this->themes; } } ``` **Usage:** ```php public function onThemes(ThemesRegistering $event): void { $event->theme('default', DefaultTheme::class, priority: 0); $event->theme('premium', PremiumTheme::class, priority: 100); } ``` ## Event Validation Validate registrations: ```php fields[$type] = $class; } public function getFields(): array { return $this->fields; } } ``` ## Event Documentation Document your events with docblocks: ```php processor('watermark', WatermarkProcessor::class); * $event->processor('thumbnail', ThumbnailProcessor::class); * } * ``` */ class MediaProcessorsRegistering extends LifecycleEvent { protected array $processors = []; /** * Register a media processor. * * @param string $name Processor name (e.g., 'watermark') * @param string $class Processor class (must implement ProcessorInterface) */ public function processor(string $name, string $class): void { $this->processors[$name] = $class; } /** * Get all registered processors. * * @return array */ public function getProcessors(): array { return $this->processors; } } ``` ## Testing Custom Events ```php app->boot(); Event::assertDispatched(PaymentGatewaysRegistering::class); } public function test_registers_payment_gateway(): void { $event = new PaymentGatewaysRegistering(); $event->gateway('stripe', StripeGateway::class); $this->assertEquals( ['stripe' => StripeGateway::class], $event->getGateways() ); } public function test_stripe_module_registers_gateway(): void { $event = new PaymentGatewaysRegistering(); $boot = new \Mod\Stripe\Boot(); $boot->onPaymentGateways($event); $this->assertArrayHasKey('stripe', $event->getGateways()); } } ``` ## Best Practices ### 1. Use Descriptive Names ```php // ✅ Good class PaymentGatewaysRegistering extends LifecycleEvent // ❌ Bad class RegisterGateways extends LifecycleEvent ``` ### 2. Provide Fluent API ```php // ✅ Good - chainable public function gateway(string $name, string $class): self { $this->gateways[$name] = $class; return $this; } // Usage: $event->gateway('stripe', StripeGateway::class) ->gateway('paypal', PayPalGateway::class); ``` ### 3. Validate Early ```php // ✅ Good - validate on registration public function gateway(string $name, string $class): void { if (!class_exists($class)) { throw new InvalidArgumentException("Gateway class not found: {$class}"); } $this->gateways[$name] = $class; } ``` ### 4. Version Your Events ```php // ✅ Good - versioned use HasEventVersion; public function version(): string { return '1.0.0'; } ``` ## Real-World Example Complete example of a custom event system: ```php // Event class SearchProvidersRegistering extends LifecycleEvent { use HasEventVersion; protected array $providers = []; public function provider( string $name, string $class, int $priority = 0, array $config = [] ): void { $this->providers[$name] = compact('class', 'priority', 'config'); } public function getProviders(): array { uasort($this->providers, fn($a, $b) => $b['priority'] <=> $a['priority']); return $this->providers; } public function version(): string { return '1.0.0'; } } // Fire event $event = new SearchProvidersRegistering(); event($event); foreach ($event->getProviders() as $name => $config) { app('search')->register($name, new $config['class']($config['config'])); } // Listen to event class Boot { public static array $listens = [ SearchProvidersRegistering::class => 'onSearchProviders', ]; public function onSearchProviders(SearchProvidersRegistering $event): void { $event->provider('posts', PostSearchProvider::class, priority: 100); $event->provider('users', UserSearchProvider::class, priority: 50); } } ``` ## Learn More - [Lifecycle Events →](/packages/core/events) - [Module System →](/packages/core/modules)