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

vasyaxy/doctrine-behaviors

vasyaxy/doctrine-behaviors provides reusable Doctrine behavior support for Laravel and PHP applications, making it easier to add common model behaviors and configuration without repetitive boilerplate. A lightweight option for projects already using Doctrine ORM.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you start: this is a Doctrine ORM package, not an Eloquent one. There is no ServiceProvider integration with Laravel's default ORM. To use it day-to-day you must already be running Doctrine inside Laravel (typically laravel-doctrine/orm + laravel-doctrine/extensions), which is a minority setup. If your app is Eloquent-first, this package is not applicable — look at spatie/laravel-sluggable, spatie/laravel-translatable, spatie/laravel-activitylog, and SoftDeletes instead.

Install (Doctrine projects only):

composer require vasyaxy/doctrine-behaviors

Where to look first, in this order:

  1. vendor/vasyaxy/doctrine-behaviors/src/ — the trait namespaces (Timestampable, Sluggable, SoftDeletable, Uuidable, Translatable, Tree, Blameable, Loggable).
  2. src/EventListener/ — which behaviors are driven by a listener/subscriber (Sluggable, Blameable, SoftDeletable, Translatable, Tree, Loggable) versus pure lifecycle callbacks (Timestampable, most Uuidable).
  3. tests/ in the repo — the fastest working reference for the correct trait + mapping + listener combination.
  4. composer.json — check for a extra.laravel.providers block. As of the last release there is none, so registration is manual.

First use case — a timestamped, sluggable Post:

namespace App\Entities;

use Doctrine\ORM\Mapping as ORM;
use Vasyaxy\DoctrineBehaviors\Timestampable\TimestampableTrait;
use Vasyaxy\DoctrineBehaviors\Sluggable\SluggableTrait;

#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]           // REQUIRED for timestampable to fire
class Post
{
    use TimestampableTrait;
    use SluggableTrait;

    #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 255)]
    private string $title;

    // SluggableTrait expects this shape:
    public function getSluggableFields(): array
    {
        return ['title'];
    }
}

Then register the slug listener in a service provider's boot() (see next section). Verify with $em->getConnection()->getConfiguration()->setSQLLogger(...) or Laravel Debugbar that slug is populated on persist().

Implementation Patterns

Central listener registration. Because nothing auto-wires, add a small provider that attaches every behavior listener you use to the EntityManager once, on boot:

public function boot(EntityManagerInterface $em)
{
    $evm = $em->getEventManager();
    $evm->addEventSubscriber(new SluggableListener());
    $evm->addEventSubscriber(new SoftDeleteableListener());
    $evm->addEventSubscriber(new TreeListener());
    // Blameable/Translatable need the listener instance shared globally
    // so you can call setUser() / setCurrentLocale() later.
}

Bind the BlameableListener and TranslatableListener as singletons in the container — you will need to mutate them per request.

Blameable with Laravel auth. Doctrine has no auth() helper. Push the user into the listener from a middleware or a RequestHandled hook:

public function handle($request, Closure $next)
{
    if ($user = $request->user()) {
        app(BlameableListener::class)->setUser($user->getAuthIdentifier());
    }
    return $next($request);
}

SoftDeletable filter management. The filter must be enabled on the EntityManager before querying; it is not enabled by default:

$em->getFilters()->enable('soft-deleteable')
   ->setParameter('soft_deleteable.deleted_at', 'deletedAt');

// admin view
$em->getFilters()->disable('soft-deleteable');

Translatable with Laravel's locale. Keep a single TranslatableListener and set the locale wherever App::setLocale() runs:

app(TranslatableListener::class)->setCurrentLocale(app()->getLocale());
app(TranslatableListener::class)->setDefaultLocale(config('app.fallback_locale'));

Then use $post->translate('fr')->getTitle() or $post->getTranslations() for edit forms. Translation entities must extend AbstractTranslation and carry their own ORM\Entity mapping.

Tree. Use the provided repository ($em->getRepository(Category::class)) rather than hand-writing parent assignments — node moves must go through persistAsFirstChildOf() / persistAsNextSiblingOf() or the nested-set left/right values desynchronise.

Loggable. You must create your own LogEntry entity extending AbstractLogEntry and annotate entities with #[ORM\Entity] #[ORM\Table] plus the Loggable trait — the listener will not invent a table for you.

Testing pattern. Build a fixture entity in tests and assert on the trait side effects (e.g. $post->getCreatedAt() not null after flush()), because the traits mostly mutate state rather than exposing rich APIs.

Gotchas and Tips

  • Health signal is poor. 1 star, 0 dependents, last release 2023-12-11. Treat this as a fork of the well-known KnpLabs/DoctrineBehaviors (and gedmo/doctrine-extensions) rather than an actively maintained library. Diff the source against those upstreams before relying on it; if they are equivalent, depend on upstream directly.
  • #[ORM\HasLifecycleCallbacks] is silently required. Without it, TimestampableTrait hooks never fire and your created_at stays NULL — no error, just empty columns. Same class of bug for any trait using @PrePersist/@PreUpdate.
  • Soft-delete filters don't apply everywhere. Native SQL, getConnection()->executeQuery(), and some bulk DQL bypass the filter. Deleted rows will resurface in raw reports. Also remember to disable the filter when you want to restore.
  • Blameable breaks in console/jobs. No authenticated user means null (or a thrown exception depending on listener version). Explicitly setUser() in queued jobs and Artisan commands, or guard with if (app()->runningInConsole()).
  • Sluggable collisions. Confirm whether your version appends suffixes on duplicates; if not, add a unique index and catch UniqueConstraintViolationException, then retry. Also decide whether slugs should change on title edits — changing them breaks SEO and stable URLs, so freeze the slug after first generation.
  • Uuidable changes ID semantics. GeneratedValue is replaced by client-side UUID generation. Ensure your column is guid/CHAR(36) (or binary if configured), and stop relying on ID ordering — use createdAt for chronology.
  • Translatable queries need care. Lazy-loading a translation inside a loop is an N+1; use ->leftJoin('p.translations', 't') or fetch-join hints. Missing translations return empty proxies, so check getTitle() ?? null rather than assuming a value.
  • Tree corruption is expensive. Never set $node->setParent($other) and flush by hand; use the repository's move methods. After any suspected corruption, run a tree-verification/rebuild routine.
  • Debugging: laravel-doctrine logs SQL through Monolog, not Laravel's query log. Enable doctrine.dbal.logging and doctrine.dbal.profiling in config, or attach a SQLLogger, to see what the listeners actually emit on flush.
  • Extension points are trait methods. Custom slug formatting, timestamp columns, and user-id resolution are all overridable protected methods on the traits (generateSlug(), getTimestampableFields(), getUser()). Prefer overriding in the entity over forking the package — much easier to migrate back to upstream later.
  • Metadata caching in production. doctrine.orm.auto_generate_proxy_classes=0 plus the metadata/query cache means listener or trait changes require php artisan doctrine:clear-metadata-cache.
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