Yii框架虽内置高内聚低耦合基础,但默认结构易致Controller膨胀、Model职责过载、Service缺失;落地关键在于清晰划界与依赖关系设计,如用yii\rest\Controller替代ActiveController、拆分场景化模型、构造函数注入依赖、避免Module::init()滥用。

Yii 框架本身已内置高内聚、低耦合的组件架构基础,但直接用默认结构写业务,很快就会陷入 Controller 膨胀、Model 承担太多职责、Service 层缺失的泥潭。真正落地的关键不是“能不能”,而是“怎么划界”和“谁该依赖谁”。
yii\rest\ActiveController 直接继承会破坏业务隔离
很多项目一上来就让 UserController 继承 yii\rest\ActiveController,绑死 $modelClass,看似省事,实则把数据访问逻辑、权限校验、字段过滤、状态转换全塞进一个类里。
- 它无法处理“用户列表需关联角色名 + 部门路径”的复合查询,强行在
findModel()里加joinWith()会让模型层污染 - 所有接口共用同一套
behaviors(),但「导出用户」需要额外鉴权和限流,「获取当前用户」却要跳过某些中间件——这时行为配置就变成条件判断堆砌 -
ActiveController的prepareDataProvider()默认返回ActiveDataProvider,一旦换成 Elasticsearch 或缓存聚合结果,就得重写整个动作逻辑
正确做法是:用 yii\rest\Controller 作基类,手动调用 Service 层,让控制器只做「参数接收 → 调用服务 → 组装响应」三件事。
common/models/ 下放 ActiveRecord 是陷阱
把 User、Order 这类 ActiveRecord 放在 common/models/ 目录,本意是共享,但实际导致:
- 前端 API 需要输出
user_name字段,后台管理却要显示full_name(含称谓),而User::attributes()只能返回一套字段定义 -
save()触发的beforeSave逻辑,对「注册」和「后台导入」场景要做不同处理,最终变成if ($this->scenario === 'import') { ... }满天飞 - 数据库字段变更(如
email拆成email_primary和email_backup)会迫使所有模块同步改模型、改验证规则、改视图
应拆分为:common/models/User.php 仅保留核心字段与主键约束;具体业务场景下,在 api/models/UserForm.php 或 backend/forms/UserImportForm.php 中继承并扩展验证、属性映射与保存逻辑。
Service 层必须显式声明依赖,禁用 Yii::$app->get()
常见错误是 Service 类里直接写 Yii::$app->cache->get('key') 或 Yii::$app->mailer->compose(),这等于把运行时容器当全局变量用,带来三个硬伤:
- 单元测试无法 mock 依赖,只能走真实缓存/邮件发送,CI 构建失败率飙升
- 某天要把 Redis 缓存换成 APCu,得 grep 全项目改几十个
Yii::$app->cache - Service 类职责模糊——它到底是业务编排者,还是缓存使用者?
正确方式是构造函数注入:
class UserService
{
public function __construct(
private UserRepository $userRepository,
private CacheInterface $cache,
private MailerInterface $mailer
) {}
}
再通过 DI 容器绑定具体实现,例如在 config/web.php 中配 'cache' => ['class' => yii\caching\RedisCache::class]。这样替换缓存驱动只需改配置,不碰业务代码。
Module 的 init() 不是初始化业务逻辑的地方
很多人在 Module::init() 里写日志埋点、加载配置、初始化第三方 SDK 实例,这会导致:
- 模块未被路由命中时,这些代码也执行了,浪费资源
-
init()在应用启动阶段调用,此时Yii::$app->user尚未登录,拿不到当前用户 ID,却硬要记录「模块加载耗时 by user」 - 多个模块都调
init(),相互之间无序执行,A 模块依赖 B 模块的某个静态状态,但 B 的init()还没跑
模块级初始化应下沉到具体 Controller 或 Service 的首次调用中,用 static $initialized = false; + if (!$initialized) { ...; $initialized = true; } 控制。或者更干净地,把模块配置移入 params.php,由使用方按需读取。
真正的高内聚,不是把一堆方法塞进一个类,而是让每个类只回答一个问题:“你存在的唯一理由是什么?”;真正的低耦合,也不是删掉所有 new,而是让依赖关系可追踪、可替换、不隐式泄漏。Yii 的组件机制很强大,但它的力量只在边界清晰时才真正生效。


















