ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Yii 2 Behaviors(行为)机制完全指南:定义、挂载、事件响应与内置行为实战

Yii 2 Behaviors(行为)机制完全指南:定义、挂载、事件响应与内置行为实战 后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载Behaviors行为又称 mixin/混合是 Yii 2 框架中用于在不改动组件类继承结构的前提下向既有组件注入方法、属性并拦截其事件的核心扩展机制。本文以官方指南中 Behaviors 章节为骨架结合本仓库yi/yii2中 framework/base/Behavior.php、framework/base/Component.php 等源码实现与 tests/framework/behaviors 下的单元测试系统讲解行为类的定义方式、静态/动态挂载、事件响应、TimestampBehavior等内置行为的实战用法并剖析行为与 PHP Trait 的差异帮助读者在模型、控制器等组件上安全、高效地复用横切逻辑。什么是 BehaviorsBehaviors 是类 yii\base\Behavior 或其子类的实例也被称为 mixin混入。行为允许你在不需要修改组件类继承结构的前提下增强一个既有 yii\base\Component 类的功能将行为挂载到组件后行为的方法和属性会被注入组件使其如同在组件类中直接定义一样可访问。此外行为还能响应组件触发的 事件从而定制组件的正常代码执行流程。从源码层面看yii\base\Behavior 继承自BaseObject因此支持基于 getter/setter 的属性并持有两个核心成员public $owner行为所挂载到的组件Behavior.php#L31私有数组$_attachedEvents记录已挂载到 owner 上的事件处理器Behavior.php#L36。而注入能力真正由组件侧实现yii\base\Component 在__get()、__set()、__isset()、__unset()、__call()等魔术方法中按固定优先级把对未知属性/方法的访问委托给已挂载的行为对象。例如__get()的查找顺序是组件自身 getter → 行为属性 → 抛异常Component.php#L138-L159__call()则遍历全部行为找到第一个拥有该方法的对象并执行Component.php#L308-L317。定义 Behaviors要定义一个行为只需创建一个继承 yii\base\Behavior或其子类的类。例如namespace app\components; use yii\base\Behavior; class MyBehavior extends Behavior { public $prop1; private $_prop2; public function getProp2() { return $this-_prop2; } public function setProp2($value) { $this-_prop2 $value; } public function foo() { // ... } }上述代码定义了行为类app\components\MyBehavior包含两个属性prop1、prop2和一个方法foo()。注意prop2是通过 gettergetProp2()与 settersetProp2()定义的——因为 yii\base\Behavior 扩展自BaseObject所以支持通过 getter/setter 定义属性。当该行为挂载到组件后组件也将拥有prop1、prop2属性与foo()方法。Tip在行为内部可以通过 yii\base\Behavior::owner 属性访问它所挂载的组件。Note如果重写了行为的 yii\base\Behavior::__get() 和/或__set()方法必须同时重写canGetProperty()和/或canSetProperty()否则组件在判断属性是否存在时会与实际的魔术方法行为不一致。处理组件事件如果行为需要响应它所挂载组件的事件应重写 yii\base\Behavior::events() 方法。例如namespace app\components; use yii\db\ActiveRecord; use yii\base\Behavior; class MyBehavior extends Behavior { // ... public function events() { return [ ActiveRecord::EVENT_BEFORE_VALIDATE beforeValidate, ]; } public function beforeValidate($event) { // ... } }events()方法应返回事件名 → 事件处理器的列表。上面的例子声明EVENT_BEFORE_VALIDATE事件存在其处理器为beforeValidate()方法。指定事件处理器时可以使用以下几种格式字符串指向行为类中方法名的字符串如上例数组对象或类名 方法名字符串不带括号如[$object, methodName]匿名函数。事件处理器的签名应为function ($event) { }其中$event为被处理的事件对象。关于事件的更多细节可参考事件章节。从实现角度看事件绑定发生在 Behavior::attach() 中它遍历events()的返回结果将字符串形式的处理器转换为[$this, $handler]数组回调再调用$owner-on($event, ...)注册在 Behavior::detach() 中则通过$owner-off()逐一解绑并清空$_attachedEvents。因此事件的生命周期与行为的挂载/卸载完全同步无需手动清理。挂载 Behaviors行为可以静态或动态地挂载到组件实践中静态方式更为常见。静态挂载要静态挂载行为需在目标组件类中重写 yii\base\Component::behaviors() 方法返回一组行为配置。每条行为配置既可以是行为类名字符串也可以是配置数组namespace app\models; use yii\db\ActiveRecord; use app\components\MyBehavior; class User extends ActiveRecord { public function behaviors() { return [ // 匿名行为仅行为类名 MyBehavior::class, // 命名行为仅行为类名 myBehavior2 MyBehavior::class, // 匿名行为配置数组 [ class MyBehavior::class, prop1 value1, prop2 value2, ], // 命名行为配置数组 myBehavior4 [ class MyBehavior::class, prop1 value1, prop2 value2, ], ]; } }通过为行为配置指定数组键可以为行为关联一个名字这样的行为称为命名行为上例中的myBehavior2与myBehavior4就是命名行为。未关联名字的则称为匿名行为。命名行为之后可以通过名字被getBehavior()取回或detachBehavior()卸载匿名行为则不能Component.php#L461-L463 的注释明确说明这一点。静态挂载是按需懒加载的behaviors()中声明的行为并不会在组件实例化时立即创建而是在首次需要时由 ensureBehaviors() 统一挂载——__get、__call、getBehavior()、attachBehavior()等入口都会先调用它确保_behaviors已初始化。动态挂载调用组件的 yii\base\Component::attachBehavior() 即可动态挂载use app\components\MyBehavior; // 挂载行为对象 $component-attachBehavior(myBehavior1, new MyBehavior()); // 挂载行为类 $component-attachBehavior(myBehavior2, MyBehavior::class); // 挂载配置数组 $component-attachBehavior(myBehavior3, [ class MyBehavior::class, prop1 value1, prop2 value2, ]);使用 yii\base\Component::attachBehaviors() 可一次挂载多个行为$component-attachBehaviors([ myBehavior1 new MyBehavior(), // 命名行为 MyBehavior::class, // 匿名行为 ]);在底层所有挂载最终都汇入私有方法attachBehaviorInternal()Component.php#L781-L798非Behavior实例的配置先经Yii::createObject()创建整型键匿名行为直接追加到数组末尾字符串键命名行为若已存在同名行为会先将其卸载再以该名字挂载新行为。通过配置挂载行为也可以通过配置挂载语法为as 行为名[ as myBehavior2 MyBehavior::class, as myBehavior3 [ class MyBehavior::class, prop1 value1, prop2 value2, ], ]此机制实现在 Component::__set() 中当属性名以as开头时会将其后的名字作为行为名并根据值是Behavior实例、闭包、配置数组还是类名字符串分别通过attachBehavior()挂载同时支持on前缀用于挂载事件处理器Component.php#L187-L210。更多细节参见配置章节。使用 Behaviors行为挂载完成后使用起来非常简单。可以通过组件直接访问行为的公开成员变量或由 getter/setter 定义的属性// prop1 是定义在行为类中的属性 echo $component-prop1; $component-prop1 $value;也可以用同样的方式调用行为的公开方法// foo() 是定义在行为类中的公开方法 $component-foo();可以看到虽然$component本身没有定义prop1和foo()但由于挂载了行为它们就像组件自身定义一样可用。这正是Component::__get()与Component::__call()的委托机制在起作用。优先级规则如果两个行为定义了同名属性或方法且都挂载在同一个组件上先挂载的那个行为会优先处理对该属性或方法的访问源码中__get、__set、__call均按_behaviors数组顺序遍历并返回第一个匹配项。行为在挂载时可以关联名字此时可以通过名字取回行为对象$behavior $component-getBehavior(myBehavior);也可以获取组件挂载的全部行为列表$behaviors $component-getBehaviors();卸载 Behaviors要卸载行为调用 yii\base\Component::detachBehavior() 并传入该行为关联的名字$component-detachBehavior(myBehavior1);也可以一次卸载所有行为$component-detachBehaviors();detachBehavior()在移除_behaviors中对应条目后会调用$behavior-detach()从而自动解除事件绑定detachBehaviors()则遍历并逐个调用detachBehavior()Component.php#L752-L758。另外克隆一个组件时其_behaviors会被置空Component.php#L323-L328因为行为绑定的是旧对象。实战使用 TimestampBehavior最后来看一个最常用的内置行为 yii\behaviors\TimestampBehavior。它支持在模型通过insert()、update()或save()保存时自动更新 Active Record 模型的时间戳属性。首先把该行为挂载到目标 AR 类namespace app\models; use yii\db\ActiveRecord; use yii\behaviors\TimestampBehavior; class User extends ActiveRecord { // ... public function behaviors() { return [ [ class TimestampBehavior::class, attributes [ ActiveRecord::EVENT_BEFORE_INSERT [created_at, updated_at], ActiveRecord::EVENT_BEFORE_UPDATE [updated_at], ], // 如果使用 datetime 列而不是 UNIX 时间戳可启用 // value new Expression(NOW()), ], ]; } }上述配置的含义是新增记录时行为将当前 UNIX 时间戳赋给created_at与updated_at更新记录时行为将当前 UNIX 时间戳赋给updated_at。Note要使上述实现适用于 MySQL 数据库请将created_at、updated_at列声明为int(11)以存放 UNIX 时间戳。配置完成后保存User对象时created_at与updated_at会被自动填充当前 UNIX 时间戳$user new User; $user-email testexample.com; $user-save(); echo $user-created_at; // 显示保存时刻的时间戳TimestampBehavior 还提供了一个实用的 touch() 方法可以为指定属性赋当前时间戳并保存到数据库$user-touch(login_time);从源码看TimestampBehavior扩展自 AttributeBehavior其默认行为由 init() 定义当未显式配置attributes时默认在EVENT_BEFORE_INSERT时填充created_at与updated_at在EVENT_BEFORE_UPDATE时填充updated_at对应属性可通过createdAtAttribute、updatedAtAttribute修改设为false可禁用某项value为null时默认取time()的返回值getValue()。touch()方法要求 owner 不是新记录否则抛出InvalidCallException内部通过updateAttributes()直接写库不触发校验与事件循环。仓库中的单元测试 tests/framework/behaviors/TimestampBehaviorTest.php 验证了这些行为testNewRecord()断言新建记录后created_at/updated_at均大于等于测试起始时间testUpdateRecord()断言更新时created_at保持不变而updated_at被刷新expressionProvider数据提供器则覆盖了Expression、闭包、标量、数组回调等多种value取值形式。AttributeBehavior一切自动填充行为的基础理解 AttributeBehavior 能帮你掌握所有自动填充类行为的通用机制它通过events()把attributes中的每个事件都映射到evaluateAttributes处理器AttributeBehavior.php#L103-L109后者在事件触发时取出对应属性列表与getValue()的计算结果并赋值AttributeBehavior.php#L115-L138。它还支持skipUpdateOnClean记录无脏属性时跳过更新默认true与preserveNonEmptyValues保留非空属性值两个开关。TimestampBehavior、BlameableBehavior、SluggableBehavior都是它的子类通过重写getValue()提供各自的默认值逻辑。其他内置与第三方 Behaviors以下是仓库中内置以及常用的第三方行为yii\behaviors\BlameableBehavior —— 自动将当前用户 ID 填充到指定属性。默认在插入时填充created_by与updated_by更新时填充updated_by可通过createdByAttribute、updatedByAttribute改名或设为false禁用value为null时默认取Yii::$app-user-id并支持defaultValue处理访客场景。yii\behaviors\SluggableBehavior —— 自动将指定属性填充为可用于 URL 的 slug 值。默认在验证时EVENT_BEFORE_VALIDATE将attribute如title转为slug属性依赖 php-intl 扩展做音译未安装时回退到 Inflector::$transliteration 的替换表支持immutable生成后不再变动、ensureUnique保证唯一失败时按uniqueSlugGenerator追加递增后缀与skipOnEmpty等配置SluggableBehavior.php#L68-L130。yii\behaviors\AttributeBehavior —— 在特定事件发生时自动为 Active Record 对象的一个或多个属性赋指定值是上述行为的通用基类。yii2tech\ar\softdelete\SoftDeleteBehavior—— 提供软删除与软恢复 Active Record 的方法通过设置标记记录为已删除的标志位/状态。yii2tech\ar\position\PositionBehavior—— 通过提供重排序方法管理记录在整型字段中的顺序。注以上第三方行为需通过 Composer 单独安装如yii2tech/ar-softdelete、yii2tech/ar-position本文仅作介绍仓库内不包含其源码。Behaviors 与 Traits 的对比虽然行为与 Trait 相似——两者都会把属性与方法注入主类——但它们在许多方面存在差异各有优劣更应视为互补而非替代关系。使用 Behaviors 的优势行为类与普通类一样支持继承而 Trait 更像是语言层面的复制粘贴不支持继承行为可以在不修改组件类代码的前提下动态挂载/卸载而使用 Trait 必须修改使用它的类代码行为是可配置的通过配置数组/behaviors()Trait 不可配置行为可以通过响应组件事件来定制组件的代码执行当多个行为挂载到同一组件发生命名冲突时框架会自动解决优先先挂载的行为而不同 Trait 引起的命名冲突需要手动重命名受影响的属性或方法来处理。使用 Traits 的优势Trait效率远高于行为行为是对象需要额外的时间和内存开销IDE 对 Trait 支持更好因为它是语言原生构造。小结与阅读延伸Behaviors 是 Yii 2 组件体系属性、事件、行为三大特性中最具扩展性的部分通过 yii\base\Behavior 定义、经 Component::behaviors() 静态声明或attachBehavior()动态挂载行为可以把方法与属性注入组件并拦截事件而这一切的底层支撑是 Component.php 中精心设计的魔术方法委托与懒加载机制。掌握行为机制后你可以把时间戳、软删除、审计日志等横切关注点从模型代码中彻底剥离出来实现真正的复用。建议继续阅读仓库中的相关文档与源码基础概念组件、属性、事件、配置内置行为源码framework/behaviors 目录下的TimestampBehavior、BlameableBehavior、SluggableBehavior、AttributeBehavior核心机制源码framework/base/Behavior.php、framework/base/Component.php行为测试用例tests/framework/behaviors含TimestampBehaviorTest.php、SluggableBehaviorTest.php、BlameableBehaviorTest.php等赞分享后端Web框架【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址https://gitcode.com/gh_mirrors/yi/yii2点击查看免费下载相关推荐Telegraf v1.13 及更早版本发布记录全解从 v0.1.1 到 v1.13.4 的版本演进与升级避坑指南Telegraf v1.13 及更早版本发布记录全解从 v0.1.1 到 v1.13.4 的版本演进与升级避坑指南 本文以 Telegraf 仓库中的历史变更后端Web框架Yii 2 Behaviors 行为扩展机制完全指南从自定义 Behavior 到 TimestampBehavior 实战Yii 2 Behaviors 行为扩展机制完全指南从自定义 Behavior 到 TimestampBehavior 实战 导读 Behaviors行为后端Web框架Yii 2 行为Behavior机制全解析从属性注入到事件响应与内置行为实战Yii 2 行为Behavior机制全解析从属性注入到事件响应与内置行为实战 Yii 2 的 Behavior行为又称 mixin机制允许在不修改组后端Web框架创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表