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 Bundle Laravel Package

astina/domain-events-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require astina/domain-events-bundle:dev-master
    

    Ensure your doctrine/doctrine-bundle is on dev-master (as per the package requirement).

  2. Enable the Bundle: Add to config/bundles.php (or AppKernel.php for older Laravel versions):

    Astina\Bundle\DomainEventsBundle\AstinaDomainEventsBundle::class => ['all' => true],
    
  3. First Use Case: Create a subscriber service (e.g., app/Services/EventListeners/ProductUpdatedListener.php):

    namespace App\Services\EventListeners;
    
    class ProductUpdatedListener
    {
        public function postUpdate(\App\Models\Product $product, $eventArgs)
        {
            // Handle post-update logic (e.g., log, notify, or trigger workflows)
            logger()->info("Product updated: {$product->name}");
        }
    }
    
  4. Register the Subscriber: Define the service in config/services.yaml:

    services:
        App\Services\EventListeners\ProductUpdatedListener:
            arguments: ['@logger'] # Inject dependencies if needed
    
  5. Configure the Listener: Add to config/packages/astina_domain_events.yaml:

    astina_domain_events:
        subscribers:
            App\Services\EventListeners\ProductUpdatedListener:
                entity: App\Models\Product
                events: [postUpdate]
    

Implementation Patterns

Workflow Integration

  1. Event-Driven Logic: Use subscribers to decouple domain logic from entities. Example:

    // app/Services/EventListeners/OrderFulfillmentListener.php
    class OrderFulfillmentListener
    {
        public function postPersist(\App\Models\Order $order, $eventArgs)
        {
            if ($order->isFulfillable()) {
                $this->fulfillmentService->process($order);
            }
        }
    }
    
  2. Dependency Injection: Inject services into listeners via constructor or arguments:

    services:
        App\Services\EventListeners\InventoryListener:
            arguments:
                - '@inventoryService'
                - '@notificationService'
    
  3. Conditional Logic: Use $eventArgs to inspect entity state or event metadata:

    public function preUpdate(\App\Models\User $user, PreUpdateEventArgs $eventArgs)
    {
        $uow = $eventArgs->getObjectManager()->getUnitOfWork();
        $changes = $uow->getEntityChangeSet($user);
    
        if (isset($changes['role'])) {
            $this->roleChangeHandler->handle($user, $changes['role'][1]);
        }
    }
    
  4. Bulk Operations: Leverage preFlush/postFlush for batch processing:

    public function preFlush($eventArgs)
    {
        $entityManager = $eventArgs->getObjectManager();
        $entities = $entityManager->getUnitOfWork()->getScheduledEntityInsertions();
    
        foreach ($entities as $entity) {
            if ($entity instanceof \App\Models\Loggable) {
                $this->logger->logEntityAction($entity, 'create');
            }
        }
    }
    
  5. Cross-Entity Workflows: Chain listeners to propagate events across aggregates:

    astina_domain_events:
        subscribers:
            App\Services\EventListeners\OrderStatusListener:
                entity: App\Models\Order
                events: [postUpdate]
            App\Services\EventListeners\InventorySyncListener:
                entity: App\Models\Product
                events: [postUpdate] # Triggered by OrderStatusListener
    

Gotchas and Tips

Pitfalls

  1. Doctrine Version Lock:

    • The bundle requires doctrine/doctrine-bundle:dev-master. Avoid using stable releases to prevent runtime errors.
    • Workaround: Pin to a specific dev-master commit if stability is critical.
  2. Method Naming Strictness:

    • The subscriber method must exactly match the event name (e.g., postUpdate, not handlePostUpdate). Case-sensitive.
  3. Service ID Mismatch:

    • The subscribers YAML key must use the exact service ID defined in services.yaml. Typos here will silently fail.
  4. Event Argument Order:

    • The first parameter must be the entity class, followed by $eventArgs. Reversing this breaks binding:
      // ❌ Wrong
      public function postUpdate($eventArgs, \App\Models\User $user) { ... }
      
  5. Circular Dependencies:

    • Avoid injecting the EntityManager directly into listeners. Use $eventArgs->getObjectManager() instead to prevent circular references.
  6. Performance with preFlush/postFlush:

    • These events fire once per transaction, not per entity. Heavy processing here can block the entire transaction.

Debugging Tips

  1. Verify Subscriber Registration: Check if the subscriber is loaded by inspecting the EventManager:

    $eventManager = $entityManager->getEventManager();
    $listeners = $eventManager->getListeners('preUpdate');
    dump($listeners); // Should include your subscriber
    
  2. Enable Doctrine Debugging: Add to config/packages/doctrine.yaml:

    doctrine:
        orm:
            eventmanager:
                debug: true
    

    Logs event listener invocations to the Symfony profiler.

  3. Test Event Firing: Use a dummy subscriber to confirm events trigger:

    class DebugSubscriber
    {
        public function postPersist($entity, $eventArgs)
        {
            logger()->debug("Event fired for: " . get_class($entity));
        }
    }
    

Extension Points

  1. Dynamic Subscribers: Override the bundle’s SubscriberLoader to load subscribers dynamically (e.g., from a database):

    // src/EventListener/SubscriberLoader.php
    class CustomSubscriberLoader extends \Astina\Bundle\DomainEventsBundle\SubscriberLoader
    {
        public function loadSubscribers()
        {
            $subscribers = $this->fetchFromDatabase(); // Custom logic
            return $subscribers;
        }
    }
    

    Register it in services.yaml:

    services:
        Astina\Bundle\DomainEventsBundle\SubscriberLoader:
            class: App\EventListener\CustomSubscriberLoader
    
  2. Custom Events: Extend the bundle to support non-Doctrine events (e.g., Symfony kernel events) by creating a wrapper service.

  3. Field-Level Subscriptions: Implement a proxy pattern to intercept field-specific changes (as hinted in the TODO):

    class FieldChangeSubscriber
    {
        public function preUpdate($entity, PreUpdateEventArgs $eventArgs)
        {
            $uow = $eventArgs->getObjectManager()->getUnitOfWork();
            $changes = $uow->getEntityChangeSet($entity);
    
            foreach ($changes as $field => $change) {
                if ($this->shouldHandleField($field)) {
                    $this->handleFieldChange($entity, $field, $change);
                }
            }
        }
    }
    
  4. Priority Handling: Use Symfony’s priority tag to control listener execution order:

    services:
        App\Services\EventListeners\HighPriorityListener:
            tags:
                - { name: 'astina_domain_events.subscriber', priority: 100 }
    
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.
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament