From 70488367f92db801696aa6c9d456c5fd57d461ad Mon Sep 17 00:00:00 2001 From: Jarek Tkaczyk Date: Fri, 1 Mar 2019 09:31:37 +0800 Subject: [PATCH] restoring only models that were deleted along with parent, not before --- README.md | 8 ++++++++ src/CascadeDeletes.php | 20 +++++++++++++++----- src/CascadeDeletesExtension.php | 33 +++++++++++++++++++++------------ 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3186d37..b6c56d2 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Cascading (soft / hard) deletes for the [Eloquent ORM (Laravel 5.0+)](https://la * [simple usage](#simple) * [using with `SoftDeletes`](#using-with-softdeletes) +* [CHANGELOG](#changelog) ## Installation @@ -214,3 +215,10 @@ class Product extends \Illuminate\Database\Eloquent\Model ## Contribution All contributions are welcome, PRs must be **PSR-2 compliant**. + + +## CHANGELOG + +#### v6 <- v5.x +- Restoring now will cascade **only for children that were deleted along with the parent model**, not before. That is, if some of children models were soft deleted before the parent model got deleted, those children will not be restored when parent is being restored. That's the expected behavior. +- The above requires that when calling restore on the query builder rather than single model (`$query->restore()` vs `$model->restore()`), it will run N queries, 1 for each restored model. diff --git a/src/CascadeDeletes.php b/src/CascadeDeletes.php index 41357d1..865b6bd 100644 --- a/src/CascadeDeletes.php +++ b/src/CascadeDeletes.php @@ -57,11 +57,21 @@ protected static function registerDeletedHandler() protected static function registerRestoredHandler() { static::restored(function ($model) { - foreach ($model->deletesWith() as $relation) { - if ($model->{$relation}()->getMacro('onlyTrashed')) { - $model->{$relation}()->onlyTrashed()->get()->each(function ($related) { - $related->restore(); - }); + foreach ($model->deletesWith() as $relation_name) { + $relation = $model->{$relation_name}(); + + if ($relation->getMacro('onlyTrashed')) { + $related = $relation->getRelated(); + + $parent_deleted_at = $model->getAttribute($model->getDeletedAtColumn()); + + $relation->onlyTrashed() + // This will ensure we don't restore models that had been deleted before this model + ->where($related->getQualifiedDeletedAtColumn(), '>=', $parent_deleted_at) + ->get() + ->each(function ($related) { + $related->restore(); + }); } } }); diff --git a/src/CascadeDeletesExtension.php b/src/CascadeDeletesExtension.php index 466020e..a1d5ef2 100644 --- a/src/CascadeDeletesExtension.php +++ b/src/CascadeDeletesExtension.php @@ -41,19 +41,28 @@ protected function registerRestoredHandler(Builder $builder) { // Here we override restore macro in order to add required behaviour. $builder->macro('restore', function (Builder $builder) { - $restored = $builder->onlyTrashed()->get()->all(); - - // In order to get relation query with correct constraint applied we have - // to mimic eager loading 'where KEY in' behaviour rather than default - // constraints for single model which would be invalid in this case. - Relation::noConstraints(function () use ($model, $restored) { - foreach ($model->deletesWith() as $relation) { - if ($this->usesSoftDeletes($query = $model->{$relation}())) { - $query->onlyTrashed()->addEagerConstraints($restored); - $query->restore(); + $model = $builder->getModel(); + + collect($model->deletesWith()) + ->filter(function ($relation_name) use ($model) { + return $this->usesSoftDeletes($model->{$relation_name}()); + })->each(function ($relation_name) use ($builder) { + // It is a bit tricky to achieve expected result which is restoring only those children that were + // delete along with the parent model (not before). We cannot easily achieve that on the query + // level, so we'll simply run N queries here. Should be fine as this is an edge case anyway. + $restored_models = $builder->onlyTrashed()->get(); + + foreach ($restored_models as $restored_model) { + $relation = $restored_model->{$relation_name}(); + $related = $relation->getRelated(); + + $parent_deleted_at = $restored_model->getAttribute($restored_model->getDeletedAtColumn()); + + $relation + ->where($related->getQualifiedDeletedAtColumn(), '>=', $parent_deleted_at) + ->restore(); } - } - }); + }); return $builder->update([$builder->getModel()->getDeletedAtColumn() => null]); });