Doctrine Behavioral Extensions

repository·main·Indexed 26 days ago

https://github.com/doctrine-extensions/doctrineextensions

A suite of tools for Doctrine ORM and MongoDB ODM that automates common tasks by hooking into the Doctrine event system. Available extensions include Blameable, Loggable, Sluggable, Timestampable, Translatable, Tree, IpTraceable, SoftDeleteable, Sortable, Uploadable, References, and ReferenceIntegrity. Supports Attribute, XML, and Annotation mapping.

Tokens
57.6K
Snippets
139
Records
210
Agent score
84%

What's inside Doctrine Behavioral Extensions

  1. Use ReferenceIntegrity behavior for Doctrine ODM

    main

    The ReferenceIntegrity behavior automates reference integrity for referenced documents in Doctrine MongoDB ODM. It ensures that when a document is deleted, its references in other documents are handled according to a specified strategy.

    Supported strategies:

    • nullify: Automatically removes the reference (sets it to null) in the referenced document.
    • pull: When used on a ReferenceMany association, the removed document is automatically pulled from the collection array.
    • restrict: Throws a ReferenceIntegrityStrictException if the document is still being referenced.

    Requirements:

    • This is an ODM only extension.
    • You must have the mappedBy option set on the association to allow the extension to access and update the referenced documents.
  2. Understand the Mapping extension for Doctrine

    main
    The Mapping extension allows you to map additional metadata for event listeners in Doctrine. It supports Attribute, Xml, and Annotation drivers, automatically selecting the appropriate one based on your domain objects' mapping driver. It also provides an abstraction layer for EventArgs to enable single listeners to work across different object managers, such as ORM and ODM.
  3. Available Doctrine Extensions

    main

    The package provides several behaviors that can be attached to the Doctrine event system.

    ORM & MongoDB ODM Extensions

    • Blameable: Updates string or reference fields (e.g., a user object) on create, update, or property change.
    • Loggable: Tracks changes and history of objects; supports version management.
    • Sluggable: Converts specified fields into unique URL-friendly slugs.
    • Timestampable: Updates date fields on create, update, or property change.
    • Translatable: Provides a solution for translating records into different languages.
    • Tree: Automates tree handling (supports closure, nested set, or materialized path). Note: MongoDB ODM only supports materialized path.

    ORM Only Extensions

    • IpTraceable: Sets the IP address instead of a timestamp.
    • SoftDeleteable: Allows for implicit record removal.
    • Sortable: Makes any entity or document sortable.
    • Uploadable: Handles file uploads in entity fields.

    MongoDB ODM Only Extensions

    • References: Supports linking Entities in Documents and vice versa.
    • ReferenceIntegrity: Constrains ODM MongoDB Document references.

    All extensions support Attribute, XML, and Annotation (deprecated) mapping.

  4. Run the Doctrine Extensions Example

    main

    To run the provided example project, follow these steps:

    1. Navigate to the root directory of the extensions.
    2. Install development dependencies: composer install.
    3. Configure your database by editing example/em.php.
    4. Run the console application: php example/bin/console.
    5. Create the database schema: php example/bin/console orm:schema-tool:create.
    6. Run the specific translation tree example: php example/bin/console app:print-category-translation-tree.
    composer install
    # Edit example/em.php with your DB credentials
    php example/bin/console orm:schema-tool:create
    php example/bin/console app:print-category-translation-tree
  5. Set up the Blameable behavior extension

    main

    To use the Blameable behavior, register the BlameableListener as an event subscriber to your Doctrine Object Manager (either an ORM Entity Manager or a MongoDB ODM Document Manager). After registration, you must provide the user information that should be recorded. You can do this by implementing a Gedmo\\Tool\\ActorProviderInterface and passing it to the listener via setActorProvider(), or by manually calling setUserValue() with the resolved user (which can be an object or a string).

    Note: If an actor provider is configured, any values manually set via setUserValue() will be ignored.

    use Gedmo\Blameable\BlameableListener;
    
    $listener = new BlameableListener();
    
    // $om is an instance of the ORM's entity manager or the MongoDB ODM's document manager
    $om->getEventManager()->addEventSubscriber($listener);
    
    // Option 1: Using an Actor Provider
    $listener->setActorProvider($provider);
    
    // Option 2: Setting user value manually
    $listener->setUserValue($user);
  6. Attach a listener to the Doctrine EventManager

    main

    Once your listener is created, you must register it as a subscriber to the Doctrine EventManager before passing the event manager to the Entity Manager constructor.

    <?php
    $evm = new \Doctrine\Common\EventManager();
    $encoderListener = new \Extension\Encoder\EncoderListener;
    $evm->addEventSubscriber($encoderListener);
    // Pass $evm to your EntityManager constructor
  7. Handle non-uploaded files (URLs or local files) via FileInfoInterface

    main

    To process files that are not part of a standard $_FILES upload (such as files from a URL or existing local files), implement the Gedmo\Uploadable\FileInfo\FileInfoInterface.

    Crucially, ensure isUploadedFile() returns false. This instructs the extension to use the copy function instead of move_uploaded_file to move the file to its destination.

    Alternatively, you can extend FileInfoArray and populate its $fileInfo array with keys: tmp_name, name, size, type, and error.

    use Gedmo\
    Uploadable\\FileInfo\\FileInfoInterface;
    
    class CustomFileInfo implements FileInfoInterface
    {
        protected $path;
        protected $name;
        protected $size;
        protected $type;
        protected $filename;
        protected $error = 0;
    
        public function __construct($path)
        {
            $this->path = $path;
            // Process the file and fill properties...
        }
    
        public function getTmpName() { return $this->path; }
        public function getName() { return $this->name; }
        public function getSize() { return $this->size; }
        public function getType() { return $this->type; }
        public function getError() { return $this->error; }
    
        public function isUploadedFile()
        {
            // Return false to use 'copy' instead of 'move_uploaded_file'
            return false;
        }
    }
    
    // Usage:
    $listener->setDefaultPath('/my/path');
    $file = new File();
    $listener->addEntityFileInfo($file, new CustomFileInfo('/path/to/file.txt'));
    $em->persist($file);
    $em->flush();
  8. Use PHP Attributes for Doctrine Extensions metadata

    main
    As of version 3.5, the Doctrine Extensions library supports mapping metadata using native PHP 8 Attributes. This functionality is modeled after the existing annotation metadata system. Use these attributes on your entity properties or classes to enable specific behavioral extensions like Blameable, Loggable, or Sluggable.
  9. Log IP changes for specific field values or field sets

    main

    Beyond general create/update actions, you can use the change action to log an IP address only when specific conditions are met:

    Single Field Changed to Specific Value

    Use on: 'change' with field and value parameters to trigger logging when a specific field reaches a specific value. You can use dot notation to watch related objects (e.g., category.archived).

    One of Many Fields Changed

    Pass an array of field names to the field parameter to trigger logging if any of the specified fields (or related paths via dot notation) are modified.

    #[ORM\Entity]
    class Article
    {
        // Track IP when 'published' becomes 'true'
        #[ORM\Column(type: Types::STRING, nullable: true)]
        #[Gedmo\IpTraceable(on: 'change', field: 'published', value: true)]
        public ?string $publishedFromIp = null;
    
        // Track IP when any of these fields change
        #[ORM\Column(type: Types::STRING, nullable: true)]
        #[Gedmo\IpTraceable(on: 'change', field: ['metaDescription', 'metaKeywords', 'category.metaDescription'])]
        public ?string $seoMetadataChangedFromIp = null;
    }
  10. Configure Sluggable behavior for Doctrine ORM or ODM

    main

    The Sluggable behavior automatically generates a slug from predefined fields and stores it in a designated property. It supports both Doctrine ORM and ODM.

    You can configure it using PHP Attributes, Annotations, or XML mapping. When using Attributes or Annotations, you must specify the fields option as an array of field names that will be used to generate the slug.

    Note: If you are using SoftDeleteable alongside Sluggable, you must explicitly call addManagedFilter with the name of your soft-delete filter during the Sluggable listener initialization to ensure unique slug generation accounts for soft-deleted entities.

    // Using Attributes
    #[Gedmo\Slug(fields: ['title', 'code'])]
    #[ORM\Column(length: 128, unique: true)]
    private $slug;
  11. Implement an ActorProviderInterface for user-based extensions

    main

    Extensions that track user values (such as Blameable or Loggable) require an implementation of Gedmo\Tool\ActorProviderInterface to resolve the current user. Since the library does not provide a default implementation, you must create a class in your application that implements this interface. The getActor() method should return the user object, a string identifier, or null if no user is present.

    namespace App\ Utils;
    
    use Gedmo\Tool\ActorProviderInterface;
    use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
    
    final class SymfonyActorProvider implements ActorProviderInterface
    {
        private TokenStorageInterface $tokenStorage;
    
        public function __construct(TokenStorageInterface $tokenStorage)
        {
            $this->tokenStorage = $tokenStorage;
        }
    
        /**
         * @return object|string|null
         */
        public function getActor()
        {
            $token = $this->tokenStorage->getToken();
    
            return $token ? $token->getUser() : null;
        }
    }