yii控制器不应写通用增删改查方法,因其本质是请求调度层;应将复用逻辑下沉至activerecord模型层或服务层,通过模型静态方法、基控制器流程模板等方式实现轻量复用。

为什么不能直接在控制器里写通用增删改查方法
Yii 的控制器(Controller)本质是请求调度层,不是数据操作层。硬塞一个「通用增删改查方法」进去,会立刻遇到几个现实问题:
— 模型类名、主键字段、验证规则、返回格式、权限控制点全都不固定;
— save() 失败时没统一错误处理,delete() 被误调用无日志无钩子;
— 分页参数、排序字段、软删除标记(如 status)等业务差异无法抽象。
所以,所谓「封装」,不是写个 commonAction() 一把梭,而是把可复用逻辑下沉到合适位置。
真正该封装的地方:ActiveRecord 模型层
Yii2+ 推荐把基础增删改查能力收口到模型(ActiveRecord)里,而不是控制器。这样既复用又可控:
-
find()、findOne()、save()、delete()本身已是模型方法,无需重写 - 可在模型中加静态方法封装常用场景,例如:
public static function safeDelete($id) { $model = self::findOne($id); if (!$model || $model->status == 0) { return false; } $model->status = 0; // 软删除 return $model->save(); } - 批量操作、条件更新等也可封装为静态方法:
updateAllByCondition()、batchInsertWithTimestamp() - 所有验证、事件(
beforeSave、afterDelete)天然生效,控制器调用时不用重复处理
控制器里怎么“轻量复用”而不是“强行通用”
如果多个控制器都要做类似列表+详情+删改,可以这样组织,避免复制粘贴:
- 继承一个基控制器(如
CrudController),在里面定义actionIndex()、actionView()等骨架,但不实现具体模型逻辑 - 通过属性声明模型类:
public $modelClass = 'app\models\User';,然后在方法里动态实例化:$model = new $this->modelClass(); - 关键点:不封装「执行动作」,只封装「流程模板」——比如分页逻辑、
load()+save()的标准判断、404 处理 - 必须覆盖的方法留空或抛异常:
protected function getModelClass() { throw new NotSupportedException(); }
注意:ActiveController 已经提供了这个思路,但默认禁用了 index/view 等 action,你得手动放开并补全权限和状态过滤逻辑。
最容易被忽略的坑:事务、软删除和关联清理
封装时最常漏掉的不是语法,而是业务语义:
- 删除用户同时要删其订单?不能只调
$user->delete(),得在模型的afterDelete()里显式处理关联 - 批量启用/禁用状态,用
updateAll()是快,但它绕过模型事件和验证——如果某条记录需要触发通知,就失效了 - 软删除字段名不统一(
is_deleted/status/deleted_at),在封装查询方法时必须允许传参指定,不能写死 - 事务只包住本模型操作没用,比如「新增文章 + 同步到搜索库」,得在服务层(非控制器、非模型)起事务
控制器永远只是胶水,真正需要封装的逻辑,不在它身上,而在模型和服务层里。强行往控制器里塞“通用方法”,最后只会变成一堆带 if ($model === 'User') 的分支判断。











