ashleydawson/domain-event-dispatcher
Installation
composer require ashleydawson/domain-event-dispatcher
Register the service provider in config/app.php under providers:
AshleyDawson\DomainEventDispatcher\DomainEventDispatcherServiceProvider::class,
Define an Event Create a simple event class (must be serializable):
namespace App\Events;
class UserRegistered {
public function __construct(public string $userId) {}
}
Create a Listener
Implement __invoke() to handle the event:
namespace App\Listeners;
use App\Events\UserRegistered;
class SendWelcomeEmail {
public function __invoke(UserRegistered $event) {
// Logic here
}
}
Dispatch from a Model Use the singleton dispatcher directly in your domain model:
use AshleyDawson\DomainEventDispatcher\DomainEventDispatcher;
class User {
public function register() {
// ... business logic ...
DomainEventDispatcher::getInstance()->dispatch(
new UserRegistered($this->id)
);
}
}
Register Listeners Bind listeners in a service provider or boot method:
DomainEventDispatcher::getInstance()->addListener(
new SendWelcomeEmail()
);
Deferred Dispatching
Use defer() to queue events for later processing (e.g., after transaction commit):
DomainEventDispatcher::getInstance()->defer(
new UserRegistered($userId)
);
Process deferred events via the processDeferred() method (e.g., in a command bus or job).
Typed Listeners
Leverage type-hinting in __invoke() for clarity and IDE support:
public function __invoke(UserRegistered $event) { ... }
Dynamic Registration Register listeners conditionally (e.g., based on config or environment):
if (config('features.email_notifications')) {
DomainEventDispatcher::getInstance()->addListener(
new SendWelcomeEmail()
);
}
Integration with Laravel Events Bridge to Laravel’s event system for broader compatibility:
DomainEventDispatcher::getInstance()->addListener(
new class implements ShouldQueue {
public function __invoke(UserRegistered $event) {
event(new \Illuminate\Queue\Events\JobProcessed(...));
}
}
);
Domain-Driven Dispatching Use in aggregate roots or domain services:
class Order {
public function place() {
// ... validation ...
$this->status = 'placed';
DomainEventDispatcher::getInstance()->dispatch(
new OrderPlaced($this->id)
);
}
}
Service Container Binding Bind the dispatcher to Laravel’s container for dependency injection:
$app->singleton(DomainEventDispatcher::class, function () {
return DomainEventDispatcher::getInstance();
});
Then inject DomainEventDispatcher into classes via constructor.
Testing Mock the dispatcher in tests:
$dispatcher = Mockery::mock(DomainEventDispatcher::class);
$dispatcher->shouldReceive('dispatch')->once();
$this->app->instance(DomainEventDispatcher::class, $dispatcher);
Event Serialization
Ensure events implement Serializable or use __serialize()/__unserialize() for deferred dispatching.
Singleton State The dispatcher is a singleton, so avoid shared state (e.g., static properties in listeners). Use dependency injection for shared dependencies.
Deferred Event Order Deferred events are processed in FIFO order, but not transactionally. Use a queue (e.g., Laravel Queues) for reliability.
Listener Order Listeners are invoked in registration order. Use priority flags (if extended) or explicit ordering logic.
Circular Dependencies
Avoid circular references between events/listeners (e.g., EventA dispatches EventB, which re-dispatches EventA).
Performance Heavy listeners may block dispatching. Offload work to queues or background jobs.
Listener Not Firing Verify:
__invoke().Deferred Events Lost
Ensure processDeferred() is called (e.g., in a console command or HTTP middleware).
Serialization Errors
Check for non-serializable properties in events. Use json_encode()/json_decode() for debugging:
json_encode(new UserRegistered('123')); // Should not throw
Custom Storage
Override deferred event storage (e.g., database or Redis) by extending DomainEventDispatcher:
class CustomDispatcher extends DomainEventDispatcher {
protected function storeDeferredEvent($event) {
// Custom logic
}
}
Listener Priorities Add priority support:
interface ListenerPriority {
public const LOW = 0;
public const HIGH = 100;
}
Modify addListener() to accept a priority parameter.
Event Filtering
Implement filtering (e.g., by event properties) in dispatch():
if (!$this->shouldDispatch($event)) return;
Laravel Integration Extend to support Laravel’s event system:
DomainEventDispatcher::getInstance()->addListener(
new class implements ShouldBroadcast {
public function __invoke(UserRegistered $event) {
broadcast(new UserRegisteredBroadcast($event));
}
}
);
No Built-in Config The package has no config file. Default behavior is fixed; extend the class for customization.
Service Provider
Ensure the provider is registered before first use (e.g., in AppServiceProvider::boot()).
How can I help you explore Laravel packages today?