rundum/event-base
rundum/event-base gives Laravel developers a lightweight foundation for event-driven code, offering base event classes and helpers for defining, dispatching, and organizing domain events consistently across an application.
This is a Symfony-first package (not Laravel), so treat the first hour as reconnaissance rather than integration.
composer require rundum/event-base, then immediately open vendor/rundum/event-base/src/ and composer.json. You are looking for three things:
Event or a marker interface),*Bundle.php) that must be registered in config/bundles.php, or whether it is a plain library you wire yourself,symfony/event-dispatcher constraint — this tells you which Symfony versions it was actually tested against and is your best compatibility signal.symfony/event-dispatcher. The description is "basic events for symfony projects," so the value is almost certainly a thin convention layer: a base event class, possibly an event-name convention, a recording trait, or a dispatcher decorator. If it is only a marker interface, you may decide plain Symfony events are enough and skip the dependency entirely.OrderPlaced), extend/implement the package's base event, dispatch it from your service after the unit of work completes, and handle it in one listener. That one loop will teach you the package's conventions faster than any doc.// app/Event/OrderPlaced.php
final class OrderPlaced extends BaseEvent // <- whatever the package's base type is
{
public function __construct(public readonly string $orderId) {}
}
# config/services.yaml — required, autoconfigure does NOT tag third-party subscribers for you
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/'
Constructor-inject the dispatcher, never the container. Whether you use the package's dispatcher wrapper or Symfony's EventDispatcherInterface, inject EventDispatcherInterface into the service that owns the state change so the dispatch site is explicit and testable.
Record-then-dispatch (unit-of-work pattern). The most useful day-to-day pattern with any Symfony event base is to collect events on your entity and release them after the write succeeds:
final class Order
{
private array $events = [];
public function place(): void
{
// ... mutate state
$this->events[] = new OrderPlaced($this->id);
}
public function releaseEvents(): array
{
$events = $this->events;
$this->events = [];
return $events;
}
}
Then flush a single place: a Doctrine postFlush listener, a repository decorator, or an application service. This avoids dispatching events for transactions that later roll back.
Listeners vs. subscribers. Use EventSubscriberInterface when a class handles several events or you want the event map co-located; use a plain callable listener when it handles one event and you register it in services.yaml. Subscribers only get auto-tagged with autoconfigure: true and must be in the App\ resource glob — a common silent failure.
Priorities for cross-cutting concerns. Register audit/metrics listeners at a high priority so they run before business listeners, and give each listener a single responsibility. Do not let one listener both persist and send mail — the send will run even when persistence throws.
Bridge to Laravel deliberately. Laravel's Illuminate\Contracts\Events\Dispatcher is not PSR-14/Symfony-compatible. If you want this package inside a Laravel app, install symfony/event-dispatcher and register the Symfony dispatcher as a singleton in a service provider, then dispatch Symfony events from your domain layer while leaving framework events (Eloquent\*, Illuminate\*) on Laravel's dispatcher. Mixing them produces "listener never fires" bugs that are hard to see.
Test with a collecting dispatcher. Assert on dispatched events rather than on listener side effects:
$dispatcher = new class extends EventDispatcher {
public array $dispatched = [];
public function dispatch(object $event, ?string $name = null): object
{
$this->dispatched[] = $event;
return parent::dispatch($event, $name);
}
};
Inject that into your service and assert OrderPlaced was recorded once, with the right payload.
config/event-base.php. Everything is wiring you own. Budget for that or skip it if you are Laravel-only."rundum/event-base": "1.0.*"), and be prepared to vendor/fork it. Read every line of src/ — at this size that is 10 minutes and it removes all guesswork about the base class contract.name() method, or carries a stopPropagation helper changes how you write every event. Do not model your domain events on a guessed contract.preFlush/onFlush means events fire for writes that may roll back. Prefer postFlush and clear the collected events afterward — otherwise a retried flush re-dispatches duplicates.autoconfigure: true is mandatory for subscriber tagging in services.yaml; without it EventSubscriberInterface classes are instantiated but never registered. This is the #1 "the package is broken" false alarm.EventDispatcherInterface, delegates to the inner one) to add logging, correlation IDs, or a dev-mode event timeline; and extend the package's base event class once in your app (App\Event\BaseEvent) so a future swap of the upstream base is a one-file change.debug:event-dispatcher (Symfony) lists every registered listener and its priority — if your listener is missing there, it is a wiring/tag problem, not a package problem.How can I help you explore Laravel packages today?