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

Extensions Laravel Package

laravel-doctrine/extensions

Adds common Doctrine behavioral extensions to Laravel Doctrine, including soft deletes, timestamps, blameable, sortable, sluggable, tree, loggable and more, with config and service provider wiring so these behaviors work smoothly in your Laravel models.

View on GitHub
Deep Wiki
Context7

Getting Started

laravel-doctrine/extensions is the glue package that wires the Gedmo doctrine-extensions behaviour library (SoftDeleteable, Timestampable, Blameable, Sluggable, Loggable, Tree, Sortable, Translatable, Uploadable, IpTraceable) into a laravel-doctrine/orm entity manager. You don't call it directly — you register extension classes in config, annotate entities, and the package attaches the right event subscribers.

2.0.3 is a compatibility-only release (Laravel v13 support). There are no API changes, no new behaviours, no deprecations. Treat the upgrade as routine, but confirm your laravel-doctrine/orm version resolves alongside it.

Minimal setup:

composer require laravel-doctrine/extensions
  1. Look here first: the extensions key in config/doctrine.php. That array is the single source of truth for which behaviours are active. The package's README maps each config class to the Gedmo behaviour it enables — skim that table before writing any code.
  2. Second look: php artisan doctrine:info and php artisan doctrine:mapping:info to confirm your manager is booting and your entities are discovered.
  3. First use case: soft deletes plus timestamps on one entity.
// config/doctrine.php
'extensions' => [
    LaravelDoctrine\Extensions\SoftDeleteable\SoftDeleteableExtension::class,
    LaravelDoctrine\Extensions\Timestampable\TimestampableExtension::class,
],
#[ORM\Entity]
#[Gedmo\SoftDeleteable(fieldName: 'deletedAt', timeAware: false)]
class Post
{
    #[ORM\Column(type: 'datetime_immutable', nullable: true)]
    private ?\DateTimeImmutable $deletedAt = null;

    #[Gedmo\Timestampable(on: 'create')]
    #[ORM\Column(type: 'datetime_immutable')]
    private \DateTimeImmutable $createdAt;
}

Then push the schema (php artisan doctrine:schema:update --force in dev, a real migration anywhere else) and run php artisan doctrine:clear-metadata-cache — the listener set is only rebuilt when metadata is re-read.

Implementation Patterns

Config-gated behaviours, entity-level opt-in. The extension classes are global listeners; the annotations decide which entities they touch. Keep the extensions array explicit rather than copying the whole README list — every enabled behaviour adds subscribers to the event manager and filters to the EM, which is measurable overhead and a debugging hazard when something mutates an entity unexpectedly.

Keep the config in version control, not in a published vendor file. Publish config/doctrine.php once and treat extensions as code. A common pattern:

'extensions' => array_filter([
    SoftDeleteableExtension::class,
    TimestampableExtension::class,
    BlameableExtension::class,
    app()->environment('production') ? LoggableExtension::class : null,
]),

Blameable needs an explicit user-resolution seam. Gedmo's listener has no idea about Laravel's auth guards. Set the user once per request in a middleware and once per job in a job middleware:

public function handle($request, Closure $next)
{
    $listener = $this->em->getEventManager()->getListeners('prePersist');
    // or resolve the Gedmo listener from the container and ->setUser(Auth::user())
    return $next($request);
}

Wrap Blameable in a small service class so console commands, queued jobs, and impersonation all go through the same code path.

Soft delete + "admin sees everything" pattern. Don't sprinkle getFilters()->disable() through controllers. Put it in a dedicated repository method or a query scope so the filter is re-enabled in a finally block — a leaked disabled filter silently resurrects deleted records for the rest of the request.

public function withTrashed(): static
{
    $this->em->getFilters()->disable('soft-deleteable');
    return $this;
}

Pair Timestampable with immutable datetime columns. Gedmo works with both DateTime and DateTimeImmutable, but only if the entity property type matches the column type. Pick one convention (datetime_immutable + \DateTimeImmutable) and enforce it across the codebase.

Sluggable for URLs, Loggable for audit. Sluggable is a write-time concern; Loggable produces an ext_log_entries table that grows without bound. If you enable Loggable, decide up front on a retention/pruning job, and exclude high-churn entities with #[Gedmo\Loggable(logEntryClass: ...)]-adjacent config or by simply not annotating them.

Upgrade workflow for a release like 2.0.3. Bump, then:

composer update laravel-doctrine/extensions --with-dependencies
php artisan doctrine:clear-metadata-cache
php artisan doctrine:schema:validate

Smoke-test the four things that actually break on framework bumps: soft-delete filter still hides rows, timestamps still populate on create and update, Blameable resolves a user in HTTP context, and Translatable returns the fallback locale.

Testing. Use the Laravel Doctrine testing traits rather than Eloquent's RefreshDatabase. In tests that assert a row is gone, remember the soft-delete filter is on by default — assertNull($em->find(Post::class, $id)) passes for a soft-deleted row. Assert against raw DBAL or explicitly disable the filter.

Gotchas and Tips

  • Native SQL and DBAL queries bypass every filter. $em->getConnection()->executeQuery(...) will return soft-deleted rows. So will ->createNativeQuery(). There is no fix; it's how SQL filters work.
  • Blameable in console/queue context. No authenticated user means null written to the createdBy column — or a TypeError if the property isn't nullable. Either make the columns nullable or set the user before dispatching work.
  • config:cache and metadata cache both need clearing after adding or removing an extension class. A stale metadata cache is the #1 cause of "my annotation is ignored." Symptom: the column exists, the behaviour just doesn't fire.
  • Sluggable uniqueness is racy. Gedmo's check-then-insert can lose under concurrency. Catch Doctrine\DBAL\Exception\UniqueConstraintViolationException on persist/flush and retry with a suffix, or add a DB unique index and accept the retry loop.
  • Uploadable does not speak Laravel's filesystem. It writes via local paths and its own adapters; Storage::disk('s3') is not pluggable without a custom adapter. For most Laravel apps, handle uploads yourself and let the entity store the path.
  • Translatable adds a table you must migrate (ext_translations by default) and its listener needs locale + fallback configured. If translated fields come back empty in a non-default locale, check setTranslationFallback and whether the translation row was persisted at all.
  • Tree (nested set) writes are expensive. Node moves touch many rows. Never trigger tree rebuilds inside a request cycle; do it in a queued job.
  • Ordering can matter when SoftDeleteable and a custom filter both attach to the same entity — verify with $em->getFilters()->isEnabled('soft-deleteable') after boot if behaviour looks inconsistent.
  • Version alignment. Extensions depends on laravel-doctrine/orm and gedmo/doctrine-extensions. On a framework bump, resolve them together: composer why-not laravel-doctrine/orm and composer show gedmo/doctrine-extensions will tell you quickly whether you're blocked by a constraint rather than a real incompatibility.
  • Laravel 13 constraint check. If 2.0.3 tightened illuminate/* to ^13.0 only, staying on Laravel 11/12 means pinning 2.0.2. Confirm the constraint before bumping in a monorepo or a package that others depend on.
  • Extension points: the individual extension classes are thin wrappers around Gedmo listeners, so anything the underlying library exposes (custom SlugRepository, custom LogEntry class, custom TranslatableListener config) is reachable — but usually via the service container binding the listener, not via this package's config. Read the wrapper's __construct before assuming there's a config key.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package