ruwork/doctrine-behaviors-bundle
Symfony bundle for integrating Doctrine behavioral extensions, simplifying configuration of behaviors such as timestampable, blameable, sluggable, tree, and soft-deletable entities in your projects.
This bundle is archived (last release 2018-06-13) and has zero dependents, so treat the README and src/ as the only source of truth. Before wiring anything in, clone the repo and read three things in this order: DependencyInjection/Configuration.php (what is configurable), the EventListener/ or Doctrine/ folder (the subscribers that actually mutate entities), and src/ for the traits you are expected to put on entities. The package follows the standard "traits + Doctrine event subscribers" pattern used by behavior bundles: you add a trait to an entity, a listener fills in the field on prePersist/preUpdate.
Minimal setup on the Symfony 3.4/4.x + Doctrine ORM 2.x stack it targets:
vcs repository in composer.json and require dev-master (or the 2018 tag) explicitly.new Ruwork\DoctrineBehaviorsBundle\RuworkDoctrineBehaviorsBundle() in AppKernel.php, or the equivalent line in config/bundles.php on Symfony 4.services: import or rely on Extension::load).bin/console debug:event-dispatcher doctrine.eventSubscriber (or doctrine.event_listener) to confirm the subscribers registered.First use case to validate the whole chain: add the timestampable trait to one throwaway entity, map the two columns (created_at, updated_at) yourself if the bundle does not do it via loadClassMetadata, run doctrine:schema:update --dump-sql, persist, and check the values are populated. If that works end to end, the rest of the behaviors (blameable, soft-deletable, etc.) follow the same wiring.
Trait-per-entity, one behavior at a time. Apply a single trait to a single entity first, verify the columns and listener fire, then add the next behavior. Do not stack four behaviors on a fresh entity and debug them together — the failure modes (missing mapping, listener not registered, wrong field name) look identical from the outside.
Blameable needs a user resolver. This is the main integration point. The bundle will either call a global callable, read from a configured service, or expect you to set the user on the subscriber. In a Symfony app, wire it to Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage and resolve getToken()->getUser(); handle the CLI/anonymous case explicitly (return null or a system user) or console commands will fail on prePersist.
Use a MappedSuperclass or shared base entity if you apply the same traits across many entities. Traits alone give you properties but not necessarily mapping, depending on whether the bundle adds fields in loadClassMetadata. A mapped superclass is the safer route: define the fields once with real mapping, put the trait on it, and inherit.
Soft delete is a query concern, not a persist concern. The listener only sets a deleted-at value. Every read path — repositories, query builders, counts, associations — must filter on it. The durable pattern is a base repository that applies the filter and exposes withTrashed()/onlyTrashed() style overrides, rather than sprinkling andWhere('e.deletedAt IS NULL') through controllers.
Keep the behavior surface small and wrap it. Because the package is archived, write a thin application-level abstraction: your own TimestampableListener-shaped service or a BehaviorManager you control, delegating to the bundle today and replaceable tomorrow. The traits are cheap to own; the listener registration is the part you want behind an interface.
Testing. Use a kernel test case with SQLite in-memory and call doctrine:schema:create in setUp so the trait mapping is actually exercised. For blameable, mock or stub the token storage with a real UserInterface implementation; for timestamps, freeze time with a clock abstraction so assertions are deterministic.
ContainerAware, old event class signatures, annotation reader assumptions). Pin PHP 7.x, Symfony 3.4/4.x and Doctrine ORM 2.x, or budget time to fork and modernize. The "opportunity" here is exactly that: a small, MIT-licensed codebase you can adopt and maintain yourself.loadClassMetadata or expects you to map them. If it is the latter, forgetting the @Column gives you a trait property that is silently ignored — the listener writes to a non-persisted field and nothing errors.updatedAt on preUpdate combined with other listeners can cause extra UPDATE statements or, worse, re-entrancy if a listener flushes. Never call flush() inside a behavior listener; let the unit of work finish. Verify with the SQL logger.prePersist are fine, but if you read the timestamp immediately after persist() before flush(), you may see null depending on when the listener runs. Always assert after flush().Configuration.php for required nodes and confirm the service is tagged (doctrine.event_subscriber) rather than merely defined.debug:container for the subscriber service, debug:event-dispatcher for the tag, doctrine:schema:validate for mapping drift, and a temporary dump() in the listener to prove it runs. In that order — it isolates registration from configuration from mapping.gedmo/doctrine-extensions and knplabs/doctrine-behaviors cover the same ground with active maintenance; Symfony's own symfony/uid and Carbon-style clock injection cover the timestamping piece if you only use one behavior.How can I help you explore Laravel packages today?