From c2c606b89ede8884842c5c05b756ec13c2f6aa7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Mon, 29 Aug 2022 23:25:03 +0200 Subject: [PATCH] working demo --- tests/RandomTest.php | 133 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 6 deletions(-) diff --git a/tests/RandomTest.php b/tests/RandomTest.php index 639f1aa31c..13b49cea77 100644 --- a/tests/RandomTest.php +++ b/tests/RandomTest.php @@ -27,6 +27,8 @@ protected function init(): void } class Model_Item extends Model { + use ModelSoftDeleteTrait; + public $table = 'item'; protected function init(): void @@ -36,6 +38,8 @@ protected function init(): void $this->addField('name'); $this->hasOne('parent_item_id', ['model' => [self::class]]) ->addTitle(); + + $this->initSoftDelete(); } } class Model_Item2 extends Model @@ -71,6 +75,83 @@ protected function init(): void } } +trait ModelSoftDeleteTrait +{ + protected function initSoftDelete(): void + { + $this->addField('is_deleted', ['type' => 'boolean', 'nullable' => false, 'default' => false]); + $this->addCondition('is_deleted', false); + $this->onHook(Model::HOOK_BEFORE_DELETE, function (Model $entity) { + $softDeleteController = new ControllerSoftDelete(); + $softDeleteController->softDelete($entity); + + $entity->hook(Model::HOOK_AFTER_DELETE); + $entity->breakHook(false); // this will cancel original Model::delete() + }); + } +} + +class ControllerSoftDelete +{ + protected function init(): void + { + // example broken for clone "Object cannot be cloned with hook bound to a different object than this" + // TODO remove this code from docs, hard to fix, controller is not meant to be added this way to model + throw new \Error(); + } + + /** + * @return mixed + */ + public function invokeCallbackWithoutUndeletedCondition(Model $model, \Closure $callback) + { + $model->getField('is_deleted'); // assert field exists + + $scopeElementsOrig = $model->scope()->elements; + try { + foreach ($model->scope()->elements as $k => $v) { + if ($v instanceof Model\Scope\Condition && $v->key === 'is_deleted' && $v->operator === '=' && $v->value === false) { + unset($model->scope()->elements[$k]); + } + } + + return $callback(); + } finally { + $model->scope()->elements = $scopeElementsOrig; + } + } + + public function softDelete(Model $entity): void + { + $entity->assertIsLoaded(); + + $this->invokeCallbackWithoutUndeletedCondition($entity->getModel(), function () use ($entity): void { + if ($entity->hook('beforeSoftDelete') === false) { + return; + } + + $entity->saveAndUnload(['is_deleted' => true]); + + $entity->hook('afterSoftDelete'); + }); + } + + public function restore(Model $entity): void + { + $entity->assertIsLoaded(); + + $this->invokeCallbackWithoutUndeletedCondition($entity->getModel(), function () use ($entity): void { + if ($entity->hook('beforeRestore') === false) { + return; + } + + $entity->saveAndUnload(['is_deleted' => false]); + + $entity->hook('afterRestore'); + }); + } +} + class RandomTest extends TestCase { public function testRate(): void @@ -87,6 +168,46 @@ public function testRate(): void static::assertSame(2, $m->executeCountQuery()); } + public function testSoftDelete(): void + { + $m = new Model_Item($this->db); + $this->createMigrator($m)->dropIfExists()->create(); + + $m->insert(['name' => 'John']); + $m->insert(['name' => 'Michael']); + + $softDeleteController = new ControllerSoftDelete(); + + $entity = $m->loadBy('name', 'Michael'); + $softDeleteController->softDelete($entity); + static::assertEquals([ + 'item' => [ + 1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => null, 'is_deleted' => '0'], + 2 => ['id' => 2, 'name' => 'Michael', 'parent_item_id' => null, 'is_deleted' => '1'], + ], + ], $this->getDb()); + + $entity = $softDeleteController->invokeCallbackWithoutUndeletedCondition($m, function () use ($m) { + return $m->loadBy('name', 'Michael'); + }); + $softDeleteController->restore($entity); + static::assertEquals([ + 'item' => [ + 1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => null, 'is_deleted' => '0'], + 2 => ['id' => 2, 'name' => 'Michael', 'parent_item_id' => null, 'is_deleted' => '0'], + ], + ], $this->getDb()); + + $entity = $m->loadBy('name', 'Michael'); + $entity->delete(); + static::assertEquals([ + 'item' => [ + 1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => null, 'is_deleted' => '0'], + 2 => ['id' => 2, 'name' => 'Michael', 'parent_item_id' => null, 'is_deleted' => '1'], + ], + ], $this->getDb()); + } + public function testTitleImport(): void { $this->setDb([ @@ -205,16 +326,16 @@ public function testSameTable(): void { $this->setDb([ 'item' => [ - 1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => 1], - 2 => ['id' => 2, 'name' => 'Sue', 'parent_item_id' => 1], - 3 => ['id' => 3, 'name' => 'Smith', 'parent_item_id' => 2], + 1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => 1, 'is_deleted' => false], + 2 => ['id' => 2, 'name' => 'Sue', 'parent_item_id' => 1, 'is_deleted' => false], + 3 => ['id' => 3, 'name' => 'Smith', 'parent_item_id' => 2, 'is_deleted' => false], ], ]); $m = new Model_Item($this->db, ['table' => 'item']); static::assertSame( - ['id' => 3, 'name' => 'Smith', 'parent_item_id' => 2, 'parent_item' => 'Sue'], + ['id' => 3, 'name' => 'Smith', 'parent_item_id' => 2, 'parent_item' => 'Sue', 'is_deleted' => false], $m->load(3)->get() ); } @@ -348,8 +469,8 @@ public function testGetTitle(): void { $this->setDb([ 'item' => [ - 1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => 1], - 2 => ['id' => 2, 'name' => 'Sue', 'parent_item_id' => 1], + 1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => 1, 'is_deleted' => false], + 2 => ['id' => 2, 'name' => 'Sue', 'parent_item_id' => 1, 'is_deleted' => false], ], ]);