文字
分享

行为

行为

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

定义行为

要定义行为,通过继承 yii\base\Behavior 或其子类来建立一个类。如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">namespace</span> <span style="box-sizing: border-box;">app</span>\<span style="box-sizing: border-box;">components</span>;

 

<span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">yii</span>\<span style="box-sizing: border-box;">base</span>\<span style="box-sizing: border-box;">Behavior</span>;

 

<span style="box-sizing: border-box;"><span style="box-sizing: border-box;">class</span> <span style="box-sizing: border-box;">MyBehavior</span> <span style="box-sizing: border-box;">extends</span> <span style="box-sizing: border-box;">Behavior</span></span>{

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;">$prop1</span>;

 

    <span style="box-sizing: border-box;">private</span> <span style="box-sizing: border-box;">$_prop2</span>;

 

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">getProp2</span><span style="box-sizing: border-box;">()</span>

    </span>{

        <span style="box-sizing: border-box;">return</span> <span style="box-sizing: border-box;">$this</span>->_prop2;

    }

 

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">setProp2</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box;">$value</span>)</span>

    </span>{

        <span style="box-sizing: border-box;">$this</span>->_prop2 = <span style="box-sizing: border-box;">$value</span>;

    }

 

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">foo</span><span style="box-sizing: border-box;">()</span>

    </span>{

        <span style="box-sizing: border-box;">// ...</span>

    }

}

</code>

以上代码定义了行为类 app\components\MyBehavior 并为要附加行为的组件提供了两个属性 prop1 、 prop2 和一个方法 foo() 。注意属性 prop2 是通过 getter getProp2() 和 setter setProp2() 定义的。能这样用是因为 yii\base\Object 是 yii\base\Behavior 的祖先类,此祖先类支持用 getter 和 setter 方法定义属性

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

处理事件

如果要让行为响应对应组件的事件触发,就应覆写 yii\base\Behavior::events() 方法,如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">namespace</span> <span style="box-sizing: border-box;">app</span>\<span style="box-sizing: border-box;">components</span>;

 

<span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">yii</span>\<span style="box-sizing: border-box;">db</span>\<span style="box-sizing: border-box;">ActiveRecord</span>;

<span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">yii</span>\<span style="box-sizing: border-box;">base</span>\<span style="box-sizing: border-box;">Behavior</span>;

 

<span style="box-sizing: border-box;"><span style="box-sizing: border-box;">class</span> <span style="box-sizing: border-box;">MyBehavior</span> <span style="box-sizing: border-box;">extends</span> <span style="box-sizing: border-box;">Behavior</span></span>{

    <span style="box-sizing: border-box;">// 其它代码</span>

 

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">events</span><span style="box-sizing: border-box;">()</span>

    </span>{

        <span style="box-sizing: border-box;">return</span> [

            ActiveRecord::EVENT_BEFORE_VALIDATE => <span style="box-sizing: border-box;">'beforeValidate'</span>,

        ];

    }

 

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">beforeValidate</span><span style="box-sizing: border-box;">(<span style="box-sizing: border-box;">$event</span>)</span>

    </span>{

        <span style="box-sizing: border-box;">// 处理器方法逻辑</span>

    }

}

</code>

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

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

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

1

2

3

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">(<span style="box-sizing: border-box;">$event</span>)</span> </span>{

}

</code>

附加行为

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">namespace</span> <span style="box-sizing: border-box;">app</span>\<span style="box-sizing: border-box;">models</span>;

 

<span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">yii</span>\<span style="box-sizing: border-box;">db</span>\<span style="box-sizing: border-box;">ActiveRecord</span>;

<span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">app</span>\<span style="box-sizing: border-box;">components</span>\<span style="box-sizing: border-box;">MyBehavior</span>;

 

<span style="box-sizing: border-box;"><span style="box-sizing: border-box;">class</span> <span style="box-sizing: border-box;">User</span> <span style="box-sizing: border-box;">extends</span> <span style="box-sizing: border-box;">ActiveRecord</span></span>{

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">behaviors</span><span style="box-sizing: border-box;">()</span>

    </span>{

        <span style="box-sizing: border-box;">return</span> [

            <span style="box-sizing: border-box;">// 匿名行为,只有行为类名</span>

            MyBehavior::className(),

 

            <span style="box-sizing: border-box;">// 命名行为,只有行为类名</span>

            <span style="box-sizing: border-box;">'myBehavior2'</span> => MyBehavior::className(),

 

            <span style="box-sizing: border-box;">// 匿名行为,配置数组</span>

            [

                <span style="box-sizing: border-box;">'class'</span> => MyBehavior::className(),

                <span style="box-sizing: border-box;">'prop1'</span> => <span style="box-sizing: border-box;">'value1'</span>,

                <span style="box-sizing: border-box;">'prop2'</span> => <span style="box-sizing: border-box;">'value2'</span>,

            ],

 

            <span style="box-sizing: border-box;">// 命名行为,配置数组</span>

            <span style="box-sizing: border-box;">'myBehavior4'</span> => [

                <span style="box-sizing: border-box;">'class'</span> => MyBehavior::className(),

                <span style="box-sizing: border-box;">'prop1'</span> => <span style="box-sizing: border-box;">'value1'</span>,

                <span style="box-sizing: border-box;">'prop2'</span> => <span style="box-sizing: border-box;">'value2'</span>,

            ]

        ];

    }

}

</code>

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

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

1

2

3

4

5

6

7

8

9

10

11

12

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">app</span>\<span style="box-sizing: border-box;">components</span>\<span style="box-sizing: border-box;">MyBehavior</span>;

 

<span style="box-sizing: border-box;">// 附加行为对象</span><span style="box-sizing: border-box;">$component</span>->attachBehavior(<span style="box-sizing: border-box;">'myBehavior1'</span>, <span style="box-sizing: border-box;">new</span> MyBehavior);

 

<span style="box-sizing: border-box;">// 附加行为类</span><span style="box-sizing: border-box;">$component</span>->attachBehavior(<span style="box-sizing: border-box;">'myBehavior2'</span>, MyBehavior::className());

 

<span style="box-sizing: border-box;">// 附加配置数组</span><span style="box-sizing: border-box;">$component</span>->attachBehavior(<span style="box-sizing: border-box;">'myBehavior3'</span>, [

    <span style="box-sizing: border-box;">'class'</span> => MyBehavior::className(),

    <span style="box-sizing: border-box;">'prop1'</span> => <span style="box-sizing: border-box;">'value1'</span>,

    <span style="box-sizing: border-box;">'prop2'</span> => <span style="box-sizing: border-box;">'value2'</span>,

]);

</code>

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

1

2

3

4

5

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">$component</span>->attachBehaviors([

    <span style="box-sizing: border-box;">'myBehavior1'</span> => <span style="box-sizing: border-box;">new</span> MyBehavior,  <span style="box-sizing: border-box;">// 命名行为</span>

    MyBehavior::className(),          <span style="box-sizing: border-box;">// 匿名行为</span>

]);

</code>

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

1

2

3

4

5

6

7

8

9

10

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;">[

    <span style="box-sizing: border-box;">'as myBehavior2'</span> => MyBehavior::className(),

 

    <span style="box-sizing: border-box;">'as myBehavior3'</span> => [

        <span style="box-sizing: border-box;">'class'</span> => MyBehavior::className(),

        <span style="box-sizing: border-box;">'prop1'</span> => <span style="box-sizing: border-box;">'value1'</span>,

        <span style="box-sizing: border-box;">'prop2'</span> => <span style="box-sizing: border-box;">'value2'</span>,

    ],

]

</code>

详情请参考配置章节。

使用行为

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

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

1

2

3

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">// "prop1" 是定义在行为类的属性</span><span style="box-sizing: border-box;">echo</span> <span style="box-sizing: border-box;">$component</span>->prop1;

<span style="box-sizing: border-box;">$component</span>->prop1 = <span style="box-sizing: border-box;">$value</span>;

</code>

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

1

2

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">// foo() 是定义在行为类的公共方法</span><span style="box-sizing: border-box;">$component</span>->foo();

</code>

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

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

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

1

2

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">$behavior</span> = <span style="box-sizing: border-box;">$component</span>->getBehavior(<span style="box-sizing: border-box;">'myBehavior'</span>);

</code>

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

1

2

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">$behaviors</span> = <span style="box-sizing: border-box;">$component</span>->getBehaviors();

</code>

移除行为

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

1

2

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">$component</span>->detachBehavior(<span style="box-sizing: border-box;">'myBehavior1'</span>);

</code>

也可以移除全部行为:

1

2

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">$component</span>->detachBehaviors();

</code>

使用 TimestampBehavior

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

首先,附加这个行为到计划使用该行为的 yii\db\ActiveRecord 类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">namespace</span> <span style="box-sizing: border-box;">app</span>\<span style="box-sizing: border-box;">models</span>\<span style="box-sizing: border-box;">User</span>;

 

<span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">yii</span>\<span style="box-sizing: border-box;">db</span>\<span style="box-sizing: border-box;">ActiveRecord</span>;

<span style="box-sizing: border-box;">use</span> <span style="box-sizing: border-box;">yii</span>\<span style="box-sizing: border-box;">behaviors</span>\<span style="box-sizing: border-box;">TimestampBehavior</span>;

 

<span style="box-sizing: border-box;"><span style="box-sizing: border-box;">class</span> <span style="box-sizing: border-box;">User</span> <span style="box-sizing: border-box;">extends</span> <span style="box-sizing: border-box;">ActiveRecord</span></span>{

    <span style="box-sizing: border-box;">// ...</span>

 

    <span style="box-sizing: border-box;">public</span> <span style="box-sizing: border-box;"><span style="box-sizing: border-box;">function</span> <span style="box-sizing: border-box;">behaviors</span><span style="box-sizing: border-box;">()</span>

    </span>{

        <span style="box-sizing: border-box;">return</span> [

            [

                <span style="box-sizing: border-box;">'class'</span> => TimestampBehavior::className(),

                <span style="box-sizing: border-box;">'attributes'</span> => [

                    ActiveRecord::EVENT_BEFORE_INSERT => [<span style="box-sizing: border-box;">'created_at'</span>, <span style="box-sizing: border-box;">'updated_at'</span>],

                    ActiveRecord::EVENT_BEFORE_UPDATE => [<span style="box-sizing: border-box;">'updated_at'</span>],

                ],

            ],

        ];

    }

}

</code>

以上指定的行为数组:

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

保存 User 对象,将会发现它的 created_at 和 updated_at 属性自动填充了当前时间戳:

1

2

3

4

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">$user</span> = <span style="box-sizing: border-box;">new</span> User;

<span style="box-sizing: border-box;">$user</span>->email = <span style="box-sizing: border-box;">'test@example.com'</span>;

<span style="box-sizing: border-box;">$user</span>->save();

<span style="box-sizing: border-box;">echo</span> <span style="box-sizing: border-box;">$user</span>->created_at;  <span style="box-sizing: border-box;">// 显示当前时间戳</span></code>

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

1

2

<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: inherit; padding: 0px; color: inherit; border-radius: 0px; white-space: pre-wrap; background-color: transparent;"><span style="box-sizing: border-box;">$user</span>->touch(<span style="box-sizing: border-box;">'login_time'</span>);

</code>

与 PHP traits 的比较

尽管行为在 "注入" 属性和方法到主类方面类似于 traits ,它们在很多方面却不相同。如上所述,它们各有利弊。它们更像是互补的而不是相互替代。

行为的优势

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

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

行为是可配置的而 traits 不能。

行为以响应事件来自定义组件的代码执行。

当不同行为附加到同一组件产生命名冲突时,这个冲突通过先附加行为的优先权自动解决。而由不同 traits 引发的命名冲突需要通过手工重命名冲突属性或方法来解决。

traits 的优势

traits 比起行为更高效,因为行为是对象,消耗时间和内存。

IDE 对 traits 更友好,因为它们是语言结构。