Weave Code
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 Modification Events Bundle Laravel Package

dmytrof/doctrine-modification-events-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Install the Bundle:

    composer require dmytrof/doctrine-modification-events-bundle
    

    Enable it in config/bundles.php:

    return [
        // ...
        Dmytrof\DoctrineModificationEventsBundle\DmytrofDoctrineModificationEventsBundle::class => ['all' => true],
    ];
    
  2. First Use Case: Audit Logging Create a listener service to log entity changes:

    php bin/console make:event-listener AuditLogger
    

    Update the generated listener (src/EventListener/AuditLogger.php):

    namespace App\EventListener;
    
    use Dmytrof\DoctrineModificationEventsBundle\Event\ModificationEvent;
    use Psr\Log\LoggerInterface;
    
    class AuditLogger
    {
        public function __construct(private LoggerInterface $logger)
        {
        }
    
        public function onEntityUpdate(ModificationEvent $event): void
        {
            $entity = $event->getEntity();
            $changes = $event->getChanges();
            $this->logger->info(
                sprintf('Entity %s updated. Changes: %s', get_class($entity), json_encode($changes))
            );
        }
    }
    
  3. Configure the Listener Add the listener to config/packages/dmytrof_doctrine_modification_events.yaml:

    dmytrof_doctrine_modification_events:
        listeners:
            App\Entity\User:  # Target entity
                - on: [postUpdate]  # Event type
                  method: onEntityUpdate  # Listener method
                  service: App\EventListener\AuditLogger
    
  4. Test It Update a User entity and check logs:

    $user = $entityManager->getRepository(User::class)->find(1);
    $user->setEmail('[email protected]');
    $entityManager->flush();
    

    Verify the log entry appears.


Implementation Patterns

Core Workflows

  1. Entity-Specific Listeners Target specific entities with granular control:

    dmytrof_doctrine_modification_events:
        listeners:
            App\Entity\Product:
                - on: [preUpdate, postUpdate]
                  method: validatePrice
                  service: app.product_validator
            App\Entity\Order:
                - on: [postUpdate]
                  method: notifyCustomer
                  service: app.order_notifier
    
  2. Global Listeners Apply to all entities (use cautiously):

    dmytrof_doctrine_modification_events:
        global_listeners:
            - on: [postUpdate]
              method: logChange
              service: app.global_audit_logger
    
  3. Conditional Logic Filter events in listeners:

    public function onProductUpdate(ModificationEvent $event): void
    {
        $product = $event->getEntity();
        if ($product->isDiscounted()) {
            $this->dispatchDiscountAlert($product);
        }
    }
    

Integration Tips

  • Symfony Messenger Offload async tasks (e.g., notifications) to a queue:

    use Symfony\Component\Messenger\MessageBusInterface;
    
    public function __construct(private MessageBusInterface $bus) {}
    
    public function onUserUpdate(ModificationEvent $event): void
    {
        $this->bus->dispatch(new UserUpdatedEvent($event->getEntity()));
    }
    
  • Doctrine Lifecycle Callbacks Combine with @HasLifecycleCallbacks for pre/post-persist/update:

    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity]
    #[ORM\HasLifecycleCallbacks]
    class Product {
        #[ORM\PreUpdate]
        public function preUpdate(): void {
            // Custom logic before bundle events fire
        }
    }
    
  • Event Payload Leverage ModificationEvent methods:

    $entity = $event->getEntity();
    $changes = $event->getChanges(); // Array of [field => [old, new]]
    $unitOfWork = $event->getUnitOfWork();
    $isFlushAndModified = $event->isFlushAndModified();
    

Best Practices

  • Prioritize Listeners Use priority in YAML to control execution order:

    listeners:
        App\Entity\User:
            - on: [postUpdate]
              method: notifyAdmin
              service: app.user_listener
              priority: 10  # Higher priority = earlier execution
    
  • Idempotent Design Ensure listeners handle duplicate events gracefully (e.g., during retries):

    public function onOrderUpdate(ModificationEvent $event): void
    {
        if ($event->isFlushAndModified()) {
            $this->updateExternalSystem($event->getEntity());
        }
    }
    
  • Testing Use ModificationEvent in unit tests:

    $event = new ModificationEvent(
        $entityManager->getUnitOfWork(),
        $entity,
        ['price' => [100, 150]]
    );
    $listener->onProductUpdate($event);
    

Gotchas and Tips

Pitfalls

  1. Duplicate Events

    • Issue: postUpdate may fire twice if ForceFlushPreviousModificationsInterface is triggered (e.g., in nested transactions).
    • Fix: Check $event->isFlushAndModified() or use postFlush instead of postUpdate for critical logic.
  2. Circular Dependencies

    • Issue: Listeners modifying the same entity during event processing can cause infinite loops.
    • Fix: Avoid modifying entities in preUpdate listeners. Use postUpdate for side effects.
  3. Performance Spikes

    • Issue: Heavy listeners (e.g., API calls) during bulk operations.
    • Fix: Batch events or use async processing (Symfony Messenger).
  4. Missing Changes

    • Issue: getChanges() may return empty for null values or unchanged fields.
    • Fix: Compare old/new values manually if needed:
      $uow = $event->getUnitOfWork();
      $changeSet = $uow->getEntityChangeSet($entity);
      
  5. Annotation Conflicts

    • Issue: Custom Doctrine lifecycle callbacks may override bundle events.
    • Fix: Test with @ORM\PreUpdate/@ORM\PostUpdate annotations disabled.

Debugging Tips

  • Enable Event Dispatcher Debugging

    php bin/console debug:event-dispatcher
    

    Look for dmytrof.doctrine.modification.* events.

  • Log Event Payloads

    public function onDebugEvent(ModificationEvent $event): void
    {
        file_put_contents(
            'debug_event.log',
            print_r($event->getChanges(), true),
            FILE_APPEND
        );
    }
    
  • Check UnitOfWork State Use getScheduledEntityInsertions()/getScheduledEntityUpdates() to inspect pending changes:

    $uow = $event->getUnitOfWork();
    $updates = $uow->getScheduledEntityUpdates();
    

Extension Points

  1. Custom Event Classes Extend ModificationEvent for additional data:

    namespace App\Event;
    
    use Dmytrof\DoctrineModificationEventsBundle\Event\ModificationEvent;
    
    class CustomModificationEvent extends ModificationEvent
    {
        public function getCustomData(): array
        {
            return $this->customData;
        }
    }
    
  2. Dynamic Listeners Register listeners programmatically in a compiler pass:

    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    
    class DynamicListenerPass implements CompilerPassInterface
    {
        public function process(ContainerBuilder $container): void
        {
            $definition = $container->findDefinition('dmytrof.doctrine_modification_events.listener_registry');
            $definition->addMethodCall('addDynamicListener', [
                'App\Entity\Product',
                ['on' => 'postUpdate', 'method' => 'syncToExternalApi']
            ]);
        }
    }
    
  3. Event Filtering Create a decorator to filter events:

    use Dmytrof\DoctrineModificationEventsBundle\Event\ModificationEvent;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    
    class FilteringSubscriber implements EventSubscriberInterface
    {
        public static function getSubscribedEvents(): array
        {
            return [
                ModificationEvent::POST_UPDATE => 'filterEvent',
            ];
        }
    
        public function filterEvent(ModificationEvent $event): void
        {
            if (!$event->getEntity()->isActive()) {
                $event->stopPropagation();
            }
        }
    }
    

Configuration Quirks

  • YAML vs. Attributes Prefer YAML for complex configurations; use attributes for simple cases:
    use Dmytrof\DoctrineModification
    
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.
calmfox/watch-sylius
damienfern/grpc-symfony-bundle
atoolo/index-bundle
atoolo/genai-bundle
coprotoai/laravel-ticket
davidjln/llm-carbon-bundle
cryonighter/valid-request-bundle
coolms/taxonomy-bundle
coolms/field-bundle
articulate-orm/symfony
aaix/laravel-tall-architect
ephoto/akeneo-connector
emmanuelballery/eb-plantumlbundle
emielburgman/symfony-visitor-beacon
emielburgman/symfony-visit-storage
emielburgman/symfony-security-headers
emielburgman/symfony-log-viewer
emarref/xdebug-bundle
emarref/pubnub-bundle
elriseio/finance-money-bundle