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

Lifecycle Events Bundle Laravel Package

w3c/lifecycle-events-bundle

A Symfony bundle that integrates Doctrine entity lifecycle events with Symfony’s event dispatcher, letting you handle prePersist, postPersist, preUpdate, postUpdate, preRemove, and postRemove hooks in dedicated listeners instead of entity callbacks.

View on GitHub
Deep Wiki
Context7

Getting Started

Be aware up front: this is a Symfony bundle backed by Doctrine ORM, not a Laravel package. You cannot drop it into a Laravel app — there is no service provider, no Eloquent integration, and no composer require that will "just work" in an app/ tree. Treat this assessment as evaluating the pattern (decoupled entity lifecycle events) through a Laravel lens, and only adopt the package itself if you're also running a Symfony/Doctrine service.

For a Symfony project the minimal steps are roughly:

  1. composer require w3c/lifecycle-events-bundle
  2. Register W3C\LifecycleEventsBundle\W3CLifecycleEventsBundle in config/bundles.php
  3. Configure the bundle in config/packages/w3c_lifecycle_events.yaml (which entity classes are tracked, which create/update/delete events to dispatch — check the README and DependencyInjection/Configuration.php for the actual keys, since defaults are not obvious)
  4. Tag your entity classes (or implement the bundle's lifecycle interface) so the Doctrine subscriber knows to fire events for them
  5. Create a listener/subscriber for the dispatched events and hang your side effects off it

Where to look first in the repo: the README (short — read it end to end), then EventListener/ for the Doctrine lifecycle hooks, then Event/ for the actual event objects you'll consume. The DependencyInjection/Configuration.php is where the real config surface lives.

First use case: the canonical one is dispatch-on-postPersist/postUpdate/postRemove so you can send a notification, write an audit record, or push to a search index without coupling that logic to a controller or to the entity itself.

Implementation Patterns

The mental model maps cleanly to Laravel observers

If you know Laravel's model events (creating, created, updating, updated, deleting, deleted), you already understand what this bundle is trying to do. The difference is where the events live and how they're typed:

Laravel This bundle
Model::created(fn ($model) => ...) EventDispatcher listener on a typed CreateEvent
php artisan make:observer UserObserver A tagged Symfony EventSubscriberInterface
Model events fire on Eloquent save/delete Doctrine postPersist/postUpdate/postRemove
Events are string-keyed (eloquent.created: App\Models\User) Events are typed objects with getEntity()

The pattern is the same: keep persistence-adjacent side effects out of the write path and out of the entity. When you find yourself wanting to email on user creation, index on product update, or log on soft-delete, that's the shape this serves.

Pattern 1 — Fan-out side effects through an event subscriber

The idiomatic use is one subscriber per concern, each listening to the entity lifecycle events it cares about:

class NotifySalesOnOrderUpdate implements EventSubscriberInterface
{
    public function __construct(private Mailer $mailer) {}

    public static function getSubscribedEvents(): array
    {
        return [
            OrderUpdatedEvent::class => 'onUpdate',
        ];
    }

    public function onUpdate(OrderUpdatedEvent $event): void
    {
        $order = $event->getEntity(); // per README the entity hangs off the event
        // decide, dispatch, mail, etc.
    }
}

This is the Laravel equivalent of a dedicated observer, but it's global — every configured entity type funnels through the same dispatcher, so your subscriber must be defensive about types unless the bundle filters per-class.

Pattern 2 — Audit log / cache invalidation spine

Because the events are fired for every tracked entity, the highest-value integration is a generic "on any create/update/delete" listener that writes an immutable audit row or busts a cache key. Do it once, keyed by entity class + id, and you never forget to invalidate a cache on a write again.

Pattern 3 — Combine with transactional wrappers in your domain services

Since the events fire inside the Doctrine flush(), treat them as "inside the transaction" hooks. A common split is:

  • In-transaction: audit trail, cache key invalidation, domain event outbox rows
  • Post-commit: emails, HTTP calls, webhooks

If the bundle only gives you in-flush events, write an outbox row in the subscriber and hand that to a separate worker/handler. In Laravel you'd do exactly this with ShouldDispatchAfterCommit (Laravel 8.38+) or by queuing the notification with dispatchAfterCommit().

Pattern 4 — Porting the concept to Laravel (the "why you should care" angle)

If you're evaluating this for inspiration in a Laravel app, the equivalent stack is:

  • Eloquent observers for per-model concerns (app/Observers/)
  • Model events in a base model for cross-cutting audit/cache invalidation
  • ShouldBroadcast / ShouldQueue on the events so listeners don't slow the request
  • DB::afterCommit() (or event listeners implementing ShouldDispatchAfterCommit) to defer risky side effects until data is durable

You get 95% of this bundle's value with zero extra dependencies in a Laravel codebase via observers. That's the practical takeaway.

Gotchas and Tips

The elephant in the room: it's not for Laravel

Don't waste a day trying to make this work in an Illuminate app. There is no Laravel bridge, no facade, no service provider. Use php artisan make:observer and app/Observers; that is the Laravel-native answer to this package.

Doctrine-only, and Doctrine flush is not a request transaction

Events fire inside EntityManager::flush(). Consequences:

  • Not all-or-nothing with HTTP response. If the DB transaction rolls back after the subscriber already sent a Slack message, that message is gone forever. Queue or outbox anything external.
  • You can't safely re-flush() inside a listener. Doing so during a Doctrine postPersist/postUpdate is a classic footgun and can loop or trigger the UnitOfWork into inconsistent state. Persist related entities via the same flush() or defer.
  • Deletes are especially tricky. postRemove fires before the actual DELETE statement in some flows; if your listener touches the entity you can get weird state. Prefer capturing an id + class at event time.

Entity re-flush loops

If a listener mutates a tracked entity, you can re-trigger the update event and create an infinite loop or a "unit of work is not clean" error. Guard by comparing a hash of relevant fields (or a dirty() snapshot) before firing side effects, the way you'd guard a Laravel model observer.

Low-trust signal from the repo

  • 18 stars, 0 dependents — very small ecosystem; you're largely on your own for bugs.
  • License is NOASSERTION. Before using in commercial code, read the actual LICENSE file. GitHub not being able to classify it is a yellow flag, not necessarily fatal, but verify.
  • Last release dated 2026-05-27 in the metadata is suspicious (likely a mirror or future-dated tag); cross-check the actual tag history on GitHub before pinning a version.
  • No community answers. Search Stack Overflow for "W3CLifecycleEventsBundle" and you'll find near-nothing. Expect to read the source for your specific case.

Config quirks to expect

  • The bundle almost certainly requires you to opt entities in (interface, annotation, or config list). If events aren't firing, check the opt-in first — this is the single most common failure mode with Doctrine lifecycle bundles.
  • The create/update event set is likely limited to persisted state changes, not "was this field actually changed?" — if you need dirty-field granularity, compare values in the listener ($event->getEntity()->field against a snapshot) or move to Doctrine's onFlush with UnitOfWork::getEntityChangeSet().
  • Symfony version compatibility matters. composer.json constraints and W3CLifecycleEventsBundle::getContainerExtension() style boilerplate tend to lag; check the Symfony range in composer.json before installing.

Debugging playbook

  1. php bin/console debug:event-dispatcher — confirm the bundle's Doctrine listener is actually registered.
  2. Dump in the subscriber with var_dump(get_class($event), $event->getEntity()) at the very top — if you never see the subscriber called, it's an opt-in config issue, not a listener issue.
  3. If subscribers fire but with the wrong entities, inspect the bundle's EventListener/ to understand the filter — some bundles track everything, some require an interface.
  4. For "nothing happens after save," confirm you called $em->flush(); persist() alone doesn't fire postPersist.

Extension points worth knowing

  • Custom event classes: if the bundle lets you configure which event class is dispatched per operation, do that — a UserCreatedEvent is far nicer to type in listeners than a generic LifecycleEvent with a string $action.
  • Event subscriber tags: all listener wiring is via Symfony's kernel.event_subscriber autoconfiguration, so standard priorities (['onUpdate', 10]) work; use them when ordering matters (audit before side effects, not after).
  • The real "extension point" for Laravel users is a BaseModel booted() hook that wires an AuditableObserver and a CacheInvalidationObserver across every model — it's ~30 lines and gives you the same fan-out the bundle advertises.

Bottom line

The pattern is sound and worth adopting. The package is a Symfony/Doctrine-specific implementation with a small, unaudited footprint and no Laravel path. If you're in Laravel: replicate it with observers, DB::afterCommit(), and queued events, and skip the dependency. If you're in Symfony and need entity lifecycle events without Doctrine's onFlush boilerplate, it's a reasonable small helper — just pin it, read the source, and keep all external side effects out of the flush.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log