# DoctrineExtensions: behavioral extensions for Doctrine ORM and MongoDB ODM

> The gedmo/doctrine-extensions package attaches behaviors like Translatable, Sluggable and Tree to Doctrine's event system. It is a strong fit for Symfony projects that already map entities with attributes, and a poor fit if you want behavior without an event listener in the flush path.

**doctrine-extensions/DoctrineExtensions** — Doctrine2 behavioral extensions, Translatable, Sluggable, Tree-NestedSet, Timestampable, Loggable, Sortable.

- Repository: https://github.com/doctrine-extensions/DoctrineExtensions
- Stars: 4,138 · Forks: 1,248
- Language: PHP
- License: MIT
- Published: 2026-08-08 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/doctrine-extensions-doctrineextensions

## What DoctrineExtensions adds to a plain Doctrine entity

Doctrine gives you mapping and a unit of work. It does not give you a slug column that fills itself, a created_at that is set on insert and left alone on update, or a nested set that keeps left and right bounds consistent when you move a node. DoctrineExtensions supplies those behaviors as separate extensions that hook into Doctrine's event system and act on records as they are flushed.

The package covers two persistence layers. Against Doctrine ORM and MongoDB ODM you get Blameable, Loggable, Sluggable, Timestampable, Translatable and Tree. ORM-only extensions are IpTraceable, SoftDeleteable, Sortable and Uploadable. MongoDB ODM-only extensions are References and ReferenceIntegrity. Tree supports closure, nested set and materialized path on the ORM side, while the ODM only supports materialized path.

The audience is narrow and specific: PHP teams already running Doctrine, usually inside Symfony, Laravel or Laminas, who would otherwise write the same listener code by hand in every project. The README links setup guides for all three frameworks, which tells you where the maintainers expect this to be used.

## How the behaviors attach to Doctrine's event system

The README describes the design in one line: behaviors are attached to Doctrine's event system and handle records being flushed. That is the whole architecture. There is no proxy layer and no separate persistence mechanism. Each extension registers listeners for the Doctrine events it cares about, reads the metadata it needs, and mutates the entity or document before the unit of work writes it.

That design explains the metadata requirement. Every extension needs to know which property it owns, so it reads mapping metadata through a mapping driver. The package supports Attribute, XML and Annotation mapping, with annotations marked deprecated, and the README notes that additional mapping drivers can be implemented through the Mapping extension. Because the behavior is metadata-driven, a Sluggable field is declared on the property rather than configured in a service, and the listener finds it at runtime.

The XML path has its own namespace, http://gediminasm.org/schemas/orm/doctrine-extensions-mapping, declared as a prefix on the root doctrine-mapping node. The README also notes that the XSD schemas are versioned with suffixes such as -2-2 and -2-1, so an XML mapping file pins a schema version rather than tracking the latest one. That is a deliberate stability choice, and it also means an XML file written years ago may validate against an old schema while the runtime code has moved on.

## Installing DoctrineExtensions with Composer and running the example

The README gives a single installation command. It pulls the package from Packagist under the gedmo/doctrine-extensions name.

```bash
composer require gedmo/doctrine-extensions
```

After that, framework-specific wiring is documented separately for Symfony, Laravel and Laminas. The README does not inline those steps, so the framework guide is the place to look for how the listeners get registered in a service container.

If you are setting up an Entity Manager without a framework, the README points at example/em.php and says to follow it to avoid issues like the one tracked as #1310. The repository ships a runnable example under example/, with a console entry point at example/bin/console. The documented sequence is to install dev dependencies, edit example/em.php to configure your database at the top of the file, then create the schema and run the demo command.

```bash
composer install
php example/bin/console orm:schema-tool:create
php example/bin/console app:print-category-translation-tree
```

The last command prints a category translation tree, which exercises the Translatable and Tree extensions together. If you want to run the package's own test suite instead, the README describes a Docker path: start containers with docker compose up -d, enter the PHP container with docker compose exec php bash, run composer install, then vendor/bin/phpunit. The compose.yaml in the repository defines a php service built from .docker/php/Dockerfile with PHP_VERSION defaulting to 8.5-cli, plus mysql:8.0 and mongo services, so the test environment expects both database engines.

## Version constraints and the Loggable gap on DBAL 4.0

The compatibility table in the README is the first thing to check before adopting. DBAL is supported at ^3.2 for all extensions, or ^4.0 for all extensions except Loggable. ORM is supported at ^2.14 or ^3.0, and MongoDB ODM at ^2.3.

That single exception matters more than it looks. If your project has already moved to DBAL 4.0 and you need change tracking with version management, the README gives you no supported path to Loggable. You either hold DBAL at 3.x, or you implement the history yourself, or you pick a different tool. This is the kind of constraint that surfaces late, after a dependency bump, so it is worth checking against composer.json before writing any Loggable mapping.

The second constraint is mapping style. Attribute and XML are the supported drivers; annotations are deprecated. A codebase still using annotation mapping will work today but is on the path the maintainers have already flagged. Migrating mapping is mechanical but touches every entity that uses an extension.

The third is the ORM and ODM split. SoftDeleteable, Sortable, Uploadable and IpTraceable are ORM only. References and ReferenceIntegrity are ODM only. Tree is available on both, but only with materialized path on the ODM side. A project that plans to run the same behavior against both persistence layers will find that only part of the extension set travels.

## Where DoctrineExtensions is the wrong tool

The event-system design is the source of its main limitation. Because extensions act on records during flush, the behavior is tied to the unit of work. Bulk operations that bypass the ORM, such as raw SQL updates or DQL mass updates, do not pass through the listeners, so a slug column or a timestamp column will not be maintained by them. The README does not document a mechanism for covering those paths.

