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 Changeset Laravel Package

bentools/doctrine-changeset

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require bpolaszek/doctrine-changeset
    

    Ensure your project uses Doctrine ORM (Laravel Eloquent is not supported).

  2. Service Registration: Add the EntityTracker to your Laravel service container (e.g., in config/app.php under bindings or via a service provider):

    $this->app->bind(EntityTracker::class, function ($app) {
        return new EntityTracker($app->make(EntityManagerInterface::class));
    });
    
  3. First Use Case: Inject EntityTracker into a service or controller and track changes on an entity:

    use BenTools\DoctrineChangeSet\Tracker\EntityTracker;
    
    public function updateBook(Book $book, EntityTracker $tracker) {
        $book->setName('New Title');
        if ($tracker->hasChanged($book, 'name')) {
            // Handle the change (e.g., log, audit, or trigger events)
            $changeSet = $tracker->getChangeSet($book, 'name');
            logger()->info("Name changed from {$changeSet->from} to {$changeSet->to}");
        }
    }
    
  4. Nested Object Tracking: Use the #[TrackChanges] attribute on properties to track changes in nested objects:

    use BenTools\DoctrineChangeSet\Attribute\TrackChanges;
    
    class Author {
        #[TrackChanges]
        public AuthorDetails $details;
    }
    

Implementation Patterns

Core Workflows

  1. Change Detection: Use hasChanged() to check if an entity or specific property has changed:

    if ($tracker->hasChanged($entity)) {
        // Entity has any changes
    }
    if ($tracker->hasChanged($entity, 'propertyName')) {
        // Specific property changed
    }
    
  2. ChangeSet Retrieval: Fetch detailed change information for auditing or validation:

    $changeSet = $tracker->getChangeSet($entity, 'propertyName');
    $oldValue = $changeSet->from;
    $newValue = $changeSet->to;
    
  3. Conditional Logic: Use ChangeSet methods to enforce business rules:

    if ($changeSet->hadPreviousValue('active') && !$changeSet->hasNewValue('active')) {
        // Handle deactivation logic
    }
    
  4. Nested Object Handling: Track changes in nested objects by annotating properties with #[TrackChanges]:

    class Order {
        #[TrackChanges]
        public OrderDetails $details;
    }
    $order->details->setStatus('shipped');
    $tracker->hasChanged($order, 'details'); // true
    

Integration Tips

  1. Event Listeners: Combine with Doctrine lifecycle events (e.g., preUpdate) to react to changes:

    $em->getEventManager()->addEventListener(
        \Doctrine\ORM\Events::preUpdate,
        function ($event) {
            $entity = $event->getObject();
            $tracker = $event->getEntityManager()->getRepository()->getEntityTracker();
            if ($tracker->hasChanged($entity)) {
                // Custom logic before update
            }
        }
    );
    
  2. Audit Logging: Log changesets to a separate table for compliance or debugging:

    $auditLog = new AuditLog();
    $auditLog->entityClass = get_class($entity);
    $auditLog->property = $propertyName;
    $auditLog->oldValue = $changeSet->from;
    $auditLog->newValue = $changeSet->to;
    $em->persist($auditLog);
    
  3. Validation: Validate changes before persisting:

    $changeSet = $tracker->getChangeSet($entity, 'price');
    if ($changeSet->hasNewValue() && $changeSet->to < 0) {
        throw new \InvalidArgumentException("Price cannot be negative");
    }
    
  4. Testing: Mock EntityTracker in unit tests to verify change detection logic:

    $tracker = $this->createMock(EntityTracker::class);
    $tracker->method('hasChanged')->with($entity, 'name')->willReturn(true);
    

Gotchas and Tips

Pitfalls

  1. EntityManager Scope: The EntityTracker is tied to the EntityManager. Ensure you’re using the same EntityManager instance when checking changes (e.g., avoid mixing EntityManager instances in multi-DB setups).

  2. Nested Object Limitations: #[TrackChanges] only works for properties typed as object. Primitive types (e.g., string, int) or collections require manual tracking.

  3. Change Detection Timing: Changes are detected after the property is modified. For example:

    $entity->setName('foo');
    $tracker->hasChanged($entity, 'name'); // true (detected after setName)
    $entity->name = 'bar'; // Direct assignment may not trigger tracking unless EntityTracker is configured to handle it.
    
  4. Performance: Frequent calls to hasChanged() or getChangeSet() on large entities may impact performance. Cache results if checking the same properties repeatedly.

  5. Doctrine Events: Changesets are cleared after preFlush or postFlush events. If you need to persist changesets, do so in preUpdate/prePersist listeners.

Debugging

  1. Verify Tracking: If hasChanged() returns false unexpectedly, ensure:

    • The EntityManager is the same instance used to persist the entity.
    • The property name matches exactly (case-sensitive).
    • For nested objects, #[TrackChanges] is applied correctly.
  2. ChangeSet Inspection: Use var_dump($changeSet) to debug ChangeSet objects:

    $changeSet = $tracker->getChangeSet($entity, 'property');
    var_dump($changeSet->from, $changeSet->to, $changeSet->getMetadata());
    
  3. Clear Changes Manually: If changes are not being cleared as expected, manually reset the UnitOfWork:

    $em->getUnitOfWork()->clear($entity);
    

Tips

  1. Custom ChangeSet Methods: Extend the ChangeSet class to add domain-specific methods:

    class CustomChangeSet extends \BenTools\DoctrineChangeSet\ChangeSet {
        public function isPriceIncreased(): bool {
            return $this->from < $this->to && $this->property === 'price';
        }
    }
    
  2. Bulk Change Detection: For bulk operations, iterate over entities and collect changesets:

    $changesets = [];
    foreach ($entities as $entity) {
        if ($tracker->hasChanged($entity)) {
            $changesets[$entity->getId()] = $tracker->getChangeSet($entity);
        }
    }
    
  3. Configuration: If using Laravel’s service container, bind EntityTracker with a specific EntityManager:

    $this->app->bind(EntityTracker::class, function ($app) {
        return new EntityTracker($app->make('em.default')); // or 'em.secondary'
    });
    
  4. Laravel Eloquent Alternative: While this package is Doctrine-specific, consider using Laravel’s built-in synchronizedToDatabase() or fresh() for Eloquent models if you’re not using raw Doctrine.

  5. Testing Edge Cases: Test scenarios like:

    • Null values ($entity->property = null).
    • Uninitialized properties.
    • Circular references in nested objects.
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.
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
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