Doctrine Behaviors Documentation

repository·master·Indexed 21 days ago

https://github.com/knplabs/doctrinebehaviors

A collection of traits and interfaces for adding common behaviors to Doctrine entities and repositories, including Timestampable, SoftDeletable, Sluggable, Blameable, Loggable, Translatable, and Tree structures. Includes guides on implementation, Symfony configuration, PHPStan extension setup, and automated upgrading from version 1.x to 2.x using Rector.

Tokens
7.9K
Snippets
29
Records
31
Agent score
75%

What's inside Doctrine Behaviors

  1. Override translation entity class names

    master

    If you use a custom namespace or class name for your translation entities, you must override the following methods to maintain the association:

    • In the Translatable entity: Override getTranslationEntityClass().
    • In the Translation entity: Override getTranslatableEntityClass().

    Note: If you override one, you must also override the other to return the inverse class.

  2. Customize Sluggable generation logic

    master

    You can customize how slugs are generated and when they are updated by overriding specific methods in your entity:

    • Change the generation algorithm: Override generateSlugValue($values) to define how the input field values are transformed into a slug string.
    • Change the delimiter: Override getSlugDelimiter() to change the character used to separate values (defaults to -).
    • Disable regeneration on update: By default, the slug is regenerated on update/persist. To prevent this, override shouldRegenerateSlugOnUpdate() and return false.
    public function generateSlugValue($values): string
    {
        return implode('-', $values);
    }
  3. Configure a custom logger for Loggable

    master

    The Loggable behavior passes modification messages to a configured logger. By default, it uses a standard logging mechanism, but you can provide your own implementation by passing any class that implements Psr\Log\LoggerInterface.

    // Pass a class implementing Psr\Log\LoggerInterface to the behavior configuration
  4. How Blameable determines the current user

    master
    By default, Blameable uses the current user from Symfony\Security. If you need to change how the user is retrieved (for example, if you are not using Symfony Security or need custom logic), you can implement Knp\DoctrineBehaviors\Contract\Provider\UserProviderInterface and override the native service in your container.
  5. Implement the Timestampable behavior in an Entity

    master

    To enable automatic tracking of creation and update timestamps on an entity, implement the TimestampableInterface and use the TimestampableTrait. This ensures the entity has the necessary methods to store and retrieve createdAt and updatedAt values.

    <?php
    
    declare(strict_types=1);
    
    namespace App\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface;
    use Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;
    
    /**
     * @ORM\Entity
     */
    class Category implements TimestampableInterface
    {
        use TimestampableTrait;
    }
  6. Use Translatable with API Platform

    master

    To use the Translatable behavior with API Platform, you must implement the TranslatableInterface and TranslationInterface on your entities. Instead of defining translatable properties directly on the main entity, you access them through the translate() method provided by the TranslatableTrait. This allows the API to return different values based on the current request locale.

    1. Configure the Main Entity

    Implement TranslatableInterface and use TranslatableTrait. Use the translate() method within your getters to fetch data from the translation entity.

    2. Configure the Translation Entity

    Implement TranslationInterface and use TranslationTrait. This entity holds the actual translatable fields (e.g., title).

    3. Handle Locales via Request Headers

    To make the API respond to different languages, implement a Symfony EventSubscriber that listens to the KernelEvents::REQUEST event. This subscriber should parse the Accept-Language header and set the request locale accordingly.

    ### Main Entity Example
    ```php
    use Knp//... (see full example below)
  7. How to use Doctrine Behaviors in Entities and Repositories

    master

    Doctrine Behaviors are implemented by combining interfaces and traits.

    For standard entity behaviors (like Timestampable, SoftDeletable, etc.), you must:

    1. Implement the corresponding interface on your Doctrine entity.
    2. Add the corresponding trait to your Doctrine entity.

    For specific behaviors that require repository support, such as Tree, you must also add the relevant repository trait to your repository class.

    <?php
    
    declare(strict_types=1);
    
    namespace App
    epository;
    
    use Doctrine//ORM/EntityRepository;
    use Knp\DoctrineBehaviors\ORM\Tree\TreeTrait;
    
    final class CategoryRepository extends EntityRepository
    {
        use TreeTrait;
    }
  8. Implement Blameable to track entity creators and updaters

    master

    The Blameable behavior allows you to automatically track which user created or updated an entity. To implement it, your entity must implement Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface and use the Knp\DoctrineBehaviors\Model\Blameable\BlameableTrait trait.

    <?php
    
    declare(strict_types=1);
    
    namespace App\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface;
    use Knp\DoctrineBehaviors\Model\Blameable\BlameableTrait;
    
    /**
     * @ORM\Entity
     */
    class Category implements BlameableInterface
    {
        use BlameableTrait;
    }
  9. Upgrade from Doctrine Behaviors 1.x to 2.x using Rector

    master

    Upgrading from version 1 to 2 is automated using Rector. Follow these steps:

    1. Install Rector as a dev dependency:
      composer require rector/rector --dev
    2. Initialize Rector configuration:
      vendor/bin/rector init
    3. Add the DoctrineSetList::DOCTRINE_BEHAVIORS_20 set to your rector.php file.
    4. Run the process on your source directory (e.g., src):
      vendor/bin/rector process src
    use Rector\Core\Configuration\Option;
    use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
    use Rector\Doctrine\Set\DoctrineSetList;
    
    return static function (ContainerConfigurator $containerConfigurator): void {
        $containerConfigurator->import(DoctrineSetList::DOCTRINE_BEHAVIORS_20);
    };
  10. Implement Translatable behavior for entities

    master

    To implement translatable behavior, you need two entities: a main entity and a translation entity. The TranslatableEventSubscriber handles the associations automatically based on naming conventions.

    1. The Translation Entity: Contains the fields that need to be translated. It must implement TranslationInterface and use the TranslationTrait.
    2. The Main Entity: Contains fields that do not need translation. It must implement TranslatableInterface and use the TranslatableTrait.

    Example naming convention: App\Entity\Category and App\Entity\CategoryTranslation.

    <?php
    
    namespace App//Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Knp\DoctrineBehaviors\Contract\Entity\TranslationInterface;
    use Knp\DoctrineBehaviors\Model\Translatable\TranslationTrait;
    
    #[ORM\Entity]
    class CategoryTranslation implements TranslationInterface
    {
        use TranslationTrait;
    
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column(type: 'integer')]
        private $id;
    
        #[ORM\Column(type: 'string', length: 255)]
        protected $description;
    
        public function getId(): ?int { return $this->id; }
        public function getDescription(): string { return $this->description; }
        public function setDescription(string $description): void { $this->description = $description; }
    }
    
    // --- Main Entity ---
    
    namespace App\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
    use Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait;
    
    #[ORM\Entity]
    class Category implements TranslatableInterface
    {
        use TranslatableTrait;
        
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column(type: 'integer')]
        private $id;
    
        #[ORM\Column(type: 'string', length: 255)]
        protected $someFieldYouDoNotNeedToTranslate;
    
        public function getId(): ?int { return $this->id; }
    }
  11. Manage tree hierarchies and traverse nodes

    master

    Once the TreeNodeInterface is implemented, you can build hierarchies by using setChildNodeOf($parentNode) on a child entity. To retrieve the entire tree structure, use the getTree() method on the entity's repository.

    The getTree() method returns a nested structure that allows for the following operations:

    • getParentNode(): Returns the parent of the current node (or null for root nodes).
    • getChildNodes(): Returns an ArrayCollection of child nodes.
    • isLeafNode(): Returns true if the node has no children.
    • isRootNode(): Returns true if the node has no parent.
    • Nested access: You can traverse the tree using array-like syntax (e.g., $root[0][1]) to access specific nodes or null values in the hierarchy.
    /** @var Knp\DoctrineBehaviors\Contract\Entity\TreeNodeInterface $category */
    $category = new Category();
    $category->setId(1);
    
    $child = new Category();
    $child->setId(2);
    
    // Establish the hierarchy
    $child->setChildNodeOf($category);
    
    $entityManager->persist($child);
    $entityManager->persist($category);
    $entityManager->flush();
    
    // Retrieve and traverse the tree
    $categoryRepository = $entityManager->getRepository(Category::class);
    $root = $categoryRepository->getTree();
    
    $root->getParentNode(); // null
    $root->getChildNodes(); // ArrayCollection
    $root[0][1];           // node or null
    $root->isLeafNode();    // boolean
    $root->isRootNode();    // boolean