Translatable is the other place to think carefully. The README calls it a handy solution for translating records into different languages, and the example prints a category translation tree, which shows the intended shape: translations stored alongside the entity. That works well when translations are edited as part of the entity's lifecycle. It is a weaker fit when translations come from an external translation service, when they need workflow states such as draft and published, or when the number of locales is large enough that loading an entity pulls in more translation rows than you want.

The package also assumes Doctrine. If you are evaluating it for a project that uses a different data mapper, or that talks to the database through a query builder without an entity manager, none of the extensions apply. There is no standalone mode described in the README.

Finally, the README does not document rollback behavior for any extension. If a flush fails partway through a tree operation, the documented material is silent on what state the left and right bounds are left in. That is a real gap for anyone planning nested set writes under load.

## How DoctrineExtensions differs from KnpLabs/doctrine-behaviors and Beberlei/doctrineextensions

Two names come up constantly when people search for this package, and they are different things.

KnpLabs/doctrine-behaviors is the closest conceptual alternative. It also provides behaviors for Doctrine entities, so the overlap is real: slug, timestamp and tree-style functionality exist in both. The difference in approach is where the behavior lives. DoctrineExtensions drives everything from metadata and Doctrine's event system, which is why it needs Attribute or XML mapping and why the mapping driver is a supported extension point. KnpLabs' package takes a different route, using traits and interfaces on the entity classes themselves, so the behavior is visible in the entity's PHP code rather than declared in mapping. If you prefer reading an entity class and seeing exactly what it does, the trait approach is easier to audit. If you prefer keeping entities plain and declaring behavior in mapping, DoctrineExtensions fits better. The two are not designed to be mixed on the same entity.

Beberlei/doctrineextensions is a different category entirely. It provides custom DQL functions for MySQL, such as RAND and date functions, so that you can call them from DQL queries. It does not add entity behaviors. The similar name is a persistent source of confusion, and searches for doctrineextensions query mysql rand are almost certainly looking for that package, not this one.

Symfony/orm is not an alternative at all. It is the ORM itself, and DoctrineExtensions depends on it.

## Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-08-01. That date lines up with the v3.22.1 release, published the same day. The prior releases were v3.22.0 on 2025-12-13 and v3.21.0 on 2025-09-22, so the release cadence over the past year has been roughly one minor release per quarter with patch releases as needed. The README still carries a note about the 3.0 release and links to a dedicated upgrade document at doc/upgrading/upgrade-v2.4-to-v3.0.md, which describes the minimum version bumps for PHP, Doctrine and other dependencies, support for the latest Doctrine MongoDB and Common packages, and the test suite and coding standard work that came with that release.

The upgrade cost is mostly front-loaded. If you are already on 3.x, minor releases within the line are the normal Composer update. If you are on 2.4.x, the upgrade document is the required reading, because 3.0 raised minimum versions across PHP, Doctrine and related dependencies at once. The version compatibility table is the practical checklist: DBAL ^3.2 or ^4.0, ORM ^2.14 or ^3.0, MongoDB ODM ^2.3, with Loggable excluded from the DBAL 4.0 row.

The licence is MIT, stated in the LICENSE file at the repository root. MIT is permissive: it allows use, modification and redistribution, including in closed-source products, provided the copyright notice and permission notice are retained. That is a description of the licence text, not legal advice, and if the extension is embedded in a distributed product, the notice retention requirement is the part to check with whoever handles your licensing.

The repository ships tooling that signals how changes are expected to land: a Makefile with lint-composer, lint-xml, lint-yaml and lint-doctrine-xml-schema targets, a phpstan configuration with a baseline file, rector.php, and a .php-cs-fixer.dist.php. If you plan to send patches, those are the checks your change will be measured against.

## Conclusion

Adopt DoctrineExtensions when your entities are mapped with attributes or XML and you want slug, timestamp, tree and translation behavior driven by Doctrine's event system rather than by hand-written setters. Do not adopt it if you are on DBAL 4.0 and need Loggable, if you rely on the deprecated annotation mapping, or if you want behaviors that run without a listener in the flush path. Before committing, verify the DBAL and ORM version constraints against your composer.json, confirm which of the ORM-only extensions you actually need, and read the v2.4 to v3.0 upgrade document if you are coming from the 2.x line.

## FAQ

### Is Doctrine an ORM?

Doctrine ORM is the object-relational mapper that DoctrineExtensions extends. The README describes this package as extensions for Doctrine ORM and MongoDB ODM that attach behaviors to Doctrine's event system and handle records being flushed.

### How do I install DoctrineExtensions?

The README gives one command, composer require gedmo/doctrine-extensions, and then links separate setup guides for Symfony, Laravel and Laminas. If you set up an Entity Manager without a framework, the README points at example/em.php.

### Does DoctrineExtensions work with DBAL 4.0?

The version compatibility table lists DBAL ^3.2 for all extensions and ^4.0 for all extensions except Loggable. So on DBAL 4.0, Loggable is not covered by the documented support matrix.

### Which mapping styles does DoctrineExtensions support?

The README states that all extensions support Attribute, XML and Annotation mapping, and marks annotations as deprecated. XML mapping needs the gedmo namespace http://gediminasm.org/schemas/orm/doctrine-extensions-mapping declared on the root doctrine-mapping node.

## Sources

- [Official README](https://github.com/doctrine-extensions/DoctrineExtensions#readme)
- [Project repository](https://github.com/doctrine-extensions/DoctrineExtensions)
- [Release notes](https://github.com/doctrine-extensions/DoctrineExtensions/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/doctrine-extensions-doctrineextensions
