Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Domain Events Laravel Package

cvek/domain-events

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require cvek/domain-events
    

    Register the bundle in config/bundles.php (Symfony) or manually bootstrap in Laravel via service provider.

  2. Entity Integration Add RaiseEventsTrait to your domain entity:

    use Cvek\DomainEvents\RaiseEventsTrait;
    
    class User implements \Cvek\DomainEvents\RaiseEventsInterface
    {
        use RaiseEventsTrait;
    }
    
  3. Define a Domain Event Extend AbstractSyncDomainEvent or AbstractAsyncDomainEvent:

    class UserRegisteredEvent extends AbstractSyncDomainEvent
    {
        public function __construct(private User $user) {}
        public function getUser(): User { return $this->user; }
    }
    
  4. Raise an Event Trigger in business logic:

    $user = new User();
    $this->raise(new UserRegisteredEvent($user));
    
  5. Listen to Events Implement a listener (Symfony-style):

    class UserRegisteredListener
    {
        public function __invoke(UserRegisteredEvent $event): void
        {
            // Handle event (e.g., send email)
        }
    }
    

    Register in services.yaml (Symfony) or manually bind in Laravel’s DI container.


First Use Case: Email Notification on User Registration

  1. Create UserRegisteredEvent (async for non-blocking email):
    class UserRegisteredEvent extends AbstractAsyncDomainEvent
    {
        public function __construct(private User $user) {}
    }
    
  2. Raise in UserRepository or service:
    $user->register(); // Internally raises $this->raise(new UserRegisteredEvent($user));
    
  3. Listen via queue consumer (Laravel):
    // app/Listeners/UserRegisteredListener.php
    public function handle(UserRegisteredEvent $event)
    {
        Mail::to($event->getUser()->email)->send(new WelcomeEmail());
    }
    

Implementation Patterns

1. Event Lifecycle Management

Type When Triggered Use Case Async Support?
preFlush Before DB persistence Validate business rules pre-save ❌ No
onFlush During DB persistence Optimistic locking, audit logs ✅ Yes
postFlush After DB persistence Notifications, analytics, external APIs ✅ Yes

Example Workflow:

// 1. Pre-save validation
$this->raise(new ValidateUserEvent($user));

// 2. During save (async: send SMS)
$this->raise(new SendSmsVerificationEvent($user));

// 3. Post-save (async: log to analytics)
$this->raise(new TrackUserCreatedEvent($user));

2. Async Event Handling

Laravel Integration:

  1. Configure async transport (default: db):
    // config/domain_events.php
    'transports' => [
        'async' => [
            'dsn' => 'doctrine://default',
        ],
    ],
    
  2. Use Laravel Queues for consumers:
    // app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('domain-events:consume')->everyMinute();
    }
    
  3. Run consumer:
    php artisan domain-events:consume
    

Best Practices:

  • Use async for external calls (APIs, emails, SMS).
  • Keep preFlush/onFlush sync for critical logic (e.g., invariants).
  • Batch async events to reduce DB load.

3. Event Dispatching Strategies

Strategy When to Use Example
Explicit One-off events in business logic $user->promote() raises event
Aggregate Root Domain-driven design (DDD) UserAggregate dispatches events
Repository Hooks CRUD operations UserRepository::save()
Command Handlers CQRS pattern RegisterUserCommand

Example: Aggregate Root Pattern

class UserAggregate
{
    use RaiseEventsTrait;

    public function promoteToAdmin()
    {
        $this->role = 'admin';
        $this->raise(new UserRoleChangedEvent($this, 'admin'));
    }
}

4. Testing Events

Unit Test Example:

public function test_user_registration_emits_event()
{
    $user = new User();
    $event = new UserRegisteredEvent($user);

    $this->expectException(LogicException::class);
    $this->expectExceptionMessage('Email not valid');

    $user->register(); // Should raise $event
    // Assert event was raised (use a mock listener)
}

Integration Test (Async):

public function test_async_event_is_processed()
{
    $event = new UserRegisteredEvent(new User());
    $this->app->make(DomainEventDispatcher::class)->dispatch($event);

    // Assert queue job was dispatched (Laravel)
    $this->assertDatabaseHas('domain_events', [
        'event_class' => UserRegisteredEvent::class,
    ]);
}

Gotchas and Tips

Pitfalls

  1. Async Event Ordering

    • Async events are not guaranteed to execute in order. Use event_id or timestamps for critical workflows.
    • Fix: Add a priority field to events or use Laravel’s queue priorities.
  2. Circular Dependencies

    • Raising events in listeners can cause infinite loops.
    • Fix: Use preFlush for validation, not postFlush.
  3. Doctrine Event System Limits

    • onFlush/postFlush events cannot modify the same entity they’re processing.
    • Fix: Use preFlush for entity changes or async events.
  4. Async Transport Configuration

    • The db transport requires Doctrine. For Laravel, ensure:
      // config/domain_events.php
      'transports' => [
          'async' => [
              'dsn' => 'doctrine://default', // Laravel: 'doctrine://entity_manager'
          ],
      ],
      
  5. Event Serialization

    • Async events must be serializable. Avoid:
      • Closures, resources, or non-serializable properties.
    • Fix: Use DTOs or __serialize() magic method.

Debugging Tips

  1. Log Unhandled Events Add a global listener:

    $dispatcher->addListener('*', function ($event) {
        \Log::debug('Unhandled event', ['event' => get_class($event)]);
    });
    
  2. Check Async Queue Laravel:

    php artisan queue:work --once --queue=domain_events
    

    Symfony:

    php bin/console domain-events:consume
    
  3. Event Dispatcher Inspection Dump all listeners:

    $dispatcher = $this->app->make(DomainEventDispatcher::class);
    print_r($dispatcher->getListeners());
    

Extension Points

  1. Custom Transports Implement TransportInterface for non-DB async (e.g., Redis, RabbitMQ):

    class RedisTransport implements TransportInterface
    {
        public function publish(DomainEvent $event): void
        {
            Redis::publish('domain_events', serialize($event));
        }
    }
    
  2. Event Retry Logic Extend AbstractAsyncDomainEvent to add retry metadata:

    class RetryableEvent extends AbstractAsyncDomainEvent
    {
        public int $retries = 0;
        public ?\DateTimeInterface $retryAt = null;
    }
    
  3. Event Versioning Add a version field to events for backward compatibility:

    class UserRegisteredEvent extends AbstractAsyncDomainEvent
    {
        public const VERSION = '1.0';
        public string $version;
    }
    
  4. Laravel Service Provider Bind the dispatcher and listeners in register():

    $this->app->bind(DomainEventDispatcher::class, function ($app) {
        $dispatcher = new DomainEventDispatcher();
        $dispatcher->addListener(UserRegisteredEvent::class, $app->make(UserRegisteredListener::class));
        return $dispatcher;
    });
    

Laravel-Specific Quirks

  1. Entity Manager Binding Ensure the EntityManager is bound to the doctrine key:

    // config/doctrine.php
    'entity_managers' => [
        'default' => [
            'connection' => 'default',
            'mappings' => [...],
        ],
    ],
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle