Search by

phil / doctrine-behaviours

phil

Doctrine ORM behaviours written as interfaces, traits and listeners: timestamps, blame, soft delete, trees kept as materialized paths, deactivation, owner, publication by workflow, positions within groups, scoped slugs, translations along fallback chains, versions and dated identifiers

0.1.0-alpha 2026-11-12 14:43 UTC

This package is auto-updated.

Last update: 2026-11-12 23:51:28 UTC


README

Twelve Doctrine behaviours, from timestamps and trees to positions, slugs, translations and versions, each one opt-in.

phil/doctrine-behaviours gives Doctrine ORM entities twelve behaviours. Timestamps stamp when an entity is created and updated, in UTC. Blame records who created and last changed it, as a user identifier. Soft delete hides a removed entity behind a filter instead of deleting it, and removing it a second time deletes it. Trees keep each node's materialized path and depth, and the Tree reader gives roots, children, descendants, ancestors and the hierarchy. Deactivation switches a record off and back on behind a filter of its own. An owner is a stable identifier, never an e-mail address. Publication is a state of symfony/workflow with its dates. A position orders records within a group, and a record moves before or after another. A slug made by phil/slug is kept unique in its table, or within a scope such as a tenant, a locale or a parent. Translations keep the text of a record in a row per locale, read along a declared fallback chain and never copied from one locale to another. Versions freeze what a record held, fork the next edit, and go back under a right of their own. A dated identifier is a UUID v7.

It is for any application on Doctrine ORM that needs these columns kept without writing listeners of its own. Each behaviour stands alone, so an application registers only the listeners its entities use. Trees are materialized paths: moving a branch is one update instead of renumbering the tree. In a Symfony application, phil/doctrine-behaviours-bundle registers the listeners, the filter and the blame of the authenticated user. A record that should leave its table, ready to be brought back, belongs in phil/trash-bundle instead.

It is a plain PHP library on Doctrine ORM, DBAL, psr/clock and phil/locale-dimensions; phil/slug, symfony/uid and symfony/workflow are needed only by the behaviours that name them. Each behaviour is an interface, a trait that maps its columns, and a listener that keeps them. Who is acting comes from a BlameResolver the application provides, such as FixedBlame for a command that acts for a known account, or a test.

Status

Part State
Contract\Timestampable, TimestampableEntity, TimestampListener Implemented and tested
Contract\Blameable, BlameableEntity, BlameListener, Contract\BlameResolver, FixedBlame Implemented and tested
BlameEraser: a user's name found and replaced in every blamed table Implemented and tested
Contract\SoftDeletable, SoftDeletableEntity, SoftDeleteListener, SoftDeletedFilter Implemented and tested
Contract\TreeNode, TreeNodeEntity, TreeListener, Tree, Branch Implemented and tested
Deactivatable, DeactivatableEntity, DeactivationListener, DeactivatedFilter Implemented and tested
OwnerId, Ownable, OwnableEntity, OwnerListener, OwnerResolver, FixedOwner, OwnerEraser Implemented and tested
Publishable, PublishableEntity, PublicationWorkflow, PublicationListener Implemented and tested
Contract\Positionable, PositionableEntity, PositionListener, Positions, groups Implemented and tested
Contract\Sluggable, SluggableEntity, ScopedSluggableEntity, SlugListener, scopes Implemented and tested
Contract\Translatable, Contract\Translation, TranslationEntity, TranslationListener, Translations Implemented and tested
Versionable, VersionableEntity, VersionListener, Versions, RecordVersion, VersionRestoreGuard Implemented and tested
DatedIdentifiable, DatedIdentifierEntity, DatedIdentifierListener Implemented and tested

Documentation

Document What it covers
Timestamps and blame Who and when, what an entity was given and keeps, privacy and erasure.
Soft delete Removal as a date, the filter, restoring, deleting for good.
Trees Paths, moves of a branch, reading a tree.
Deactivation Switching a record off and on, the filter, how it differs from soft delete.
Owner A stable identifier, the resolver, handing over, erasure.
Publication The state machine, its dates, publishing at a set time.
Position Ordering within a group, moving before and after, the index.
Slug When a slug is made, how it stays unique, in the table or within a scope.
Translation A row per locale, read along a fallback chain, a list in one query, writing without copying, slugs and versions per locale.
Versions Freezing, forking, going back under a right of its own.
Dated identifier A UUID v7 as the primary key.
Integration Registering the listeners and the filter without Symfony.
Extension points The ports, arguments and data an application chooses or replaces.
Testing How the library is tested.

Quick start

#[ORM\Entity]
class Category implements Timestampable, Blameable, SoftDeletable, TreeNode
{
    use TimestampableEntity;
    use BlameableEntity;
    use SoftDeletableEntity;
    use TreeNodeEntity;

    #[ORM\ManyToOne(targetEntity: self::class)]
    private ?self $parent = null;

    public function treeId(): string { return (string) $this->id; }
    public function treeParent(): ?self { return $this->parent; }
}
$tree->hierarchy(Category::class);    // list<Branch>, each node with its children
$tree->ancestors($category);          // the root down to the parent

In a Symfony application, phil/doctrine-behaviours-bundle registers the listeners and the filter.

Requirements

PHP 8.5 or later, Doctrine ORM 3.7 or later, DBAL 4.5, doctrine/collections, psr/clock and phil/locale-dimensions; phil/slug for slugs, symfony/uid for dated identifiers, and symfony/workflow for publication.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.