行为

未匹配的标注

行为

行为是 yii\base\Behavior 或其子类的实例。行为,也称为 mixins,可以无须改变类继承关系即可增强一个已有的 yii\base\Component 组件类功能。当行为附加到组件后,它将“注入”它的方法和属性到组件,然后可以像访问组件内定义的方法和属性一样访问它们。此外,行为通过组件能响应被触发的 事件,从而自定义或调整组件正常执行的代码。

定义行为

要定义行为,通过继承 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 并为要附加行为的组件提供了两个属性 prop1prop2 和一个方法 foo() 。注意属性 prop2 是通过 getter getProp2() 和 setter setProp2() 定义的。能这样用是因为 yii\base\Objectyii\base\Behavior 的祖先类,此祖先类支持用 getter 和 setter 方法定义 属性

因为这是一个行为类,当它附加到一个组件时,该组件也将具有 prop1prop2 属性和 foo() 方法。

Tip: 在行为内部可以通过 [[yii\base\Behavior::owner]] 属性访问行为已附加的组件。

Note: 如果 [[yii\base\Behavior::get()]] 和或 [[yii\base\Behavior::set()]] 行为方法被覆盖,则需要覆盖 [[yii\base\Behavior::canGetProperty()]] 和或 [[yii\base\Behavior::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)
    {
        // 处理器方法逻辑
    }
}

[[yii\base\Behavior::events()]] 方法返回事件列表和相应的处理器。上例声明了 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE]] 事件和它的处理器 beforeValidate() 。当指定一个事件处理器时,要使用以下格式之一:

  • 指向行为类的方法名的字符串,如上例所示;
  • 对象或类名和方法名的数组,如 [$object, 'methodName']
  • 匿名方法。

处理器的格式如下,其中 $event 指向事件参数。关于事件的更多细节请参考 事件

function ($event) {
}

附加行为

可以静态或动态地附加行为到 yii\base\Component 组件。前者在实践中更常见。

要静态附加行为,覆写行为要附加的组件类的 [[yii\base\Component::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',
            ]
        ];
    }
}

通过指定行为配置数组相应的键可以给行为关联一个名称。这种行为称为 命名行为。上例中,有两个命名行为:myBehavior2myBehavior4 。如果行为没有指定名称就是 匿名行为

要动态附加行为,在对应组件里调用 [[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,          // 匿名行为
]);

还可以通过 配置 去附加行为:

[
    'as myBehavior2' => MyBehavior::class,

    'as myBehavior3' => [
        'class' => MyBehavior::class,
        'prop1' => 'value1',
        'prop2' => 'value2',
    ],
]

详情请参考 配置 章节。

使用行为

使用行为,必须像前文描述的一样先把它附加到 yii\base\Component 类或其子类。一旦行为附加到组件,就可以直接使用它。

行为附加到组件后,可以通过组件访问一个行为的 公共 成员变量 或 getter 和 setter 方法定义的属性:

// "prop1" 是定义在行为类的属性
echo $component->prop1;
$component->prop1 = $value;

类似地也可以调用行为的 公共 方法:

// foo() 是定义在行为类的公共方法
$component->foo();

如你所见,尽管 $component 未定义 prop1foo() ,它们用起来也像组件自己定义的一样。

如果两个行为都定义了一样的属性或方法,并且它们都附加到同一个组件,那么 首先 附加上的行为在属性或方法被访问时有优先权。

附加行为到组件时的命名行为,可以使用这个名称来访问行为对象,如下所示:

$behavior = $component->getBehavior('myBehavior');

也能获取附加到这个组件的所有行为:

$behaviors = $component->getBehaviors();

移除行为

要移除行为,可以调用 [[yii\base\Component::detachBehavior()]] 方法用行为相关联的名字实现:

$component->detachBehavior('myBehavior1');

也可以移除 全部 行为:

$component->detachBehaviors();

内置行为

Yii 有几种内置行为可用:

属性行为

yii\behaviors\AttributeBehavior 这个行为,在发生特定事件时自动为 ActiveRecord 对象的一个或多个属性指定一个指定的值。

要使用该行为,请配置 attributes 属性,该属性应指定需要更新的属性列表,以及应该触发更新的相应事件。然后,用一个 PHP 可调用对象来配置 value 属性,该对象的返回值将被用来分配给当前属性。例如:

use yii\behaviors\AttributeBehavior;

public function behaviors()
{
    return [
        [
            'class' => AttributeBehavior::class,
            // 可以只定义 insert 或 update 之一,缺省的那个表示不执行写表
            'attributes' => [
                // insert 表的时候
                ActiveRecord::EVENT_BEFORE_INSERT => 'attribute1',
                // update 表的时候
                ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
            ],
            'value' => function ($event) {
                return 'some value';
            },
        ],
    ];
}

由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,它们不应该出现在模型的 [[yii\base\Model::rules()] 方法中。

时间戳行为

yii\behaviors\TimestampBehavior 这个行为,支持在 yii\db\ActiveRecord 活动记录存储时,自动更新它的时间戳属性,通常是创建时间和更新时间,它是 yii\behaviors\AttributeBehavior 行为的子类,要使它,请在你的 ActiveRecord 类中插入以下代码:

use yii\behaviors\TimestampBehavior;

public function behaviors()
{
    return [
        TimestampBehavior::class,
    ];
}

再完整一些,如下:

namespace app\models\User;

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()'),
            ],
        ];
    }
}

默认情况下,当关联的 AR 对象被 insert 时,该行为将用当前时间戳填充 created_atupdated_at 属性,当 AR 对象被 update 时,它将用时间戳填充 updated_at 属性。时间戳值由 time() 获取。

