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 Changes Fetcher Laravel Package

daimos/entity-changes-fetcher

Small PHP service that detects and reports changes made to an entity by comparing values before and after updates. Useful for auditing, logging, change tracking, and syncing—returns a structured list of modified fields and their old/new values.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation Add the package via Composer:

    composer require daimos/entity-changes-fetcher
    

    Publish the config (if needed):

    php artisan vendor:publish --provider="Daimos\EntityChangesFetcher\EntityChangesFetcherServiceProvider"
    
  2. Basic Usage Inject the EntityChangesFetcher service into your controller or service:

    use Daimos\EntityChangesFetcher\EntityChangesFetcher;
    
    public function __construct(private EntityChangesFetcher $changesFetcher) {}
    
  3. First Use Case: Detecting Changes Fetch changes between two instances of an Eloquent model:

    $original = User::find(1);
    $updated = User::find(1)->update(['name' => 'New Name']);
    $changes = $this->changesFetcher->getChanges($original, $updated);
    

    Output:

    [
        'name' => [
            'old' => 'Old Name',
            'new' => 'New Name',
        ],
    ]
    

Implementation Patterns

Common Workflows

  1. Model Updates Use in update methods to log or audit changes:

    public function update(Request $request, User $user)
    {
        $original = $user->fresh();
        $user->update($request->validated());
        $changes = $this->changesFetcher->getChanges($original, $user->fresh());
    
        // Log changes to a dedicated table or service
        $this->logChanges($changes);
    
        return $user;
    }
    
  2. Form Request Validation Compare submitted data with existing data to enforce rules:

    public function validateChanges(Request $request, User $user)
    {
        $changes = $this->changesFetcher->getChanges(
            $user->fresh(),
            new User($request->all())
        );
    
        if ($changes['email'] && !str_contains($changes['email']['new'], '@company.com')) {
            throw ValidationException::withMessages(['email' => 'Email must be company domain.']);
        }
    }
    
  3. Event Listeners Attach to updated events to trigger side effects:

    public function handle(Updated $event)
    {
        $changes = $this->changesFetcher->getChanges(
            $event->model->fresh(['deleted_at']), // Exclude soft-deletes if needed
            $event->model
        );
    
        // Dispatch a notification or update related models
        event(new EntityUpdated($event->model, $changes));
    }
    

Integration Tips

  • Custom Attributes Extend the fetcher to handle custom attributes or accessors:
    $this->changesFetcher->getChanges($original, $updated, ['full_name']);
    
  • Nested Relationships For nested changes, recursively fetch changes on related models:
    $postChanges = $this->changesFetcher->getChanges($originalPost, $updatedPost);
    $authorChanges = $this->changesFetcher->getChanges($originalPost->author, $updatedPost->author);
    
  • Soft Deletes Exclude deleted_at from comparisons if using soft deletes:
    $this->changesFetcher->getChanges($original, $updated, [], ['deleted_at']);
    

Gotchas and Tips

Pitfalls

  1. Timestamps The fetcher may include created_at/updated_at in changes by default. Exclude them explicitly:

    $changes = $this->changesFetcher->getChanges($original, $updated, [], ['timestamps']);
    
  2. Casts and Accessors Custom casts (e.g., date: 'Y-m-d') or accessors may not reflect raw attribute changes. Fetch raw attributes if needed:

    $original->getAttributes(); // Raw attributes
    
  3. Circular References Avoid infinite loops when comparing nested relationships with circular references (e.g., User->posts->author->user).

  4. Mass Assignment Changes from fill() or update() may not match if mass assignment protection is enabled. Use forceFill() or $model->setRawAttributes().

Debugging

  • Verify Output Log the raw $original and $updated attributes to debug discrepancies:
    \Log::debug('Original:', $original->getAttributes());
    \Log::debug('Updated:', $updated->getAttributes());
    
  • Check for Mutators If changes aren’t detected, mutators may be altering values. Temporarily disable them for testing:
    $original->setMutator('attribute', null);
    

Extension Points

  1. Custom Comparators Override the default comparison logic by extending the fetcher:

    class CustomChangesFetcher extends EntityChangesFetcher
    {
        protected function compareValues($old, $new)
        {
            // Custom logic (e.g., ignore case for strings)
            return strtolower($old) !== strtolower($new);
        }
    }
    
  2. Event-Based Changes Hook into Laravel’s events to trigger the fetcher automatically:

    Event::listen('eloquent.updated', function ($model) {
        $changes = app(EntityChangesFetcher::class)->getChanges(
            $model->fresh(['deleted_at']),
            $model
        );
        // Handle changes
    });
    
  3. Database-Level Triggers For critical systems, combine with database triggers (e.g., PostgreSQL BEFORE UPDATE) to ensure no changes are missed.

Performance

  • Avoid fresh() in Loops Fetching fresh instances in loops (e.g., bulk updates) can be expensive. Cache original instances or use transactions:
    DB::transaction(function () {
        $originals = User::whereIn('id', [1, 2, 3])->get(['id', 'name']);
        foreach ($originals as $user) {
            $user->update(['name' => 'Updated']);
            $changes = $this->changesFetcher->getChanges($user, $user->fresh());
            // Process changes
        }
    });
    
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.
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony
spatie/flare-daemon-runtime