必须用Behavior机制实现动态能力扩展,它通过事件系统绑定逻辑,要求目标类实现ComponentInterface;行为需继承Behavior并重写events(),绑定后键名不可重复,解除时需手动清理资源。

要在Yii3.0中为控制器或模型动态添加权限检查、日志记录、数据转换等能力,又不改动原有类定义,必须用Behavior机制绑定事件响应逻辑——它不是装饰器,也不是Trait,而是对象生命周期与事件系统的桥梁。
确认目标类继承自ComponentInterface
行为只能绑定到实现yii\base\ComponentInterface的类上。Yii3.0中绝大多数核心类(如yii\web\Controller、yii\db\ActiveRecord)已默认实现该接口,但自定义类必须显式声明:
class MyController extends yii\web\Controller implements yii\base\ComponentInterface
若未实现,调用attachBehavior()会抛出InvalidArgumentException。这不是警告,是运行时致命错误。
定义一个可响应事件的行为类
方法一:继承yii\base\Behavior并重写events()
在src/behaviors/LogBeforeSaveBehavior.php中创建:
namespace App\Behaviors;
use yii\base\Behavior;
use yii\db\ActiveRecord;
class LogBeforeSaveBehavior extends Behavior
{
public function events(): array
{
return [
ActiveRecord::EVENT_BEFORE_INSERT => 'logInsert',
ActiveRecord::EVENT_BEFORE_UPDATE => 'logUpdate',
];
}
public function logInsert(): void
{
$this->owner->logger->info('Record about to be inserted: {id}', ['id' => $this->owner->id ?? 'new']);
}
public function logUpdate(): void
{
$this->owner->logger->info('Record about to be updated: {id}', ['id' => $this->owner->id]);
}
}
注意:【$this->owner必须是Component实例,且已注入logger依赖】。Yii3.0中不再自动提供$this->owner->logger,需在构造函数中接收或由容器注入。
将行为动态绑定到模型实例
第一步:获取模型实例
$user = new User();
第二步:实例化行为并绑定
$behavior = new \App\Behaviors\LogBeforeSaveBehavior();
$user->attachBehavior('logBeforeSave', $behavior);
第三步:触发事件验证绑定生效
$user->name = 'test';
$user->save(); // 此时logInsert会被调用
行为绑定后不可重复使用同一键名;若再次用'logBeforeSave'绑定新行为,旧行为会被覆盖且detach()不会自动调用——内存泄漏风险由此产生。
在控制器中绑定行为并监听请求事件
Yii3.0控制器不再支持beforeAction()钩子,但可通过行为监听PSR-15中间件流程中的事件(需配合自定义中间件或事件总线)。最直接方式是监听yii\web\Application::EVENT_BEFORE_REQUEST(需确保应用层已启用事件分发):
方法一:在config/web.php中配置全局行为
'components' => [
'application' => [
'class' => yii\web\Application::class,
'behaviors' => [
'authCheck' => [\App\Behaviors\AuthCheckBehavior::class],
],
],
],
方法二:在控制器构造函数中动态绑定(仅对当前控制器生效)
public function __construct(private LoggerInterface $logger)
{
parent::__construct();
$this->attachBehavior('auth', new AuthCheckBehavior($this->logger));
}
行为类构造参数必须与容器注册一致;若AuthCheckBehavior构造函数要求LoggerInterface,而容器未注册该接口,则抛出InvalidConfigException。
解除行为并清理资源
① 调用detachBehavior()传入键名
$user->detachBehavior('logBeforeSave');
② 行为内部若有打开的文件句柄、数据库连接或定时器,应在detach()方法中显式释放
public function detach(): void
{
if ($this->fileHandle) {
fclose($this->fileHandle);
$this->fileHandle = null;
}
}
③ 若行为已监听事件,detach()会自动取消所有事件订阅——这是框架保障,无需手动off()。


















