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 Watcher Bundle Laravel Package

bentools/doctrine-watcher-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Bundle

    composer require bentools/doctrine-watcher-bundle:1.0.x-dev
    

    Ensure compatibility with your Symfony version (e.g., ^5.4 or ^6.0).

  2. Enable the Bundle Add to config/bundles.php:

    return [
        // ...
        Bentools\DoctrineWatcherBundle\DoctrineWatcherBundle::class => ['all' => true],
    ];
    
  3. Basic Configuration Define a watcher service and configure it in config/packages/doctrine_watcher.yaml:

    doctrine_watcher:
        watch:
            App\Entity\Book:
                properties:
                    title:
                        callback: 'App\Services\BookWatcher::onTitleChange'
    
  4. First Use Case: Trigger Logic on Entity Changes Create a watcher service:

    // src/Services/BookWatcher.php
    namespace App\Services;
    
    class BookWatcher
    {
        public function onTitleChange(string $oldValue, string $newValue, object $entity): void
        {
            // Logic for title changes (e.g., log, notify, or update related data)
            \Log::info("Book title changed from {$oldValue} to {$newValue}");
        }
    }
    
  5. Hook into Doctrine Events Register the watcher in config/packages/doctrine.yaml:

    doctrine:
        orm:
            event_subscribers:
                - Bentools\DoctrineWatcherBundle\EventSubscriber\DoctrineWatcherSubscriber
    

Implementation Patterns

Workflow: Entity Change Monitoring

  1. Define Watchable Entities Configure properties to watch in doctrine_watcher.yaml:

    doctrine_watcher:
        watch:
            App\Entity\User:
                properties:
                    email:
                        callback: 'App\Services\UserWatcher::validateEmail'
                    posts:
                        callback: 'App\Services\UserWatcher::onPostsChange'
                        iterable: true  # For collections/arrays
    
  2. Implement Callbacks Create services with methods matching the configured callbacks:

    // src/Services/UserWatcher.php
    class UserWatcher
    {
        public function validateEmail(string $oldEmail, string $newEmail, User $user): void
        {
            if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) {
                throw new \InvalidArgumentException("Invalid email format");
            }
        }
    
        public function onPostsChange(array $oldPosts, array $newPosts, User $user): void
        {
            // Handle post collection changes (e.g., sync metadata)
        }
    }
    
  3. Tag-Based Configuration (Alternative) Use service tags for dynamic registration:

    services:
        App\Services\PostWatcher:
            tags:
                - { name: bentools.doctrine_watcher, entity_class: App\Entity\Post, property: 'content', method: 'onContentUpdate' }
    
  4. Integration with Doctrine Events Leverage Symfony’s event system for pre/post-save logic:

    // src/EventSubscriber/CustomSubscriber.php
    use Doctrine\Common\EventSubscriber;
    use Doctrine\ORM\Event\OnFlushEventArgs;
    
    class CustomSubscriber implements EventSubscriber
    {
        public function getSubscribedEvents(): array
        {
            return ['onFlush'];
        }
    
        public function onFlush(OnFlushEventArgs $args): void
        {
            $entityManager = $args->getEntityManager();
            $uow = $entityManager->getUnitOfWork();
    
            foreach ($uow->getScheduledEntityUpdates() as $entity) {
                if ($entity instanceof \App\Entity\Book) {
                    // Trigger watcher logic manually if needed
                }
            }
        }
    }
    
  5. Bulk Operations For mass updates (e.g., via Doctrine QueryBuilder), disable watchers temporarily:

    $entityManager->getConnection()->getConfiguration()->setSQLLogger(null);
    // Perform bulk operations
    $entityManager->flush();
    

Gotchas and Tips

Pitfalls

  1. Circular Dependencies Avoid circular references in callbacks (e.g., UserWatcher calling BookWatcher which triggers UserWatcher again). Use dependency injection carefully.

  2. Performance Overhead

    • Iterable Properties: Watching large collections (e.g., OneToMany) can slow down flushes. Use iterable: false for performance-critical paths or limit watched properties.
    • Deep Cloning: The bundle clones entity properties for comparison. For complex objects, this may cause memory issues. Use serializable: true in config to serialize instead:
      doctrine_watcher:
          watch:
              App\Entity\ComplexEntity:
                  properties:
                      data:
                          callback: 'App\Services\ComplexWatcher::handleData'
                          serializable: true
      
  3. Event Ordering Callbacks run after Doctrine’s preUpdate/prePersist but before postUpdate/postPersist. Avoid side effects that interfere with Doctrine’s lifecycle.

  4. Configuration Merging If using Symfony Flex, ensure doctrine_watcher.yaml is in the correct location (config/packages/) to avoid merge conflicts with default configs.

  5. Doctrine Version Compatibility The bundle assumes Doctrine ORM. For Doctrine DBAL or other ORMs, extend the subscriber manually.


Debugging

  1. Enable Logging Add to config/services.yaml:

    Bentools\DoctrineWatcherBundle\:
        resource: '../vendor/bentools/doctrine-watcher-bundle/Resources/config/services.yaml'
        public: true
    

    Then log callback invocations:

    public function onTitleChange(string $old, string $new, object $entity): void
    {
        \Log::debug("Watcher triggered for {$entity::class}.title: {$old} -> {$new}");
    }
    
  2. Disable Watchers Temporarily Override the subscriber in a test environment:

    # config/packages/test/doctrine_watcher.yaml
    doctrine_watcher:
        enabled: false
    
  3. Check for Missing Services If callbacks fail with ServiceNotFoundException, ensure the service is:

    • Autowired (use autowire: true in services.yaml).
    • Public (add public: true if needed).

Extension Points

  1. Custom Comparison Logic Extend the Bentools\DoctrineWatcher\PropertyWatcher class to add custom comparison (e.g., ignore whitespace):

    class CustomPropertyWatcher extends PropertyWatcher
    {
        protected function compareValues($oldValue, $newValue): bool
        {
            return trim($oldValue) === trim($newValue);
        }
    }
    

    Register it in services.yaml:

    services:
        App\Services\CustomPropertyWatcher:
            tags:
                - { name: bentools.doctrine_watcher.property_watcher, alias: 'trim_comparison' }
    
  2. Async Callbacks Use Symfony Messenger to offload watcher logic:

    public function onTitleChange(string $old, string $new, Book $book): void
    {
        $message = new ProcessBookTitleChange($book->getId(), $old, $new);
        $this->messageBus->dispatch($message);
    }
    
  3. Dynamic Property Watching Implement a runtime-based watcher (e.g., via Doctrine lifecycle callbacks):

    use Doctrine\ORM\Event\LifecycleEventArgs;
    
    class DynamicWatcher
    {
        public function postLoad(LifecycleEventArgs $args): void
        {
            $entity = $args->getObject();
            if ($entity instanceof Book && $entity->isDynamicWatchEnabled()) {
                // Register watcher dynamically
            }
        }
    }
    
  4. Bulk Property Updates For batch operations, use a single callback with a list of changes:

    doctrine_watcher:
        watch:
            App\Entity\Product:
                properties:
                    prices:
                        callback: 'App\Services\ProductWatcher::handleBulkPriceUpdate'
                        iterable: true
                        bulk: true  # Custom tag to indicate bulk handling
    
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.
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui