行为

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

定义行为

要定义行为,通过继承 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() 方法。

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

注意: 如果 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)
    {
        // 处理器方法逻辑
    }
}

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

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

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

function ($event) {
}

附加行为

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

要静态附加行为,覆写行为要附加的组件类的 behaviors() 方法即可。 behaviors() 方法应该返回行为配置列表。 每个行为配置可以是行为类名也可以是配置数组。如:

namespace app\models;

use yii\db\ActiveRecord;
use app\components\MyBehavior;

class User extends ActiveRecord
{
    public function behaviors()
    {
        return [
            // 匿名行为,只有行为类名
            MyBehavior::className(),

            // 命名行为,只有行为类名
            'myBehavior2' => MyBehavior::className(),

            // 匿名行为,配置数组
            [
                'class' => MyBehavior::className(),
                'prop1' => 'value1',
                'prop2' => 'value2',
            ],

            // 命名行为,配置数组
            'myBehavior4' => [
                'class' => MyBehavior::className(),
                'prop1' => 'value1',
                'prop2' => 'value2',
            ]
        ];
    }
}

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

要动态附加行为,在对应组件里调用 yii\base\Component::attachBehavior() 方法即可,如:

use app\components\MyBehavior;

// 附加行为对象
$component->attachBehavior('myBehavior1', new MyBehavior);

// 附加行为类
$component->attachBehavior('myBehavior2', MyBehavior::className());

// 附加配置数组
$component->attachBehavior('myBehavior3', [
    'class' => MyBehavior::className(),
    'prop1' => 'value1',
    'prop2' => 'value2',
]);

可以通过 yii\base\Component::attachBehaviors() 方法一次附加多个行为:

$component->attachBehaviors([
    'myBehavior1' => new MyBehavior,  // 命名行为
    MyBehavior::className(),          // 匿名行为
]);

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

[
    'as myBehavior2' => MyBehavior::className(),

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

详情请参考 配置章节。

使用行为

使用行为,必须像前文描述的一样先把它附加到 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();

使用 TimestampBehavior

最后以 yii\behaviors\TimestampBehavior 的讲解来结尾, 这个行为支持在 Active Record 存储时自动更新它的时间戳属性。

首先,附加这个行为到计划使用该行为的 Active Record 类:

namespace app\models\User;

use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;

class User extends ActiveRecord
{
    // ...

    public function behaviors()
    {
        return [
            [
                'class' => TimestampBehavior::className(),
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                ],
                // if you're using datetime instead of UNIX timestamp:
                // 'value' => new Expression('NOW()'),
            ],
        ];
    }
}

以上指定的行为数组:

  • 当记录插入时,行为将当前时间戳赋值给 created_atupdated_at 属性;
  • 当记录更新时,行为将当前时间戳赋值给 updated_at 属性。

注意: 对于上述实现使用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;  // 显示当前时间戳

TimestampBehavior 行为还提供了一个有用的方法 touch(), 这个方法能将当前时间戳赋值给指定属性并保存到数据库:

$user->touch('login_time');

其它行为

有几种内置和外部行为可用:

比较行为与 Traits

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

使用行为的原因

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

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

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

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

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

使用 Traits 的原因

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

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