Installation:
composer require knplabs/doctrine-behaviors
Ensure your Laravel project uses Doctrine ORM (e.g., via doctrine/orm or laravel-doctrine/orm).
First Use Case:
Add a behavior to an entity. For example, to make an entity Blameable (track created/updated by users):
// src/Entity/Post.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model\Blameable\BlameableInterface;
use Knp\DoctrineBehaviors\Model\Blameable\BlameableTrait;
#[ORM\Entity]
class Post implements BlameableInterface
{
use BlameableTrait;
// ... other fields/methods
}
Configuration:
Blameable, set the blameable.user and blameable.ip services in your Doctrine config (e.g., config/packages/doctrine.yaml):
knp_doctrine_behaviors:
blameable:
user: 'App\Service\CurrentUserService'
ip: 'App\Service\CurrentIpService'
Migrations:
Run migrations to add required fields (e.g., createdBy, updatedBy for Blameable):
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Entity Behaviors:
$post = new Post();
$post->setTitle('Hello World');
$post->setCreatedBy($currentUser); // Automatically set by trait
createdAt/updatedAt.
$post->setTitle('Updated');
// $post->updatedAt is auto-updated
$post->delete(); // Sets `deletedAt` instead of deleting
$deletedPosts = $entityManager->getRepository(Post::class)->findDeleted();
Repository Behaviors:
// src/Repository/CategoryRepository.php
use Knp\DoctrineBehaviors\ORM\Tree\TreeTrait;
class CategoryRepository extends EntityRepository
{
use TreeTrait;
public function getTree(): TreeInterface
{
return $this->tree;
}
}
Usage:
$root = $categoryRepo->getRoot();
$children = $categoryRepo->getChildren($parentCategory);
Translatable:
Translation entity.
// src/Entity/Product.php
use Knp\DoctrineBehaviors\Model\Translatable\TranslatableInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait;
class Product implements TranslatableInterface
{
use TranslatableTrait;
// ...
}
Usage:
$product->setTranslation('name', 'en', 'Laptop');
$translations = $product->getTranslations();
Sluggable:
$post->setTitle('My Awesome Post');
$post->generateSlug(); // Auto-generates `slug` field
Laravel-Specific:
Auth facade to resolve blameable.user:
knp_doctrine_behaviors:
blameable:
user: 'App\Services\AuthService' # Custom service wrapping auth()->user()
Uuidable, combine with Laravel’s ramsey/uuid:
use Ramsey\Uuid\Uuid;
$post->setId(Uuid::uuid4());
Custom Logic:
trait CustomBlameableTrait extends BlameableTrait
{
public function setCreatedBy(User $user): self
{
if (!$user->isAdmin()) {
throw new \RuntimeException('Only admins can create posts.');
}
return parent::setCreatedBy($user);
}
}
Querying:
// SoftDeletable: Find non-deleted items
$activePosts = $entityManager->getRepository(Post::class)->findNonDeleted();
// Tree: Get siblings
$siblings = $categoryRepo->getSiblings($category);
Missing Configuration:
blameable.user) will cause NullReferenceException.config/packages/knp_doctrine_behaviors.yaml.Migration Order:
Blameable or Timestampable add fields. Run migrations after adding traits to entities.doctrine:migrations:diff and migrate in the correct order.Circular References:
Translatable requires a separate Translation entity. Forgetting to define it causes errors.Tree Behavior Quirks:
TreeTrait requires lft/rgt columns for nested sets. If missing, queries fail.ALTER TABLE categories ADD lft INT, ADD rgt INT;
Overwriting Defaults:
Timestampable) auto-set fields. Overwriting them manually can cause inconsistencies.setCreatedAt()/setUpdatedAt() sparingly; let the trait handle it.PHPStan Conflicts:
composer require --dev knplabs/doctrine-behaviors-phpstan
phpstan.neon:
includes:
- vendor/knplabs/doctrine-behaviors-phpstan/extension.neon
Enable SQL Logging: Debug tree queries or soft deletes with:
$entityManager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
Check Event Listeners:
Behaviors use Doctrine events (e.g., prePersist). Disable them temporarily to isolate issues:
$eventManager->removeEventListeners([YourEntity::class]);
Validate Entities: Use Doctrine’s validator to catch pre-save issues:
$errors = $validator->validate($entity);
if (count($errors) > 0) {
throw new \RuntimeException((string) $errors);
}
Custom Events:
Extend behaviors by listening to their events. Example for Blameable:
$eventManager->addEventListener(
[YourEntity::class],
function (LifecycleEventArgs $args) {
$entity = $args->getObject();
if ($entity instanceof BlameableInterface) {
// Custom logic before/after save
}
},
['prePersist', 'preUpdate']
);
Override Trait Methods: Customize behavior logic:
trait CustomSoftDeletableTrait extends SoftDeletableTrait
{
public function delete(): void
{
if (!$this->isDeletable()) {
throw new \RuntimeException('Cannot delete this entity.');
}
parent::delete();
}
}
Add New Behaviors:
Create your own traits by following the existing patterns (e.g., BlameableTrait). Example skeleton:
trait MyCustomBehaviorTrait
{
#[ORM\Column]
private ?string $customField = null;
public function setCustomField(string $value): self
{
$this->customField = $value;
return $this;
}
// Add lifecycle callbacks
#[ORM\PrePersist]
public function prePersist(): void
{
$this->customField = strtoupper($this->customField);
How can I help you explore Laravel packages today?