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

gpslab/domain-event-bundle

Symfony bundle that integrates gpslab/domain-event with the framework, making it easy to publish and handle domain events through Symfony services and configuration. Lightweight setup for decoupled domain events in Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

gpslab/domain-event-bundle is a Symfony bundle, not a Laravel package — there is no composer.json extra.laravel block, no auto-discovery, no publishable config. composer require gpslab/domain-event-bundle will install it, but nothing will happen until you wire the services yourself. Given 14 stars, zero dependents and a last release of May 2021, treat it as a reference implementation you can vendor or reimplement rather than a dependency you inherit.

Read these three files first; they define the entire contract:

  1. GpsLab\Component\DomainEvent\EventBus\EventBusInterface — the whole API is basically publish(DomainEvent $event): void.
  2. GpsLab\Component\DomainEvent\Event\SubscriberInterface — getSubscribedEvents(): array mapping event FQCN → method name(s), mirroring Symfony's EventSubscriberInterface.
  3. GpsLab\Component\DomainEvent\Aggregate\PullDomainEventsTrait — raise() / releaseEvents() for aggregate roots.

The first use case is the canonical one: an aggregate records events internally, and the application layer publishes them after persisting.

final class Order
{
    use PullDomainEventsTrait;

    public function pay(): void
    {
        // ... invariants, state change ...
        $this->raise(new OrderPaid($this->id));
    }
}

// in the use case / command handler
foreach ($order->releaseEvents() as $event) {
    $this->bus->publish($event);
}

That's the mental model. Everything else — the deferred bus, the container listener locator, the compiler pass — is plumbing to make that loop lazy, transactional, or framework-native.

Implementation Patterns

Subscriber vs. listener. Two registration styles ship with the package: ListenerInterface::handleEvent(DomainEvent $event) for single-event classes, and SubscriberInterface::getSubscribedEvents() for classes that fan out to several events. Prefer subscribers — one class per bounded-context concern (e.g. OrderNotifier handling OrderPaid, OrderRefunded) beats one class per event.

Two bus implementations. EventBus is a plain in-memory implementation given a ListenerLocatorInterface; SymfonyEventBus adapts to Symfony's EventDispatcher, registering the domain event class name as the dispatcher event name. In Laravel you will almost certainly skip SymfonyEventBus (Laravel's dispatcher has a different signature and its own subscriber contract) and use EventBus directly, or write a five-line adapter around Laravel's Event::dispatch.

Deferred publishing for transactions. DeferredEventBus wraps the real bus and queues events until you call release() (with clear() to drop them). This is the pattern you want around any state-changing use case:

DB::transaction(function () use ($order, $bus) {
    $orders->save($order);
    foreach ($order->releaseEvents() as $event) {
        $bus->publish($event);
    }
});
$bus->release(); // outside the transaction

If you'd rather not manage release ordering, Laravel's DB::afterCommit() gives you the same semantics with less rope.

Laravel service provider. Keep it small — bind the interface, provide the locator:

$this->app->singleton(ListenerLocatorInterface::class, function ($app) {
    return new ContainerListenerLocator($app, [
        OrderPaid::class => [OrderNotifier::class],
    ]);
});
$this->app->singleton(EventBusInterface::class, fn ($app) =>
    new EventBus($app->make(ListenerLocatorInterface::class))
);

Bridging to Laravel's event system. If you want queued listeners and Event::fake() in tests, have a single EventBus listener that forwards into Event::dispatch($event) and let Laravel do the routing. You lose getSubscribedEvents() and gain jobs, ShouldQueue, and the testing helpers — usually the right trade.

Reimplementing. The component is maybe 150 lines total. A DomainEventBus with a [$eventClass => [$listener, ...]] map plus a deferred variant is an afternoon's work and removes the Symfony DI coupling entirely. With this package's adoption numbers, that is a legitimate answer.

Gotchas and Tips

  • No getSubscribedEvents container wiring. The bundle's compiler pass (RegisterListenersCompilerPass / subscriber tagging) is Symfony DI-specific and does nothing in Laravel. You must build the event-class→listener map yourself — via config, attributes, or a directory scan.
  • The deferred bus fails silently. Forget release() and nothing throws; events simply never dispatch. Wrap it in a finally or in DB::afterCommit() so it can't be dropped.
  • Release after the commit, not inside it. Publishing inside the transaction means listeners fire for work that may roll back — and if a listener sends mail or pushes to a queue, the damage is already done.
  • Synchronous recursion. EventBus::publish() invokes listeners inline. A listener that publishes another event that reaches the same listener is an infinite loop. Watch for it especially with deferred release, where a large queued batch cascades.
  • ContainerListenerLocator wants a PSR-11 container. Laravel's Application implements it, so app() works — but resolve lazily, never eagerly at boot, or you'll instantiate every listener on every request.
  • 2021-era Symfony types. The bundle's internals assume Symfony 4/5 signatures. Modern PHP's stricter return types and Symfony 6/7's EventDispatcherInterface::dispatch(object, string) ordering will bite if you use SymfonyEventBus. Test before assuming compatibility.
  • No async dispatch. Everything is in-process and synchronous. If you need queues, that's a Laravel-side concern (ShouldQueue, dispatch()->afterCommit()), not a package feature.
  • Testing. The bus takes a locator, so an in-memory ListenerLocator with spy listeners gives you a clean unit test of an aggregate without booting the framework. Assert on releaseEvents() output from the aggregate and on listener invocations separately — don't conflate them.
  • Extension points, such as they are: swap ListenerLocatorInterface for a custom locator (attribute-driven, PSR-4 directory scan) and swap EventBusInterface for a decorated version (logging, transactional, queueing). Both are one-method interfaces, which is the package's genuine virtue — the interfaces are more useful than the implementations.
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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle