yii1tech/ar-softdelete

为 Yii1 提供了 ActiveRecord 软删除支持

1.0.0 2023-07-11 07:58 UTC

This package is auto-updated.

Last update: 2024-09-11 10:33:07 UTC


README

Yii 1 应用运行时配置扩展


本扩展为 Yii1 ActiveRecord 软删除提供了支持。

有关许可信息,请查看 LICENSE 文件。

Latest Stable Version Total Downloads Build Status

安装

安装此扩展的首选方式是通过 composer

运行以下命令:

php composer.phar require --prefer-dist yii1tech/ar-softdelete

"yii1tech/ar-softdelete": "*"

将以下内容添加到您的 composer.json 文件的 "require" 部分:

用法

此扩展为所谓的“软删除”ActiveRecord提供了支持,这意味着记录不会从数据库中删除,而是通过某种标志或状态标记为不再活跃。

此扩展为 Yii1 中的此类解决方案支持提供了 \yii1tech\ar\softdelete\SoftDeleteBehavior ActiveRecord 行为。您可以通过以下方式将其附加到模型类中

<?php

use CActiveRecord;
use yii1tech\ar\softdelete\SoftDeleteBehavior;

class Item extends CActiveRecord
{
    public function behaviors()
    {
        return [
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::class,
                'softDeleteAttributeValues' => [
                    'is_deleted' => true,
                ],
            ],
        ];
    }
}

有 2 种应用“软删除”的方式

  • 使用 softDelete() 分离的方法
  • 修改常规的 delete() 方法

推荐使用 softDelete(),因为它允许将记录标记为“已删除”,同时保持常规 delete() 方法完整,这样在需要时可以进行“硬删除”。例如

<?php

$id = 17;
$item = Item::model()->findByPk($id);
$item->softDelete(); // mark record as "deleted"

$item = Item::model()->findByPk($id);
var_dump($item->is_deleted); // outputs "true"

$item->delete(); // perform actual deleting of the record
$item = Item::model()->findByPk($id);
var_dump($item); // outputs "null"

但是,您可能希望以执行“软删除”而不是实际删除记录的方式修改常规 ActiveRecord delete() 方法。这在将“软删除”功能应用于现有代码的情况下是一个常见解决方案。为此功能,您应在行为配置中启用 \yii1tech\ar\softdelete\SoftDeleteBehavior::$replaceRegularDelete 选项

<?php

use CActiveRecord;
use yii1tech\ar\softdelete\SoftDeleteBehavior;

class Item extends CActiveRecord
{
    public function behaviors()
    {
        return [
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::class,
                'softDeleteAttributeValues' => [
                    'is_deleted' => true
                ],
                'replaceRegularDelete' => true // mutate native `delete()` method
            ],
        ];
    }
}

现在调用 delete() 方法将记录标记为“已删除”,而不是删除它

<?php

$id = 17;
$item = Item::model()->findByPk($id);
$item->delete(); // no record removal, mark record as "deleted" instead

$item = Item::model()->findByPk($id);
var_dump($item->is_deleted); // outputs "true"

查询“软删除”记录

显然,为了只找到“已删除”或只找到“活动”记录,您应在搜索查询中添加相应的条件

<?php

// returns only not "deleted" records
$notDeletedItems = Item::model()
    ->findAll('is_deleted = 0');

// returns "deleted" records
$deletedItems = Item::model()
    ->findAll('is_deleted = 1');

然而,您可以使用 \yii1tech\ar\softdelete\SoftDeleteQueryBehavior 来简化此类查询的构建。一旦附加,它就提供了类似于作用域的记录过滤方法。例如

<?php

// Find all "deleted" records:
$deletedItems = Item::model()->deleted()->findAll();

// Find all "active" records:
$notDeletedItems = Item::model()->notDeleted()->findAll();

您可以使用 filterDeleted() 方法轻松创建用于“已删除”记录的列表过滤器

<?php

// Filter records by "soft" deleted criteria:
$items = Item::model()
    ->filterDeleted(Yii::app()->request->getParam('filter_deleted'))
    ->findAll();

此方法在空过滤器值上应用 notDeleted() 作用域,在正过滤器值上应用 deleted() - 作用域,在负(零)值上不应用作用域(例如,显示“已删除”和“活动”记录)。

注意:`yii1tech\ar\softdelete\SoftDeleteQueryBehavior` 已设计为正确处理连接并避免模糊列错误,然而,仍有可能无法正确处理某些情况。如果您的查询涉及多个具有“软删除”功能的表,请准备好手动指定“软删除”条件。

默认情况下,yii1tech\ar\softdelete\SoftDeleteQueryBehavior 使用来自 yii1tech\ar\softdelete\SoftDeleteBehavior::$softDeleteAttributeValues 的信息来构建其作用域的筛选标准。因此,如果您正在使用用于标记“软删除”记录的复杂逻辑,则可能需要手动配置筛选条件。例如

<?php

use CActiveRecord;
use yii1tech\ar\softdelete\SoftDeleteBehavior;

class Item extends CActiveRecord
{
    public function behaviors()
    {
        return [
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::class,
                'softDeleteAttributeValues' => [
                    'statusId' => 'deleted',
                ],
                'deletedCondition' => [
                    'statusId' => 'deleted',
                ],
                'notDeletedCondition' => [
                    'statusId' => 'active',
                ],
            ],
        ];
    }
    
    // ...
}

提示:您可以将筛选“未删除”记录的条件应用于搜索查询作为默认值,启用 yii1tech\ar\softdelete\SoftDeleteBehavior::$autoApplyNotDeletedCondition

<?php

use CActiveRecord;
use yii1tech\ar\softdelete\SoftDeleteBehavior;

class Item extends CActiveRecord
{
    public function behaviors()
    {
        return [
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::class,
                'softDeleteAttributeValues' => [
                    'is_deleted' => true,
                ],
                'autoApplyNotDeletedCondition' => true,
            ],
        ];
    }

    // ...
}

$notDeletedItems = Item::model()->findAll(); // returns only not "deleted" records

$allItems = Item::find()
    ->deleted() // applies "deleted" condition, preventing default one
    ->findAll(); // returns "deleted" records

$allItems = Item::find()
    ->filterDeleted('all') // filter all records, preventing default "not deleted" condition
    ->all(); // returns all records

智能删除

通常,“软删除”功能用于防止数据库历史记录丢失,确保正在使用的数据以及可能具有引用或依赖的数据保留在系统中。然而,有时也允许实际删除此类数据。例如:通常不应删除用户账户记录,而应仅将其标记为“不活跃”,但如果您浏览用户列表并发现很久以前注册的账户,但没有至少一次登录系统,这些记录对历史没有任何价值,可以将其从数据库中删除以节省磁盘空间。

您可以使“软删除”变得更“智能”,以检测记录是否可以从数据库中删除或仅标记为“已删除”。这可以通过\yii1tech\ar\softdelete\SoftDeleteBehavior::$allowDeleteCallback实现。例如

<?php
 
use CActiveRecord;
use yii1tech\ar\softdelete\SoftDeleteBehavior;

class User extends CActiveRecord
{
    public function behaviors()
    {
        return [
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::class,
                'softDeleteAttributeValues' => [
                    'is_deleted' => true
                ],
                'allowDeleteCallback' => function ($user) {
                    return $user->last_login_date === null; // allow to delete user, if he has never logged in
                }
            ],
        ];
    }
}

$user = User::model()->find('last_login_date IS NULL');
$user->softDelete(); // removes the record!!!

$user = User::find()->find('last_login_date IS NOT NULL');
$user->softDelete(); // marks record as "deleted"

\yii1tech\ar\softdelete\SoftDeleteBehavior::$allowDeleteCallback逻辑在\yii1tech\ar\softdelete\SoftDeleteBehavior::$replaceRegularDelete也启用时应用。

处理外键约束

在关系型数据库的用法中,例如MySQL、PostgreSQL等,通常使用“软删除”来保持外键一致性。例如:如果用户在在线商店进行购买,则此购买信息应保留在系统中以供未来记账。此类数据结构的DDL可能如下所示

CREATE TABLE `customer`
(
   `id` integer NOT NULL AUTO_INCREMENT,
   `name` varchar(64) NOT NULL,
   `address` varchar(64) NOT NULL,
   `phone` varchar(20) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE InnoDB;

CREATE TABLE `purchase`
(
   `id` integer NOT NULL AUTO_INCREMENT,
   `customer_id` integer NOT NULL,
   `item_id` integer NOT NULL,
   `amount` integer NOT NULL,
    PRIMARY KEY (`id`)
    FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
    FOREIGN KEY (`item_id`) REFERENCES `item` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
) ENGINE InnoDB;

因此,当从'purchase'到'user'设置外键时,使用'ON DELETE RESTRICT'模式。因此,尝试删除至少有一笔购买的记录时,将发生数据库错误。然而,如果用户记录没有外部引用,则可以将其删除。

对于这种情况,使用\yii1tech\ar\softdelete\SoftDeleteBehavior::$allowDeleteCallback并不是非常实用。它将需要执行额外的查询以确定是否存在外部引用,从而消除外键数据库功能的优势。

方法\yii1tech\ar\softdelete\SoftDeleteBehavior::safeDelete()尝试调用常规的CBaseActiveRecord::delete()方法,如果它因异常失败,则回退到yii1tech\ar\softdelete\SoftDeleteBehavior::softDelete()

<?php

// if there is a foreign key reference :
$customer = Customer::model()->findByPk(15);
var_dump(count($customer->purchases)); // outputs; "1"
$customer->safeDelete(); // performs "soft" delete!
var_dump($customer->isDeleted) // outputs: "true"

// if there is NO foreign key reference :
$customer = Customer::model()->findByPk(53);
var_dump(count($customer->purchases)); // outputs; "0"
$customer->safeDelete(); // performs actual delete!
$customer = Customer::model()->findByPk(53);
var_dump($customer); // outputs: "null"

默认情况下,safeDelete()方法捕获\CDbException异常,这意味着在违反外键约束的数据库异常时执行软删除。您可以在此处指定另一个异常类以自定义回退错误级别。例如:使用\Throwable将导致在常规删除过程中遇到任何错误时回退到软删除。

记录恢复

在某个时候,您可能想要“恢复”以前标记为“已删除”的记录。您可以使用restore()方法进行此操作

<?php

$id = 17;
$item = Item::model()->findByPk($id);
$item->softDelete(); // mark record as "deleted"

$item = Item::model()->findByPk($id);
$item->restore(); // restore record
var_dump($item->is_deleted); // outputs "false"

默认情况下,用于记录恢复的属性值会自动从\yii1tech\ar\softdelete\SoftDeleteBehavior::$softDeleteAttributeValues中检测,但最好您通过\yii1tech\ar\softdelete\SoftDeleteBehavior::$restoreAttributeValues明确指定它们。

提示:如果您启用\yii1tech\ar\softdelete\SoftDeleteBehavior::$useRestoreAttributeValuesAsDefaults,则标记已恢复记录的属性值将在新记录插入时自动应用。

事件

默认情况下,\yii1tech\ar\softdelete\SoftDeleteBehavior::softDelete()以与常规delete()相同的方式触发\CActiveRecord::onBeforeDelete\CActiveRecord::onAfterDelete事件。

此外,\yii1tech\ar\softdelete\SoftDeleteBehavior允许您在所有者ActiveRecord类中定义特定方法,以挂钩到软删除过程

  • beforeSoftDelete() - 在“软删除”之前触发。
  • afterSoftDelete() - 在“软删除”之后触发。
  • beforeRestore() - 在从“已删除”状态恢复记录之前触发。
  • afterRestore() - 在从“已删除”状态恢复记录之后触发。

例如

<?php

use CActiveRecord;
use yii1tech\ar\softdelete\SoftDeleteBehavior;

class Item extends CActiveRecord
{
    public function behaviors()
    {
        return [
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::class,
                // ...
            ],
        ];
    }

    public function beforeSoftDelete(): bool
    {
        $this->deleted_at = time(); // log the deletion date
        
        return true;
    }

    public function beforeRestore(): bool
    {
        return $this->deleted_at > (time() - 3600); // allow restoration only for the records, being deleted during last hour
    }
}