由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,created_atupdated_at 不应该出现在模型的 [[yii\base\Model::rules()] 方法中。

对于以上使用 MySQL 数据库的实现,请事先声明列(created_at, updated_at)为 int(11) 类型来存储 UNIX 时间戳。

有了以上这段代码,如果你有一个 User 对象并且试图保存它,你会发现它的 created_atupdated_at 被当前的 UNIX 时间戳自动填充:

$user = new User;
$user->email = 'test@example.com';
$user->save();
echo $user->created_at;  // 显示当前时间戳

如果你的属性名不同,或想使用其他方式来计算时间戳,你可以配置 createdAtAttributeupdatedAtAttributevalue 属性,如下所示:

use yii\db\Expression;

public function behaviors()
{
    return [
        [
            'class' => TimestampBehavior::class,
            'createdAtAttribute' => 'create_time',
            'updatedAtAttribute' => 'update_time',
            'value' => new Expression('NOW()'),
        ],
    ];
}

如果你像上面的例子一样,使用一个 yii\db\Expression 对象,该属性将不保存时间戳值,而是在记录保存之后的 Expression 对象本身。之后,如果你需要 DB 的值,你应该调用记录的 \yii\db\ActiveRecord::refresh() 方法。

该行为还提供了一个名为 yii\behaviors\TimestampBehavior::touch() 的方法,允许您将当前的时间戳分配给指定的属性,并将它们保存到数据库中。例如:

$model->touch('creation_time');

追责行为

yii\behaviors\BlameableBehavior 这个行为,自动用当前用户 ID 填充指定的属性,通常是创建人 ID 和 更新人 ID。它也是 yii\behaviors\AttributeBehavior 行为的子类,要使用它,可以在 ActiveRecord 类中插入以下代码:

use yii\behaviors\BlameableBehavior;

public function behaviors()
{
    return [
        BlameableBehavior::class,
    ];
}

默认情况下,当关联的 AR 对象被 insert 时,该行为将用当前用户 ID 填充 created_byupdated_by 属性,当 AR 对象被 update 时,它将用当前用户 ID 填充 updated_by 属性。

由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,created_byupdated_by 不应该出现在模型的 [[yii\base\Model::rules ()] 方法中。

如果你的属性名不同,你可以配置 createdByAttributeupdatedByAttribute 属性,如下所示:

public function behaviors()
{
    return [
        [
            'class' => BlameableBehavior::class,
            'createdByAttribute' => 'author_id',
            'updatedByAttribute' => 'updater_id',
        ],
    ];
}

SEO URL Slug 行为

yii\behaviors\SluggableBehavior 这个行为,会自动用一个值填充指定的属性,这个值可以在 URL 中作为一个 slug 使用。它也是 yii\behaviors\AttributeBehavior 行为的子类。

Slug,可以参考 yoast.com/slug/#slug ,一般是指网页 URL 链接使用 slug 技术,来做 SEO。它是 URL 的一部分,它以一种易于阅读的形式,标识网站上的特定页面。

Note: 此行为依赖 php-intl 扩展进行音译。如果没有安装,则返回yii\helpers\Inflector::$transliteration 中定义的来替代。

要使用该行为,插入以下代码到你的 ActiveRecord 类:

use yii\behaviors\SluggableBehavior;

public function behaviors()
{
    return [
        [
            'class' => SluggableBehavior::class,
            'attribute' => 'title',
            // 'slugAttribute' => 'slug',
        ],
    ];
}

默认情况下,当相关的 AR 对象被验证时,该行为将可以在 URL 中使用一个 slug 值来填充 slug 属性。

由于属性值会通过该行为自动设置,它们通常不是用户输入的,因此不应该被验证,也就是说,slug 属性不应该出现在模型的 [[yii\base\Model::rules()] 方法中。

如果你的属性名不同,可以像下面这样配置 slugAttribute 属性:

public function behaviors()
{
    return [
        [
            'class' => SluggableBehavior::class,
            'slugAttribute' => 'alias',
        ],
    ];
}

属性类型转换行为

从 2.0.10 版本开始,yii\behaviors\AttributeTypecastBehavior 这个行为,提供自动模型属性类型转换的能力。

该行为是非常有用的,尤其是在针对无模式数据库,如 MongoDB 、Redis等,且使用 ActiveRecord 的情况下。对于常规的 yii\db\ActiveRecord,甚至 yii\base\Model,它也可以派上用场,允许在模型验证后维护严格的属性类型。

该行为应该被附加到 yii\base\Modelyii\db\BaseActiveRecord 的子类。您应该通过 attributeTypes 指定确切的属性类型。例如:

use yii\behaviors\AttributeTypecastBehavior;

class Item extends \yii\db\ActiveRecord
{
    public function behaviors()
    {
        return [
            'typecast' => [
                'class' => AttributeTypecastBehavior::class,
                'attributeTypes' => [
                    'amount' => AttributeTypecastBehavior::TYPE_INTEGER,
                    'price' => AttributeTypecastBehavior::TYPE_FLOAT,
                    'is_active' => AttributeTypecastBehavior::TYPE_BOOLEAN,
                ],
                'typecastAfterValidate' => true,
                'typecastBeforeSave' => false,
                'typecastAfterFind' => false,
            ],
        ];
    }

    // ...
}

Tip: 当 attributeTypes 的值将根据所有者验证规则自动检测的情况下,您可以将它置空。下面的例子将自动创建与上面配置相同的 attributeTypes 值:

use yii\behaviors\AttributeTypecastBehavior;

class Item extends \yii\db\ActiveRecord
{

    public function rules()
    {
        return [
            ['amount', 'integer'],
            ['price', 'number'],
            ['is_active', 'boolean'],
        ];
    }

    public function behaviors()
    {
        return [
            'typecast' => [
                'class' => AttributeTypecastBehavior::class,
                // 'attributeTypes' will be composed automatically according to `rules()`
            ],
        ];
    }

    // ...
}

该行为允许在以下情况下,自动进行属性类型转换:

  • 模型验证成功之后
  • 模型保存(insert 或 update)之前
  • 模型查询(通过查询找到或刷新)之后

你可以使用字段 typecastAfterValidatetypecastbeforeavetypecastAfterFind 来控制特定情况下的自动类型转换。默认情况下,只有在模型验证之后才会执行类型转换。

Note:你可以在调用 yii\behaviors\AttributeTypecastBehavior::typecastAttributes() 方法时手动触发属性类型转换,示例如下:

$model = new Item();
$model->price = '38.5';
$model->is_active = 1;
$model->typecastAttributes();

批量属性行为

从 2.0.13 版本开始,yii\behaviors\AttributesBehavior 这个行为,在当某些事件触发时,自动赋值给ActiveRecord 对象的一个或多个属性。

To use AttributesBehavior, configure the [[attributes]] property which should specify the list of attributes that need to be updated and the corresponding events that should trigger the update.
要使用该行为,请配置 attributes 属性,该属性应指定需要更新的属性列表,以及应该触发更新的相应事件。然后,使用一个 PHP 回调来配置封装数组的值,该返回值将被用于分配给当前属性。例如:

use yii\behaviors\AttributesBehavior;

public function behaviors()
{
    return [
        [
            'class' => AttributesBehavior::class,
            'attributes' => [
                'attribute1' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => new Expression('NOW()'),
                    ActiveRecord::EVENT_BEFORE_UPDATE => \Yii::$app->formatter->asDatetime('2017-07-13'),
                ],
                'attribute2' => [
                    ActiveRecord::EVENT_BEFORE_VALIDATE => [$this, 'storeAttributes'],
                    ActiveRecord::EVENT_AFTER_VALIDATE => [$this, 'restoreAttributes'],
                ],
                'attribute3' => [
                    ActiveRecord::EVENT_BEFORE_VALIDATE => $fn2 = [$this, 'getAttribute2'],
                    ActiveRecord::EVENT_AFTER_VALIDATE => $fn2,
                ],
                'attribute4' => [
                    ActiveRecord::EVENT_BEFORE_DELETE => function ($event, $attribute) {
                        static::disabled() || $event->isValid = false;
                    },
                ],
            ],
        ],
    ];
}

由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,它们不应该出现在模型的 [[yii\base\Model::rules()] 方法中。

可缓存小部件行为

从 2.0.14 版本开始,yii\behaviors\CacheableWidgetBehavior 这个行为,可以根据指定的持续时间和依赖项,自动缓存小部件内容。

如果应用程序配置了 cache 缓存组件,该行为可以在没有任何配置的情况下使用。默认情况下,小部件将被缓存 60 秒。

下面的例子将无限期地缓存 posts 小部件,直到 post 被修改:

use yii\behaviors\CacheableWidgetBehavior;

public function behaviors()
{
    return [
        [
            'class' => CacheableWidgetBehavior::class,
            'cacheDuration' => 0,
            'cacheDependency' => [
                'class' => 'yii\caching\DbDependency',
                'sql' => 'SELECT MAX(updated_at) FROM posts',
            ],
        ],
    ];
}

乐观锁行为

从 2.0.16 版本开始,yii\behaviors\OptimisticLockBehavior 这个行为,使用 [[yii\db\BaseActiveRecord::optimisticLock()]] 返回的列名,并自动升级一个模型的锁版本,它也是 yii\behaviors\AttributeBehavior 行为的子类。

乐观锁定允许多个用户访问同一条记录进行编辑,从而避免了潜在的冲突。当用户试图保存一些过期数据的记录时(因为另一个用户修改了数据),一个 yii\db\StaleObjectException 异常将被抛出,并且跳过更新或删除操作。

要使用该行为,首先按照 [[yii\db\BaseActiveRecord::optimisticLock()] 中列出的步骤启用乐观锁,再从你的 ActiveRecord 类的 [[yii\base\Model::rules()] 方法中移除持有锁版本的列名,然后向其添加以下代码:

use yii\behaviors\OptimisticLockBehavior;

public function behaviors()
{
    return [
        OptimisticLockBehavior::class,
    ];
}

默认情况下,该行为将使用 [[yii\web\Request::getBodyParam()]] 解析提交的值,或者在任何失败时将其设置为 0,这意味着不包含 version 属性的请求可能实现对实体的首次成功更新,但是从这里开始,任何进一步的尝试都应该失败,除非请求持有预期的版本号。

一旦附加行为,如果版本号不是由 [[yii\web\Request::getBodyParam()] 保存,模型类的内部使用也会失败。扩展您的模型类可能是有用的,通过重写 [[yii\db\BaseActiveRecord::optimisticLock()]] 在父类中启用乐观锁,然后将该行为附加到子类,以至于您可以将父模型绑定到内部使用,同时将持有此行为的子模型连接到负责接收最终用户输入的控制器。

或者,您也可以使用 PHP 回调 配置 value 属性来实现不同的逻辑。

该行为还提供了一个名为 upgrade() 的方法,该方法将模型的版本增加 1,当你需要在连接的客户端之间将一个实体标记为陈旧的,并避免任何更改,直到他们再次加载它时,这可能是有用的:

$model->upgrade();

其它行为

有几种外部行为可用:

比较行为与 Trait

虽然行为类似于 Trait,它们都将自己的属性和方法“注入”到主类中,但它们在许多方面有所不同。如下所述,他们都有优点和缺点。它们更像互补类而非替代类。

使用行为的原因

行为类像普通类支持继承。另一方面,Trait 可以视为 PHP 语言支持的复制粘贴功能,它不支持继承。

行为无须修改组件类就可动态附加到组件或移除。要使用 Trait,必须修改使用它的类。

行为是可配置的,而 Trait 则不可行。

行为可以通过响应事件来定制组件的代码执行。

当附属于同一组件的不同行为之间可能存在名称冲突时,通过优先考虑附加到该组件的行为,自动解决冲突。由不同 Trait 引起的名称冲突需要通过重命名受影响的属性或方法进行手动解决。

使用 Trait 的原因

Trait 比行为更有效,因为行为是既需要时间又需要内存的对象。

因为 IDE 是一种本地语言结构,所以它们对 Trait 更友好。

💖喜欢本文档的,欢迎点赞、收藏、留言或转发,谢谢支持!
作者邮箱:zhuzixian520@126.com,github地址:github.com/zhuzixian520

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
zhuzixian520
讨论数量: 0
发起讨论 只看当前版本


暂无话题~