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

Doctrine Extensions Bundle Laravel Package

polishsymfonycommunity/doctrine-extensions-bundle

Symfony bundle that integrates Doctrine extensions such as Gedmo Sluggable, Timestampable, Tree, Translatable, and Loggable, with simple configuration and service registration for adding common behavioral features to Doctrine entities.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is archived. Before writing any code, confirm it is actually the right choice: the maintained path for this exact feature is stof/doctrine-extensions-bundle + gedmo/doctrine-extensions (Blameable), or a small custom Doctrine listener. Treat the steps below as "if you must use it" or "if you're inheriting a project that already does."

1. Install (likely constrained)

composer require polishsymfonycommunity/doctrine-extensions-bundle

Expect a fight. The package targets Symfony 2/3-era APIs (AppKernel, services.yml conventions, older Doctrine). On Symfony 5+ or PHP 8 you'll usually hit constraint conflicts before you hit a single line of your own code.

2. Register the bundle (legacy Symfony only)

// app/AppKernel.php
$bundles = [
    // ...
    new PSS\Bundle\DoctrineExtensionsBundle\PSSDoctrineExtensionsBundle(),
];

3. Configure it

# app/config/config.yml
pss_doctrine_extensions:
    blameable:
        enabled: true
        # point this at your user class / resolver

Check the bundle's own README for the exact keys for your version — the config schema is small and version-sensitive.

4. Annotate a first entity — pick a low-risk one (e.g. Post) and add createdBy / updatedBy relations to your User entity, then map them as blameable. First use case to validate end-to-end:

$post = new Post();
$post->setTitle('Hello');
$em->persist($post);
$em->flush();
// createdBy and updatedBy should now be the logged-in user

If that works, the listener wiring is correct. If not, the problem is almost always the user resolver, not the entity mapping.

Implementation Patterns

Blameable on the entity, resolver wired to security

The day-to-day pattern is: entity fields + a listener that pulls the current user from the security token. Most setups look like:

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\User")
 * @ORM\JoinColumn(nullable=true) // nullable for CLI/fixtures
 */
private $createdBy;

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\User")
 */
private $updatedBy;

Keep these nullable. Console commands, fixtures, and queued jobs run without a token, and a non-nullable createdBy will make every doctrine:fixtures:load explode.

Pair with timestamps

Blameable is rarely useful alone. Track createdAt/updatedAt alongside createdBy/updatedBy so audit records are actually readable. If the bundle doesn't ship timestampable, use stof/Gedmo or a prePersist/preUpdate lifecycle callback on the entity:

/** @ORM\PrePersist */
public function onPrePersist(): void { $this->createdAt = new \DateTimeImmutable(); }

/** @ORM\PreUpdate */
public function onPreUpdate(): void { $this->updatedAt = new \DateTimeImmutable(); }

Custom user resolver

Inherit/override the bundle's resolver so you can:

  • Return null gracefully when there's no token (CLI, tests).
  • Handle impersonation (switch_user) — decide whether to blame the impersonator or the impersonated user.
  • Support API auth where the user isn't in the session token.

This is the main extension point you'll actually touch.

Testing workflow

Because blameable reads from TokenStorage, tests fail unless you seed it. In setUp():

$token = new UsernamePasswordToken($user, 'main', $user->getRoles());
$this->container->get('security.token_storage')->setToken($token);

Or stub the resolver. Either way, assert on createdBy after flush — it's the cheapest regression test for "did the listener survive the last upgrade."

Migration pattern (recommended)

If you're evaluating this today, the pragmatic "implementation" is: don't adopt it. Port existing usage to Gedmo's Blameable via stof/doctrine-extensions-bundle, which is actively maintained and has a supported upgrade path. Field names and semantics are close enough that the migration is mostly mapping/annotation changes plus resolver config.

Gotchas and Tips

Archived status is the headline gotcha. No security fixes, no PHP 8/Symfony 5+ support, no Doctrine 3 compatibility. Audit the composer.lock before touching it; if it's already in a project, pin it and plan removal.

Listeners only fire on flush(). Blameable updates won't apply to DQL UPDATE, QueryBuilder->update(), or raw SQL. If you have bulk operations that must preserve audit data, set the columns explicitly in those queries.

Token storage is the #1 source of "why is createdBy null?" In order of likelihood:

  1. Running in console / no token → expected; make fields nullable.
  2. Token is null because the request is anonymous.
  3. Listener registered but the entity isn't in the mapping (wrong bundle config key, or annotation not autoloaded).
  4. Doctrine cache — clear it: bin/console doctrine:cache:clear-metadata.

Proxy/lazy-loading confusion. createdBy is usually a ManyToOne proxy. var_dump($post->getCreatedBy()) shows a proxy object, not your user until it's initialized — that's fine, but tests that compare identities should use assertEquals on the ID or assert instanceof.

Don't blame on every flush. The listener updates updatedBy on any change. If you're doing background sync jobs that touch rows, you'll overwrite updatedBy with the system user and lose the real last human editor. Either disable blameable for those code paths (detach/reset the listener) or accept the trade-off explicitly.

Config quirks. Older Symfony bundles used tree builders that silently ignore unknown keys in some versions and throw in others. If pss_doctrine_extensions config "does nothing," check debug:config pss_doctrine_extensions (Symfony 4+) to confirm what was actually loaded rather than what you typed.

Debugging event listeners. Confirm registration with:

bin/console debug:event-dispatcher doctrine.eventSubscriber

If you don't see the blameable listener there, nothing in your entity mapping matters.

Extension points are thin. The user resolver and entity mapping are about all you get. Anything more complex (multi-tenant blame, per-entity resolvers, blame on embeddables) means writing your own listener anyway — which is a strong argument for skipping the bundle and writing ~60 lines of prePersist/preUpdate logic you fully control.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager