Skip to content

Commit

Permalink
finalize find methods
Browse files Browse the repository at this point in the history
  • Loading branch information
klimov-paul committed Jul 10, 2023
1 parent d7514d1 commit f4178bc
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 11 deletions.
188 changes: 178 additions & 10 deletions src/SoftDeleteBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
* @property bool $replaceRegularDelete whether to perform soft delete instead of regular delete.
* If enabled {@see \CActiveRecord::delete()} will perform soft deletion instead of actual record deleting.
* @property bool $useRestoreAttributeValuesAsDefaults whether to use {@see restoreAttributeValues} as defaults on record insertion.
* @property array $deletedCondition filter condition for 'soft-deleted' records.
* @property array $notDeletedCondition filter condition for not 'soft-deleted' records.
* @property bool $autoApplyNotDeletedCondition whether to automatically apply {@see notDeletedCondition} before find.
*
* @author Paul Klimov <[email protected]>
* @since 1.0
Expand Down Expand Up @@ -118,6 +121,22 @@ class SoftDeleteBehavior extends CBehavior
* @var bool whether to use {@see restoreAttributeValues} as defaults on record insertion.
*/
private $_useRestoreAttributeValuesAsDefaults = false;
/**
* @var array filter condition for 'soft-deleted' records.
*/
private $_deletedCondition;
/**
* @var array filter condition for not 'soft-deleted' records.
*/
private $_notDeletedCondition;
/**
* @var bool whether to automatically apply {@see notDeletedCondition} before find.
*/
private $_autoApplyNotDeletedCondition = false;
/**
* @var bool indicates whether "soft-delete" related condition has been already applied or not.
*/
private $isSoftDeleteConditionApplied = false;

/**
* @return bool whether to perform soft delete instead of regular delete.
Expand Down Expand Up @@ -175,6 +194,84 @@ public function setUseRestoreAttributeValuesAsDefaults(bool $useRestoreAttribute
return $this->owner;
}

/**
* @return array filter condition for 'soft-deleted' records.
*/
public function getDeletedCondition()
{
if ($this->_deletedCondition === null) {
$this->_deletedCondition = $this->defaultDeletedCondition();
}

return $this->_deletedCondition;
}

/**
* @param array $deletedCondition filter condition for 'soft-deleted' records.
* @return \CActiveRecord|null owner record.
*/
public function setDeletedCondition($deletedCondition)
{
$this->_deletedCondition = $deletedCondition;

return $this->owner;
}

/**
* @return array filter condition for not 'soft-deleted' records.
*/
public function getNotDeletedCondition()
{
if ($this->_notDeletedCondition === null) {
$this->_notDeletedCondition = $this->defaultNotDeletedCondition();
}

return $this->_notDeletedCondition;
}

/**
* @param array $notDeletedCondition filter condition for not 'soft-deleted' records.
* @return \CActiveRecord|null owner record.
*/
public function setNotDeletedCondition($notDeletedCondition)
{
$this->_notDeletedCondition = $notDeletedCondition;

return $this->owner;
}

/**
* @return bool whether to automatically apply {@see notDeletedCondition} before find.
*/
public function getAutoApplyNotDeletedCondition(): bool
{
return $this->_autoApplyNotDeletedCondition;
}

/**
* @param bool $autoApplyNotDeletedCondition whether to automatically apply {@see notDeletedCondition} before find.
* @return \CActiveRecord|null owner record.
*/
public function setAutoApplyNotDeletedCondition(bool $autoApplyNotDeletedCondition)
{
$isEnabled = is_object($this->owner) && $this->getEnabled();
if ($isEnabled) {
$this->setEnabled(false);
}

$this->_autoApplyNotDeletedCondition = $autoApplyNotDeletedCondition;

$this->isSoftDeleteConditionApplied = false;

if ($isEnabled) {
$this->setEnabled(true);
}

return $this->owner;
}

// Soft Delete :

/**
* Marks the owner as deleted.
*
Expand Down Expand Up @@ -426,9 +523,14 @@ public function safeDelete()

// Query:

public function createDeletedFindCondition()
/**
* Creates search criteria for fetching "soft-deleted" records.
*
* @return \CDbCriteria search criteria instance.
*/
protected function createDeletedFindCriteria()
{
foreach ($this->softDeleteAttributeValues as $attribute => $value) {
foreach ($this->getDeletedCondition() as $attribute => $value) {
if (!is_scalar($value) && is_callable($value)) {
$value = call_user_func($value, $this->owner);
}
Expand All @@ -441,9 +543,14 @@ public function createDeletedFindCondition()
return $criteria;
}

public function createNotDeletedFindCondition()
/**
* Creates search criteria for fetching NOT "soft-deleted" records.
*
* @return \CDbCriteria search criteria instance.
*/
protected function createNotDeletedFindCriteria()
{
foreach ($this->detectRestoreAttributeValues() as $attribute => $value) {
foreach ($this->getNotDeletedCondition() as $attribute => $value) {
if (!is_scalar($value) && is_callable($value)) {
$value = call_user_func($value, $this->owner);
}
Expand All @@ -456,14 +563,65 @@ public function createNotDeletedFindCondition()
return $criteria;
}

/**
* Generates default filter condition for 'deleted' records.
* @see deletedCondition
*
* @return array filter condition.
*/
protected function defaultDeletedCondition()
{
$condition = [];
foreach ($this->softDeleteAttributeValues as $attribute => $value) {
if (!is_scalar($value) && is_callable($value)) {
$value = call_user_func($value, $this);
}
$condition[$attribute] = $value;
}

return $condition;
}

/**
* Generates default filter condition for not 'deleted' records.
* @see notDeletedCondition
*
* @return array filter condition.
* @throws \LogicException on invalid configuration.
*/
protected function defaultNotDeletedCondition()
{
$condition = [];

foreach ($this->detectRestoreAttributeValues() as $attribute => $value) {
if (is_bool($value)) {
$restoreValue = $value;
} elseif (is_int($value)) {
$restoreValue = $value;
} elseif (!is_scalar($value) && is_callable($value)) {
$restoreValue = call_user_func($value, $this);
} elseif ($value instanceof CDbExpression) {
$restoreValue = $value;
} else {
throw new LogicException('Unable to automatically determine not delete condition, "' . get_class($this) . '::$notDeletedCondition" should be explicitly set.');
}

$condition[$attribute] = $restoreValue;
}

return $condition;
}

/**
* Filters query to return only 'soft-deleted' records.
*
* @return \CActiveRecord|static query instance.
*/
public function deleted()
{
$this->owner->getDbCriteria()->mergeWith($this->createDeletedFindCondition());
$this->owner->getDbCriteria()->mergeWith($this->createDeletedFindCriteria());

$this->isSoftDeleteConditionApplied = true;

return $this->owner;
}
Expand All @@ -475,7 +633,9 @@ public function deleted()
*/
public function notDeleted()
{
$this->owner->getDbCriteria()->mergeWith($this->createNotDeletedFindCondition());
$this->owner->getDbCriteria()->mergeWith($this->createNotDeletedFindCriteria());

$this->isSoftDeleteConditionApplied = true;

return $this->owner;
}
Expand All @@ -499,6 +659,8 @@ public function filterDeleted($deleted)
return $this->deleted();
}

$this->isSoftDeleteConditionApplied = true;

return $this->owner;
}

Expand All @@ -509,9 +671,7 @@ public function filterDeleted($deleted)
*/
public function events(): array
{
$events = [
'onBeforeFind' => 'beforeFind',
];
$events = [];

if ($this->getReplaceRegularDelete()) {
$events['onBeforeDelete'] = 'beforeDelete';
Expand All @@ -521,6 +681,10 @@ public function events(): array
$events['onBeforeSave'] = 'beforeSave';
}

if ($this->getAutoApplyNotDeletedCondition()) {
$events['onBeforeFind'] = 'beforeFind';
}

return $events;
}

Expand Down Expand Up @@ -568,6 +732,10 @@ public function beforeSave(CModelEvent $event): void
*/
public function beforeFind(CModelEvent $event): void
{
;
if (!$this->isSoftDeleteConditionApplied) {
$this->notDeleted();
}

$this->isSoftDeleteConditionApplied = false;
}
}
59 changes: 58 additions & 1 deletion tests/SoftDeleteBehaviorFindTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
namespace yii1tech\ar\softdelete\test;

use yii1tech\ar\softdelete\SoftDeleteBehavior;
use yii1tech\ar\softdelete\test\data\Category;
use yii1tech\ar\softdelete\test\data\Item;

class SoftDeleteBehaviorFindTest extends TestCase
{
public function testFindCriteria()
public function testFindCriteria(): void
{
/** @var Item|SoftDeleteBehavior $query */
$query = Item::model();
Expand All @@ -26,4 +27,60 @@ public function testFindCriteria()
$this->assertNotEquals($allCount, $deletedCount);
$this->assertNotEquals($allCount, $notDeletedCount);
}

/**
* Data provider for {@see testFilterDeleted()}
*
* @return array test data.
*/
public static function dataProviderFilterDeleted(): array
{
return [
['', 2],
[null, 2],
['1', 1],
[true, 1],
['0', 3],
[false, 3],
['all', 3],
];
}

/**
* @dataProvider dataProviderFilterDeleted
*
* @param mixed $filterDeleted
* @param int $expectedCount
*/
public function testFilterDeleted($filterDeleted, $expectedCount): void
{
Category::model()
->findByPk(2)
->softDelete();

$this->assertCount($expectedCount, Category::model()->filterDeleted($filterDeleted)->findAll());
}

public function testAutoApplyNotDeletedCondition(): void
{
Category::model()
->findByPk(2)
->softDelete();

/** @var Category|SoftDeleteBehavior $query */
$query = Category::model();
$query->autoApplyNotDeletedCondition = true;

$this->assertCount(2, $query->findAll());

/** @var Category|SoftDeleteBehavior $query */
$query = Category::model();
$query->autoApplyNotDeletedCondition = true;
$this->assertCount(1, $query->deleted()->findAll());

/** @var Category|SoftDeleteBehavior $query */
$query = Category::model();
$query->autoApplyNotDeletedCondition = true;
$this->assertCount(3, $query->filterDeleted('all')->findAll());
}
}

0 comments on commit f4178bc

Please sign in to comment.