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

Deferred Event Bundle Laravel Package

fervo/deferred-event-bundle

DeferredEventBundle for Symfony queues domain events during a request or transaction and dispatches them at a controlled later point, so listeners and side effects run after persistence completes and stay consistent with committed data.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this is a Symfony bundle (Symfony 2.x/3.x era, PHP 5.5–7.0), not a Laravel package. There is no service provider, no facade, no config/deferred-event.php. If you're evaluating it from a Laravel codebase, the honest read is: the idea is worth stealing, the code is not portable. In Laravel the equivalent is dispatch(new Job)->afterResponse() or the defer() helper — you'd only reach for this bundle if you inherited a Symfony app that already uses it.

For the Symfony case:

  1. composer require fervo/deferred-event-bundle (pin ~1.0; last tagged release was 2016-01-13, so expect to resolve against old Symfony constraints).
  2. Register new Fervo\DeferredEventBundle\FervoDeferredEventBundle() in AppKernel::registerBundles().
  3. Enable the config in config.yml and list the events you want deferred.

Where to look in the repo, in this order:

  • DependencyInjection/Configuration.php — the whole config surface is small; this tells you exactly which keys exist.
  • The dispatcher decorator (it wraps/replaces the event_dispatcher service) — this is the mechanism. Understanding that it decorates the global dispatcher explains 90% of the behavior and all of the surprises.
  • EventListener/ — the kernel.terminate / FastCGI hook that actually flushes the queued events to another process.

First use case: you have a listener that fires on user.registered and does something slow and non-critical — send a welcome email through a third-party API, push a lead into a CRM, POST to an analytics endpoint. The user shouldn't wait 800ms for that. Mark that event for deferral, let the response go out, and let the deferred pass do the work.

Implementation Patterns

Retrofit by event name, not by listener. The bundle lets you defer events, which means every listener on that event moves out of the request cycle. This is great for adoption (no code change in your controllers) and terrible for precision. Before enabling, grep for every ->addListener('user.registered', ...) and confirm all of them are safe to run after the response is sent.

Keep request-critical listeners inline. Don't defer an event whose handlers mutate state the response depends on — e.g. a listener that writes an audit row you immediately read back, or that invalidates a cache the redirect page uses. Deferral is for side effects, not for computed state.

Design payloads as IDs, not objects. The event is serialized to cross a process boundary. Any listener that receives the event and calls $event->getUser()->getEmail() needs the user to survive serialization — which for Doctrine entities means you're shipping a detached, possibly lazy-proxy-laden object into a fresh process. Strongly prefer events whose getters return scalars, or re-fetch inside the deferred listener:

class UserRegisteredEvent
{
    public function __construct(private int $userId) {}
    public function getUserId(): int { return $this->userId; }
}

// deferred listener — fetch fresh, in its own process
$user = $this->users->find($event->getUserId());
if (!$user) { return; } // deleted between request and deferral
$this->mailer->sendWelcome($user);

Watch the transaction boundary. Deferral that runs at kernel.terminate can execute before your Doctrine flush/commit has landed on disk — especially with a wrapping sub-request that opens its own connection. The classic bug: the deferred listener reads the row that the originating request hasn't committed, gets null, and silently no-ops. If your listener depends on freshly written rows, this ordering matters more than anything else in the setup.

Fall back to cron on hosts without FastCGI. The whole premise is a FastCGI sub-request after the response is flushed. On Apache mod_php, CLI, or a container without PHP-FPM, the deferral degrades — usually into "runs later in the same process," which still helps latency but gives you none of the isolation. Have a scheduled command or a kernel.terminate sweep as a safety net for anything left in the queue store.

Treat deferred listeners as idempotent jobs. There is no retry/backoff/dead-letter machinery here — it's a deferral mechanism, not a queue. Write each handler so running it twice is harmless, and log failures loudly, because nobody is watching the second process's return value.

Gotchas and Tips

  • Serialization failures are the #1 debugging tax. Closures, resources, SplFileInfo, DI containers, entities with lazy proxies — all blow up or silently corrupt at defer time. Reproduce with a plain serialize()/unserialize() on the event in isolation before blaming the bundle.
  • Exceptions vanish. The deferred work runs in a sub-request whose output nobody reads. Make sure your Monolog handlers capture it (same log channel, or a dedicated one), and add an explicit try/catch in the heavy listeners that logs with a correlation id you also log at dispatch time. Otherwise "the email never sent" has no trail at all.
  • Re-entrancy / double dispatch. Because the dispatcher is decorated globally, an event fired inside a deferred listener can itself be queued, and if the deferred sub-request boots the same kernel you can loop. Guard by suppressing deferral for the deferred pass (check the request context/route) or by never raising the same event names from deferred handlers.
  • Config quirks: the option list is tiny and undocumented in the README — read Configuration.php. YAML anchors and %parameters% in the event list are easy to get wrong, and a typo'd event name fails open (runs inline) rather than erroring, so your "it's deferred" assumption may be false.
  • Extension points are thin but real: a custom adapter/transport if you want Redis or the DB instead of FastCGI, and a subscriber around the deferred dispatch for metrics. That's about the extent of it — no middleware pipeline.
  • Maintenance risk is the real blocker. Last release 2016, zero dependents, Symfony 2/3-era signatures (EventDispatcherInterface::dispatch($name, Event $event) ordering). It will not run cleanly on Symfony 5+/PHP 8 without patching the dispatch() call sites and return types. Budget for vendor fork or, more realistically, port the pattern: kernel.terminate + fastcgi_finish_request() is ~40 lines you can own outright.
  • Verdict for day-to-day use: fine as an accelerator for one or two slow, non-critical listeners on a legacy Symfony 2/3 app under PHP-FPM. Not a queue, not maintained, not Laravel-compatible — don't build a critical path on it.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums