CodeIgniter需通过MY_Model基类封装实现软删除,核心是用deleted_at字段替代物理删除并自动过滤查询;直接硬编码where条件会导致重复、泄露和开关失控,必须统一下沉至基类,重写get/delete等方法,并注意count_all、join等边界兼容。

CodeIgniter 本身不提供软删除的原生支持,但可以通过自定义类库 + 模型层封装实现统一、可复用的软删除逻辑。关键在于:用 deleted_at 字段替代物理删除,并让所有查询/更新操作自动感知该字段状态。
为什么不能直接在模型里写死 soft_delete 逻辑
硬编码 where('deleted_at IS NULL') 到每个模型方法里,会导致三类问题:重复代码难维护、漏加条件引发数据泄露、无法统一控制软删除开关(比如某些场景需查已删数据)。真正可行的方式是把判断逻辑下沉到可复用的封装层。
- 模型方法中手动加
where条件,每次增删改查都要写一遍,改字段名时要全局搜索替换 - 忘记加条件的查询会暴露“已删除”记录,尤其在 API 或后台列表页中极易造成权限越界
-
get()、update()、delete()等原生方法无法自动过滤,必须重写或代理调用
用 MY_Model 封装软删除行为(推荐方案)
继承 CI_Model 创建基类 MY_Model,放在 application/core/MY_Model.php,并在构造函数中检查是否存在 deleted_at 字段。这样所有继承它的模型自动获得软删除能力。
- 类名必须以
MY_开头(需确认$config['subclass_prefix'] = 'MY_';已设在config.php中) - 在
__construct()中调用$this->set_soft_delete_fields(),自动检测表结构是否含deleted_at - 重写
get()、get_where()、get_all()等读取方法,在内部自动附加where('deleted_at IS NULL') - 重写
delete()方法:不执行物理删除,而是update(['deleted_at' => date('Y-m-d H:i:s')])
示例片段:
class MY_Model extends CI_Model
{
protected $soft_delete_field = 'deleted_at';
public function __construct()
{
parent::__construct();
$this->set_soft_delete_fields();
}
protected function set_soft_delete_fields()
{
$table = $this->db->table_exists($this->table) ? $this->table : '';
if ($table && $this->db->field_exists($this->soft_delete_field, $table)) {
$this->soft_delete_enabled = TRUE;
}
}
public function delete($id = NULL)
{
if (!$this->soft_delete_enabled) {
return parent::delete($id);
}
$data = [$this->soft_delete_field => date('Y-m-d H:i:s')];
return $this->update($data, $id);
}
}
如何让现有模型启用软删除
只需让模型继承 MY_Model,并确保对应数据表包含 deleted_at DATETIME NULL 字段。无需额外配置,开箱即用。
- 模型类首行改为
class User_model extends MY_Model(不是CI_Model) - 数据库迁移或建表时,必须显式添加
deleted_at字段,类型为DATETIME且允许为NULL - 若某张表不需要软删除,可在模型中设置
$this->soft_delete_enabled = FALSE关闭 - 需要临时查已删除数据?调用
$this->disable_soft_delete()(需在MY_Model中实现该方法)
容易忽略的兼容性细节
软删除不是“一加就灵”,几个边界情况常被跳过:
-
count_all()不走get()流程,不会自动加WHERE条件,必须重写为count_all_where([$this->soft_delete_field => NULL]) - 使用
join()查询关联表时,主表过滤了,但关联表没过滤 —— 需在join后手动补where或用子查询 -
insert()不受影响,但建议在模型的before_insert()回调中清空deleted_at字段,避免误插入已删状态 - 事务中执行
delete()实际是update,要注意影响行数返回值语义变化(不再是 1 表示删除成功)
软删除真正的复杂点不在实现,而在一致性 —— 所有读、写、统计、关联路径都得对 deleted_at 敏感。一旦漏掉一个入口,数据就“活”在不该出现的地方。

















