diff --git a/src/DataMapper/DataMapper.php b/src/DataMapper/DataMapper.php index 695b7cd6..fcbf6bb4 100644 --- a/src/DataMapper/DataMapper.php +++ b/src/DataMapper/DataMapper.php @@ -17,6 +17,7 @@ use Mongolid\Schema\Schema; use Mongolid\Util\ObjectIdUtils; use Mongolid\Connection\Connection; +Use Mongolid\Util\SoftDeleteQueries; /** * The DataMapper class will abstract how an Entity is persisted and retrieved @@ -259,13 +260,14 @@ public function where( $cursorClass = $cacheable ? SchemaCacheableCursor::class : SchemaCursor::class; $model = new $this->schema->entityClass; + $query = $this->prepareValueQuery($query); return new $cursorClass( $this->schema, $this->getCollection(), 'find', [ - $this->prepareValueQuery($query), + SoftDeleteQueries::insertFilterForSoftDelete($query, $model), [ 'projection' => $this->prepareProjection($projection), 'eagerLoads' => $model->with ?? [], @@ -302,8 +304,11 @@ public function first( return $this->where($query, $projection, true)->first(); } + $model = new $this->schema->entityClass; + $query = $this->prepareValueQuery($query); + $document = $this->getCollection()->findOne( - $this->prepareValueQuery($query), + SoftDeleteQueries::insertFilterForSoftDelete($query, $model), ['projection' => $this->prepareProjection($projection)] ); diff --git a/src/Model/SoftDeletesTrait.php b/src/Model/SoftDeletesTrait.php new file mode 100644 index 00000000..8c4a8806 --- /dev/null +++ b/src/Model/SoftDeletesTrait.php @@ -0,0 +1,42 @@ +{$this->getDeletedAtColumn()}); + } + + public function restore(): bool + { + $collumn = $this->getDeletedAtColumn(); + + if (!$this->{$collumn}) { + return false; + } + + $this->{$collumn} = null; + + return $this->execute('save'); + } + + public function forceDelete(): bool + { + $this->forceDelete = true; + + return $this->execute('delete'); + } + + private function getDeletedAtColumn(): string + { + return defined( + static::class . '::DELETED_AT' + ) + ? static::DELETED_AT + : 'deleted_at'; + } +} diff --git a/src/Util/SoftDeleteQueries.php b/src/Util/SoftDeleteQueries.php new file mode 100644 index 00000000..2365b6e9 --- /dev/null +++ b/src/Util/SoftDeleteQueries.php @@ -0,0 +1,36 @@ + [ + [ + $field => null, + ], + [ + $field => ['$exists' => false], + ], + ], + ] + ); + } + + private static function getDeletedAtColumn(ModelInterface $model): string + { + return defined( + $model::class . '::DELETED_AT' + ) + ? $model::DELETED_AT + : 'deleted_at'; + } +} diff --git a/tests/Integration/PersisteModelWithSoftDeleteTest.php b/tests/Integration/PersisteModelWithSoftDeleteTest.php new file mode 100644 index 00000000..57bc305a --- /dev/null +++ b/tests/Integration/PersisteModelWithSoftDeleteTest.php @@ -0,0 +1,99 @@ +persiteProduct(); + + // Actions + $result = ProductWithSoftDelete::where()->first(); + + // Assertion + $this->assertEquals($product, $result); + } + + public function testShouldNotFindDeletedProduct(): void + { + // Set + $this->persiteProduct(true); + + // Actions + $result = ProductWithSoftDelete::where()->first(); + + // Assertion + $this->assertNull($result); + } + + public function testShouldFindDeletedProductWithFirst(): void + { + // Set + $product = $this->persiteProduct(); + + // Actions + $result = ProductWithSoftDelete::first($this->_id); + + // Assertion + $this->assertEquals($product, $result); + } + + public function testShouldNotFindDeletedProductWithFirst(): void + { + // Set + $this->persiteProduct(true); + + // Actions + $result = ProductWithSoftDelete::first($this->_id); + + // Assertion + $this->assertNull($result); + } + + public function testShouldRestoreDeletedProduct(): void + { + // Set + $product = $this->persiteProduct(true); + + // Actions + $product->restore(); + $result = ProductWithSoftDelete::first($this->_id); + + // Assertion + $this->assertEquals($product, $result); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->_id = new ObjectId('5bcb310783a7fcdf1bf1a672'); + } + + private function persiteProduct(bool $softDeleted = false): ProductWithSoftDelete + { + $product = new ProductWithSoftDelete(); + $product->_id = $this->_id; + $product->short_name = 'Furadeira de Impacto Bosch com Chave de Mandril '; + $product->name = 'Furadeira de Impacto Bosch com Chave de Mandril e Acessórios 550W 1/2 GSB 550 RE 127V (110V)'; + + if ($softDeleted) { + $date = new UTCDateTime(new DateTime('today')); + + $product->deleted_at = $date; + } + + $product->save(); + + return $product; + } +} diff --git a/tests/Stubs/ProductWithSoftDelete.php b/tests/Stubs/ProductWithSoftDelete.php new file mode 100644 index 00000000..ceae0037 --- /dev/null +++ b/tests/Stubs/ProductWithSoftDelete.php @@ -0,0 +1,10 @@ +deleted_at = $date; + + // Actions + $actual = $product->isTrashed(); + + // Assertions + $this->assertTrue($actual); + } + + public function testShouldRestoreProduct(): void + { + // Set + $product = new ProductWithSoftDelete(); + $date = new UTCDateTime(new DateTime('today')); + $product->deleted_at = $date; + $dataMapper = $this->instance( + DataMapper::class, + m::mock(DataMapper::class) + ); + + // Expectations + $dataMapper->expects() + ->setSchema(m::type(DynamicSchema::class)); + + $dataMapper->expects() + ->save(m::type(Product::class), m::type('array')) + ->andReturnTrue(); + + // Actions + $actual = $product->restore(); + + // Assertions + $this->assertTrue($actual); + } + + public function testShouldNotRestoreProduct(): void + { + // Set + $product = new ProductWithSoftDelete(); + + // Actions + $actual = $product->restore(); + + // Assertions + $this->assertFalse($actual); + } +}