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 Behaviors Laravel Package

miets/doctrine-behaviors

Doctrine ORM behavior extensions: reusable traits, event listeners and mappings that add timestampable, sluggable, soft-deletable, blameable and tree behavior to entities. Cuts boilerplate, keeps models clean, and drops into existing Doctrine setups with simple config.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the fork and register the bundle manually — Flex likely has no recipe for it:
composer require miets/doctrine-behaviors
// config/bundles.php
Miets\DoctrineBehaviors\DoctrineBehaviorsBundle::class => ['all' => true],
  1. Verify the namespace and config key before writing code. This is a fork of knplabs/doctrine-behaviors, so class names and the YAML root key may differ (doctrine_behaviors vs. the legacy knp_doctrine_behaviors). Inspect what you actually installed:
bin/console config:dump-reference doctrine_behaviors   # try the likely keys
ls vendor/miets/doctrine-behaviors/src
  1. Where to look first, in this order:

    • vendor/miets/doctrine-behaviors/src/Model/ — the traits themselves (TimestampableTrait, SluggableTrait, SoftDeletableTrait, TranslatableTrait, BlameableTrait, LoggableTrait, TreeNodeTrait, UuidableTrait).
    • src/Doctrine/EventListener/ (or EventSubscriber/) — how each behavior hooks prePersist / preUpdate / postLoad.
    • The bundle's README.md and DependencyInjection/Configuration.php for the exact config schema.
  2. First use case — Timestampable, since it has zero external dependencies and immediately de-risks a new install:

use Miets\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;

#[ORM\Entity]
class Article
{
    use TimestampableTrait;
}

Then bin/console make:migration and confirm the created_at / updated_at columns appear. If they do, the bundle is wired correctly and the other traits will follow the same pattern.

Implementation Patterns

Trait + migration is the whole loop. Every behavior is (a) a trait you use in the entity, and (b) a Doctrine listener that populates fields. Your workflow is always: add trait → make:migration → review the diff → done. Never hand-write the columns.

Timestampable — pure freebie, no config. Read getCreatedAt() / getUpdatedAt() in serializers and admin grids; never expose them as writable form fields.

Sluggable — the only pattern where you must implement a method:

use SluggableTrait;

public function getSluggableFields(): array
{
    return ['title'];
}

Use it for SEO routes (/blog/{slug}) and treat the slug as a stable identifier — regenerate explicitly, not implicitly, if URLs must survive title edits.

Blameable / Loggable — configure a user callable (or user provider service) in the bundle config so the listeners know who the current user is. Typical usage:

# config/packages/doctrine_behaviors.yaml
doctrine_behaviors:
    blameable:
        user_callable: 'App\Security\CurrentUserProvider::getUser'
    loggable:
        user_callable: 'App\Security\CurrentUserProvider::getUser'

Use Blameable for createdBy / updatedBy audit columns on business entities, and Loggable only on entities with real audit requirements — it writes a row per change and grows fast.

SoftDeletable — the crucial pattern is that $em->remove($entity) does not soft-delete it. You must go through the soft-deletable manager:

$softDeletableManager->remove($article);   // sets deletedAt
$em->flush();

For read paths, rely on the query filter (enabled by default) and disable it explicitly in admin/reporting screens: $filters->disable('soft_delete').

Translatable — two entities per translatable model: the main entity (use TranslatableTrait) and a separate ArticleTranslation (use TranslationTrait) holding locale-specific fields, with a unique index on (translatable_id, locale). In controllers, fetch with a join to the translation for the current locale to avoid N+1, and let the framework's locale provider drive currentLocale. Forms for translated entities need per-locale field groups — this is the single biggest day-to-day integration cost of the package.

Tree — use for categories/menus. Persist by setting the parent and flushing; call repository methods (getChildren, getPath, getRootNodes) rather than hand-joining on parent_id. Reordering is best done in one transaction, then $em->clear() if you see stale hierarchy in memory.

Uuidable — swap the auto-increment id for a UUID on entities that are exposed in URLs or synced across systems. Pick binary vs. string storage in config and stick to it; mixing the two across entities makes joins painful.

Testing pattern — freeze time for Timestampable assertions (ClockMock or a fixed DateTimeImmutable), create entities through the entity manager (not new alone) so listeners fire, and disable query filters in fixtures where you need to assert on soft-deleted rows.

Gotchas and Tips

  • Verify adoption risk before committing. This fork has ~2 stars, no dependents, and a single recent release. It exists to keep the KnpLabs behavior API working on Symfony 7+, but it is not battle-tested at scale. For a long-lived project, weigh it against knplabs/doctrine-behaviors upstream, gedmo/doctrine-extensions, or plain #[ORM\PrePersist] / #[ORM\PreUpdate] lifecycle callbacks for the two or three behaviors you actually need.
  • No Flex recipe means bundle registration, config file, and migration columns are all manual. Do the Timestampable smoke test first; a missing bundle registration fails silently (columns just never appear) rather than throwing.
  • Config key mismatch. Being a fork, the YAML root may not be knp_doctrine_behaviors. If config:dump-reference errors, read DependencyInjection/Configuration.php in vendor — guessing the key wastes an hour.
  • Property collisions. If your entity already declares $createdAt, $slug, or $id, PHP fatal-errors on duplicate trait properties. Audit the entity before adding a trait, and rename your own columns rather than the trait's.
  • Bulk DQL updates bypass the listeners. UPDATE article SET title = ... will not touch updatedAt. Either use $em->flush() on managed entities or set the timestamp explicitly in the DQL.
  • Blameable in CLI and workers. No security token means a null user and a NotNull violation. Make the callable return null safely and keep the columns nullable.
  • Soft delete is easy to leak. Forgotten remove($entity) calls, raw SQL, and cascade deletes all hard-delete. Add a code-review checklist item and, ideally, a repository base class that only exposes the manager's remove.
  • Soft-delete filter + unique constraints. A soft-deleted row still occupies the unique index. Decide early whether uniqueness is scoped to non-deleted records and model the constraint accordingly.
  • Translatable causes N+1 if you iterate getTranslations() per row in a list. Always query with the translation joined for the active locale and check EXPLAIN on listing endpoints.
  • Migration on existing tables. New trait columns will be generated NOT NULL with no default on a table with rows — the migration fails on deploy. Add nullable: true or a data backfill in the same migration.
  • Debugging: confirm subscribers are attached with bin/console debug:event-dispatcher doctrine.event_subscriber, and check whether fields are actually populated by inspecting the SQL log (doctrine.dbal.logging: true in dev). If a behavior "doesn't work," it's almost always a missing trait, an unregistered bundle, or a bypassed listener — in that order.
  • Extension points: the user callable, the locale provider, and custom translation entities are the three hooks worth using. Subclassing the traits is possible but couples you to the fork's internals — prefer composing your own base entity over overriding trait methods.
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