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

Entity Change Watch Bundle Laravel Package

actiane/entity-change-watch-bundle

Symfony bundle to watch Doctrine entity lifecycle changes. Configure per-entity callbacks on create, update (all or specific properties), and delete. Invoke tagged services after flush with entity, changed properties, and EntityManager; supports pre-flush via flush:false.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require actiane/entity-change-watch-bundle
    

    Enable the bundle in config/bundles.php:

    return [
        // ...
        Actiane\EntityChangeWatchBundle\EntityChangeWatchBundle::class => ['all' => true],
    ];
    
  2. Configuration: Create config/packages/actiane_entity_change_watch.yaml (or merge into config/packages/framework.yaml):

    entity_change_watch:
        classes:
            App\Entity\MyEntity:
                update:
                    properties:
                        name:
                            - { name: 'App\Service\MyListener', method: 'onNameChange' }
    
  3. First Use Case: Trigger a service method when MyEntity's name property is updated:

    // src/Service/MyListener.php
    class MyListener {
        public function onNameChange(MyEntity $entity, string $oldValue, string $newValue) {
            // Log or process the change
            Logger::info("Name changed from {$oldValue} to {$newValue}");
        }
    }
    

Implementation Patterns

Core Workflows

  1. Lifecycle Hooks: Use create, update, and delete to bind services to Doctrine events:

    entity_change_watch:
        classes:
            App\Entity\User:
                create:
                    - { name: 'App\Service\AuditService', method: 'logCreation' }
                update:
                    all:
                        - { name: 'App\Service\NotificationService', method: 'sendUpdateNotification' }
    
  2. Property-Level Granularity: Target specific fields for fine-grained control:

    update:
        properties:
            email:
                - { name: 'App\Service\EmailValidator', method: 'validateEmailChange' }
            status:
                - { name: 'App\Service\WorkflowService', method: 'handleStatusTransition' }
    
  3. Service Integration: Pass entity data to services via method signatures:

    public function handleStatusTransition(MyEntity $entity, string $oldStatus, string $newStatus) {
        // Business logic for status changes
    }
    
  4. Conditional Logic: Use flush: false to prevent auto-flushing during pre-save hooks:

    create:
        - { name: 'App\Service\PreSaveService', method: 'validateBeforeSave', flush: false }
    

Advanced Patterns

  • Dynamic Configuration: Load YAML config dynamically via EntityChangeWatchBundle's Extension class:

    $this->container->get('actiane_entity_change_watch.extension')->addListener(
        'App\Entity\Product',
        'update',
        ['App\Service\InventoryService', 'updateStock']
    );
    
  • Event Aggregation: Combine multiple services for a single event:

    update:
        all:
            - { name: 'App\Service\Logger', method: 'logUpdate' }
            - { name: 'App\Service\Analytics', method: 'trackChange' }
    
  • Entity Inheritance: Extend parent entity configurations:

    classes:
        App\Entity\BaseEntity:  # Parent
            update:
                all:
                    - { name: 'App\Service\AuditService', method: 'logUpdate' }
        App\Entity\User:       # Child (inherits + overrides)
            update:
                properties:
                    username:
                        - { name: 'App\Service\UsernameValidator', method: 'validate' }
    

Gotchas and Tips

Common Pitfalls

  1. Circular Dependencies: Avoid injecting the entity manager into listeners that trigger other Doctrine operations (risk of infinite loops). Fix: Use flush: false or refactor to async processing.

  2. Missing Method Signatures: The bundle expects methods to match:

    public function methodName(Entity $entity, mixed $oldValue = null, mixed $newValue = null)
    

    Tip: Use PHPDoc to enforce this:

    /** @param mixed|null $oldValue */
    /** @param mixed|null $newValue */
    
  3. Configuration Overrides: Later YAML entries overwrite earlier ones. Use explicit keys to avoid surprises:

    # Overrides all 'update' listeners for App\Entity\User
    App\Entity\User:
        update: ~  # Clears previous listeners
    
  4. Performance: Heavy listeners on update.all can slow bulk operations. Tip: Limit to critical properties or use flush: false for async processing.

  5. Doctrine Events Order: prePersist/preUpdate run before preFlush. Place validation logic in prePersist if you need to reject saves early.

Debugging Tips

  • Enable Logging: Add to config/packages/monolog.yaml:

    handlers:
        actiane:
            type: stream
            path: "%kernel.logs_dir%/actiane_entity_change.log"
            level: debug
            channels: ["actiane_entity_change"]
    

    Then configure the bundle to log events:

    entity_change_watch:
        debug: true
    
  • Check Event Firing: Verify listeners are registered via:

    $this->container->get('actiane_entity_change_watch.listener_registry')->getListeners('App\Entity\User');
    
  • Handle Exceptions: Wrap listener calls in try-catch to prevent Doctrine from failing silently:

    try {
        $service->method($entity, $oldValue, $newValue);
    } catch (\Exception $e) {
        // Log and continue
    }
    

Extension Points

  1. Custom Event Types: Extend the bundle to support additional lifecycle events (e.g., postLoad):

    // src/EventListener/CustomListener.php
    class CustomListener implements ContainerAwareInterface {
        public function postLoad(EntityManager $em, Entity $entity) {
            // Custom logic
        }
    }
    
  2. Dynamic Property Mapping: Use a service to dynamically resolve properties:

    update:
        properties:
            dynamic_property:
                - { name: 'App\Service\DynamicPropertyResolver', method: 'resolveAndNotify' }
    
  3. Conditional Listeners: Implement logic to skip listeners based on runtime conditions:

    public function onUpdate(MyEntity $entity) {
        if (!$entity->isActive()) {
            return; // Skip inactive entities
        }
        // ...
    }
    
  4. Bulk Operation Optimization: For bulk updates, disable listeners temporarily:

    $em->getEventManager()->removeEventListeners('preUpdate');
    // Perform bulk operations
    $em->getEventManager()->addEventListeners($listeners);
    
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope