1

I am building a package that logs changes that happen on eloquent and I'm trying to figure out what to check to ignore the updated event when restored.

trait HasLogs
{
    public static function bootHasLogs(): void
    {
        // created ...
        self::updated(callback: fn ($model) => self::log($model, 'updated'));

        self::deleted(callback: function ($model) {
            // Ignore 'deleted' event when 'softDeleted' or 'forceDeleted'
            if (in_array('Illuminate\Database\Eloquent\SoftDeletes', (class_uses(self::class)))) {
                return;
            }
            self::log($model, 'deleted');
        });

        if (in_array('Illuminate\Database\Eloquent\SoftDeletes', (class_uses(self::class)))) {
            // softDeleted ...
            self::restored(callback: fn ($model) => self::log($model, 'restored'));
            // forceDeleted ...
        }
    }
    // ...
}

Here is the actions order if that helps:

  1. Model::restore() on the SoftDeletes trait.
  2. ---- restoring is fired.
  3. ---- Model::save()
  4. -------- saving is fired.
  5. -------- Model::performUpdate()
  6. ------------ updating is fired.
  7. ------------ Builder::update()
  8. ------------ updated is fired.
  9. -------- Model::finishSave()
  10. ------------ saved is fired.
  11. ---- restored is fired.
medilies
  • 1,811
  • 1
  • 8
  • 32
  • Pretty sure you can simply disable timestamps via `self::timestamps = false;` as suggested here: https://stackoverflow.com/questions/18904853/update-without-touching-timestamps-laravel – Tim Lewis Jul 28 '22 at 19:46
  • @TimLewis that would only avoid the call of `Model::updateTimestamps()` in `Model::performUpdate()` – medilies Jul 28 '22 at 19:54
  • Is there another spot then where `updated_at` is changed? I haven't dug into the chain of events called via `Model::restore()`, but I also haven't run into a case where I care if `updated_at` is changed via `restore` – Tim Lewis Jul 28 '22 at 20:01
  • 1
    @TimLewis `Model::performDeleteOnModel()` from the `SoftDeletes` doesn't touch the `timestamps` since it calls directly the `Builder::update()`. But `Model:restore()` goes through `Model::performUpdate()`. – medilies Jul 28 '22 at 20:12
  • 1
    And I found that only `performUpdate` and `performInsert` check `usesTimestamps()` then use `updateTimestamps()` – medilies Jul 28 '22 at 20:22
  • I made this PR https://github.com/laravel/framework/pull/43467 – medilies Jul 28 '22 at 22:01

1 Answers1

0

The trait HasLogs adds a Boolean attribute to the Model named loggableIsBeingRestored set to false by default.

The trait also registers a new listener for restoring which sets loggableIsBeingRestored to true at the begging of the described actions.

Then the updated listener checks for loggableIsBeingRestored before proceeding with its actions.

trait HasLogs
{
    public bool $loggableIsBeingRestored = false;

    public static function bootHasLogs(): void
    {
        // ...

        self::updated(callback: function ($model) {
            if (isset($model->loggableIsBeingRestored) && $model->loggableIsBeingRestored) {
                // This is a restored event so don't log!
                return;
            }

            self::log($model, 'updated');
        });

        // ...

        self::restored(callback: function ($model) {
            self::log($model, 'restored');

            $model->loggableIsBeingRestored = false;
        });

        self::restoring(callback: function ($model) {
            $model->loggableIsBeingRestored = true;
        });
    }
    // ...
}
medilies
  • 1,811
  • 1
  • 8
  • 32