Skip to content

getChanges() incorrectly reports stale data after refresh or repeated updates, causing unnecessary events like Scout's MakeSearchable #56254

@hadi60

Description

@hadi60

Laravel Version

12.19.3

PHP Version

8.2-fpm

Database Driver & Version

mariadb:10

Description

In Laravel 12, calling $model->getChanges() after performing an update() and a subsequent refresh() incorrectly shows old changes instead of resetting to an empty array. This results in false positives when determining if a model has dirty attributes. It affects workflows where you conditionally trigger actions like Laravel Scout's MakeSearchable job based on whether actual changes occurred.

Steps To Reproduce

Product::factory();

// 1. Change a field
$product->update(['update_time' => now()]);
dump($product->getChanges());  // Expected: ["update_time" => "..."]

// 2. Update with the same value (no actual change)
$product->update(['active' => $product->active]);
dump($product->getChanges());  // Expected: [], but returns previous changes instead

// 3. Refresh the model
$product->refresh();
dump($product->getChanges());  // Expected: [], but sometimes still returns previous changes

// 4. Update another field
$product->update(['title' => '123']);
dump($product->getChanges());  // Expected: ["title" => "123"]

Expected Behavior:

  • After an update, the $model->getChanges() should reflect only the actual changed attributes.
  • After a refresh(), the change tracking should reset to an empty state.
  • Updating with the same attribute value should not mark the model as changed.

Actual Behavior:

  • getChanges() retains previous change state even after refresh().
  • Updating with the same value still fires update events and jobs like MakeSearchable, even though no data changed.
  • This causes unintended job dispatching and unnecessary syncing with search engines or other services.

Impact:

  • Incorrect dirty state tracking causes performance issues when Scout, observers, or similar systems react to false changes.
  • This breaks a common optimization pattern where developers rely on $model->getChanges() to detect real changes before syncing with external services.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions