阿江 2017-10-12 15:51:34 6811次浏览 2条回复 3 0 0

说明

学习Yii Framework 2易2框架的过程是漫长的也是充满乐趣的以下是我学习Yii2框架时对官网英文资料(请参见原文网址)的翻译和代码实现提供了较完整的代码供你参考不妥之处请多多指正

原文网址:

http://www.yiiframework.com/doc-2.0/guide-concept-behaviors.html

本文主题:行为(Behaviors)

行为(Behavior)是yii\base\Behavior或其子类的实例,行为,以最小化最为出名,允许你扩展已存在的组件类的功能,而无需改变类的继承关系。绑定一个行为到一个组件就是依赖注入(inject)行为的方法和属性到组件中,而这些方法和属性的调用就如同是组件内部定义的一样。行为可以响应组件触发的事件,从而允许行为自定义组件执行的普通代码。

1、Defining Behaviors(定义行为)

要定义一个行为,创建一个继承自yii\base\Behavior或其子类的类文件取出可,例如:

D:\phpwork\advanced\frontend\components\MyBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
class MyBehavior extends Behavior{
    public $prop1;
    private $_prop2;
    public function setProp2($value){
        $this->_prop2=$value;
    }
    public function getProp2(){
        return $this->_prop2;
    }
    public function foo(){
        
    }
}

以上代码定义了一个行为类frontend\components\MyBehavior,和两个属性prop1和prop2,以及一个方法foo()。注意:属性prop2是通过getProp2()和setProp2()来定义的,这种情况是因为yii\base\Behavior继承自yii\base\Object,从而支持通过getter和setter来定义属性。

因为这个类是一个行为,当它绑定到一个组件时,组件就可以拥有属性prop1和prop2,也拥有一个方法foo()。

小贴士:在一个行为中你可以通过yii\base\Behavior::$owner 属性获取行为绑定的组件。

注意:如果行为的yii\base\Behavior::__get()和yii\base\Behavior::_set()方法被重写,你应该也要同时重写yii\base\Behavior::canGetProperty()和yii\base\Beharior::canSetProperty()方法。

2、Handling Component Events(绑定组件事件)

如果一个行为需要响应它所绑定的组件触发的事件,它应该重写yii\base\Behavior::events()方法,例如:

D:\phpwork\advanced\frontend\components\HandleBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
use yii\mongodb\ActiveRecord;
class HandleBehavior extends Behavior{
    public function events(){
        return [
            ActiveRecord::EVENT_BEFORE_VALIDATE=>'beforeValidate',
        ];
    }
    public function beforeValidate(){

    }
}

events()方法会返回一个事件列表,后面是其对应的事件处理器。上例中声明了一个已存在的事件EVENT_BEFORE_VALIDATE和它的事件处理器beforeValidate(),当定义一个事件处理器时,你可以使用以下的一种格式: 1、一个字符串,指向行为类的一个方法名称,如上例所示。 2、一个数组,包括对象或类名称和一个方法名称(没有圆括号),如:[$object,'methodName']。 3、一个匿名函数。

一个事件处理器的形式如下所示:

function($event){

}

$event 是指事件参数。

绑定事件处理器'dowith'到事件EVENT_DANCE上

D:\phpwork\advanced\frontend\components\EventBehavior.php

    public function events(){
        $parent=parent::events();
        $events=[
            DanceEventInterface::EVENT_DANCE=>'dowith',
        ];
        return array_merge($parent,$events);
    }
3、Attaching Behaviors(绑定行为)

我们可以绑定一个行为到组件,绑定方式可以是静态的,也可以是动态的,在操作中前者更常用些。 要静态绑定一个行为,重写要绑定的组件类的behaviors()即可。behaviors()方法会返回一个行为配置列表,每个行为配置可以是一个行为类名称或一个配置数组: D:\phpwork\advanced\frontend\models\ComModel.php

<?php
namespace frontend\models;
use yii\base\Component;
use frontend\components\MyBehavior;
class ComModel extends Component{
    public function behaviors(){
        return [
            //声明一个匿名行为,只有行为的类名称
            MyBehavior::className(),
            //声明一个命名的行为,仅指定行为的类名称
            'myBehavior2'=>MyBehavior::className(),
            //声明一个匿名行为,配置行为数组
            [
                'class'=>MyBehavior::className(),
                'prop1'=>'value1',
                'prop2'=>'value2',
            ],
            //声明一个命名的行为,配置行为数组
            'myBehavior3'=>[
                'class'=>MyBehavior::className(),
                'prop1'=>'value3',
                'prop2'=>'value4',
            ],
        ];
    }
}

在行为配置中,你可以定义一个数组键名绑定一个行为,这种情况下,此行为被称为命名的行为,在上例中,有两个命名的行为:myBehavior2和myBehavior4。如果行为没有与名字关联,它被称为匿名行为。

如果要动态绑定一个行为,可以调用要绑定的组件的yii\base\Component::attachBehavior()方法。

        //绑定到一个行为类
        $this->attachBehavior('mybehavior1',MyBehavior::className());
        //绑定到一个对象
        $this->attachBehavior('mybehavior2',new MyBehavior());
        //绑定到配置数组
        $this->attachBehavior('mybehavior3',[
            'class'=>MyBehavior::className(),
            'prop1'=>'var1',
            'prop2'=>'var2',
        ]);

我们还可以使用yii\base\Component::attachBehaviors()方法一次绑定多个行为:

        $this->attachBehaviors([
            'mybehavior3'=>[
                'class'=>MyBehavior::className(),
                'prop1'=>'var1',
                'prop2'=>'var2',
            ],
            YouBehavior::className(),
        ]);

使用组件配置绑定行为的代码如下:

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

D:\phpwork\advanced\frontend\controllers\PostController.php

    public function actionDynamic(){
        $config=[
            'as behavior'=>[
                'class'=>MyBehavior::className(),
                'prop2'=>'hello behavior',
            ],
        ];
        $dynamic=new Dynamic($config);
        $dynamic->foo();
    }
4、Using Behaviors

要使用一个行为,首先要如上所述绑定行为到组件上,一旦一个行为绑定到组件后,就可以直接调用了。 我们可以调用组件绑定的行为的公共成员变量(public member property)或者是一个使用getter或setter定义的属性(property):

//prop1是定义在行为中的一个属性:
echo $component->prop1;
$component->prop1 = $value;

我们可以调用行为的公共方法:

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

正如你所看到的,虽然$component 没有定义prop1和foo(),但绑定行为后,它们能够如同组件的一部分一样被使用。 如果两个行为定义了相同的属性或方法,并且绑定到了同一个组件上,第一个绑定到组件上的行为将获得优先权,也就是说,第一个绑定的组件的属性或方法将被获取到。

当行为绑定到组件时可以赋一个名称,在这种情总下,我们可以通过名称来获取这个行为对象:

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

我们也可以获取绑定到一个组件上的所有的行为:

$behaviors = $component->getBehaviors();
5、Detaching Behaviors(解除绑定的行为)

要解除绑定的一个行为,我们可以调用yii\base\Component::detachBehavior(),需要指定行为的名称。

$component->detachBehavior('myBehavior1');

我们可以解除绑定的所有行为:

$component->detachBeahviors();
6、Using TimestampBehavior

我们现在一起来看一下yii\behaviors\TimestampBehavior实例,这个行为支持自动更新Active Record的时间戳属性,无论此模型是调用insert()、update(),还是save()方法都会更新。 首先绑定这个行为到要使用的Active Record类上:

namespace app\models\User;
use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;
class User extends ActiveRecord{
	//自动更新`created_at`(创建时间), `updated_at`(修改时间)的时间戳
   public function behaviors()
    {
        return [
            [
				//默认字段名:created_at,updated_at
                'class' => \yii\behaviors\TimestampBehavior::className(),
            ],
            [
				//默认字段名:created_by,updated_by
                'class' => \yii\behaviors\BlameableBehavior::className(),
                'createdByAttribute' => 'userId',
                'updatedByAttribute' => 'updaterId',
            ],
        ];
    }
	//以上的behaviors()函数等效于:
    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('UNIX_TIMESTAMP()'),
            ],
        ];
    }
	//自定义 创建时间和修改时间 的字段名
	 public function behaviors(){
		 return [
			 [
				 'class' => TimestampBehavior::className(),
				 //'createdAtAttribute' => false,//如果没有字段需要更新,可以设置为false
				 'createdAtAttribute' => 'create_time',
				 'updatedAtAttribute' => 'update_time',
				 'value' => new yii\db\Expression('UNIX_TIMESTAMP()'),
			 ],
		 ];
	 }
}

上述代码中的行为配置定义了要操作的记录是以下情况: 1、插入的(inserted),此行为将给created_at和updated_at属性赋值为UNIX的当前时间戳。 2、更新的(updated),此行为将给updated_at属性赋值为UNIX的当前时间戳。

注意:以上实现是完成于MySQL数据库,需要定义两个字段(create_at和updated_at),int(11),用于存储UNIX时间戳。

通过以上代码设置,我们创建一个User对象并保存它,你会发现它的created_at和updated_at将自动填充为当前的UNIX时间戳。

$user = new User;
$user->email = 'test@example.com';
$user->save();
echo $user->created_at;  // shows the current timestamp

TimestampBehavior还提供了一个通用方法touch(),它会将当前的时间戳赋值到一个指定的属性,并保存到数据库中:

$user->touch("login_time");
7、Other behaviors(其他行为)

Yii2还提供了一些内置和扩展的行为可以使用: yii\behaviors\BlameableBehavior:自动将当前用户的ID填充到指定属性。 http://www.yiiframework.com/doc-2.0/yii-behaviors-blameablebehavior.html

public function behaviors()
{
    return [
	    [
            'class' => \yii\behaviors\BlameableBehavior::className(),
            'createdByAttribute' => 'createUserId',//定义创建时要更新的创建者id的属性名称,默认名称是:created_by
            'updatedByAttribute' => 'updateUserId',//定义编辑时要更新的编辑者id的属性名称,默认名称是:updated_by
        ],
    ];
}

yii\behaviors\SluggableBehavior:自动将URL片段填充到指定属性。 http://www.yiiframework.com/doc-2.0/yii-behaviors-sluggablebehavior.html

yii\behaviors\AttributeBehavior:在特定事件中,自动给指定的一个或多个属性赋一个值。 http://www.yiiframework.com/doc-2.0/yii-behaviors-attributebehavior.html

yii2tech\ar\softdelete\SoftDeleteBehavior:为需要软删除(soft-delete)和软存储(soft-restor)的ActiveRecord提供了方法。 https://github.com/yii2tech/ar-softdelete

yii2tech\ar\position\PositionBehavior:通过重新排序的方法来管理记录的排序,此排序是一个整数字段(integer field) https://github.com/yii2tech/ar-position

8、Comparing Behaviors with Traits(行为和Trait的比较)

行为和tait非常相似,它们都依赖注入(inject)它们的属性和方法到主类中。但它们也有很大的区别,接下来我们就讲述一下它们各自的利弊,它们看起来更象是可以相互替代的组件。

使用行为的理由:

1、行为类更象是一个普通类,支持继承;另一方面,Trait被视为PHP语言支持的拷贝,但不支持继承。 2、行为可以被动态的绑定和解绑,而无需修改组件类;使用trait,你必须修改调用类的代码。 3、行为是可配置的,而trait不行。 4、在组件的事件中调用行为,可以自定义行为的执行代码。 5、当绑定到组件中的行为有名称冲突时,首先绑定到组件的行为将获得更高优先级,这样名称冲突将被自动解决;而在不同的trait中造成的类似冲突只能通过修改属性或方法名称来手动解决。

使用Trait的原因:

1、行为是对象,会占用较多的CPU时间和内存,而Trait更高效。 2、因为Trait是原生语言结构,所以IDE对trait支持更友好一些。

//++++++++++++++++++++++++++++++++++++++

以下是behavior的应用实例

//--------------- //实例,同名属性和同名方法,先绑定的行为的将生效 D:\phpwork\advanced\frontend\controllers\PostController.php

    public function actionDynamic(){
        $dynamic=new Dynamic();
        $dynamic->foo();
        $dynamic->bar();
        echo "<br>prop2:".$dynamic->prop2;
    }

D:\phpwork\advanced\frontend\models\Dynamic.php

<?php
namespace frontend\models;
use yii\base\Component;
use frontend\components\MyBehavior;
use frontend\components\YouBehavior;
class Dynamic extends Component{
    public function init(){
        parent::init();
        //一次绑定到多个行为
        $this->attachBehaviors([
            'mybehavior3'=>[
                'class'=>MyBehavior::className(),
                'prop2'=>'var88',//同名属性先注册的生效
            ],
            [
                'class'=>YouBehavior::className(),
                'prop2'=>'var2',
            ],
        ]);
    }
}

D:\phpwork\advanced\frontend\components\MyBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
class MyBehavior extends Behavior{
    private $_prop2;
    public function setProp2($value){
        $this->_prop2=$value;
    }
    public function getProp2(){
        return $this->_prop2;
    }
    public function foo(){
        echo "this is MyBehavior's foo()<br>";
    }
    public function bar(){
        //同名方法,先绑定的行为的方法将生效
        echo "this is MyBehavior's bar()<br>";
    }
}

D:\phpwork\advanced\frontend\components\YouBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
class YouBehavior extends Behavior{
    public $prop2;
    public function bar(){
        echo "this is YouBehavior's bar()<br>";
        $class=$this->owner;
        echo "It's owner:".$class->className()."<br>";
    }
}
测试结果:
http://localhost:8082/post/dynamic
/*
this is MyBehavior's foo()
this is MyBehavior's bar()

prop2:var88
*/

//解除绑定的'mybehavior3'行为,使用上例中的其他程序 http://localhost:8082/post/dynamic

    public function actionDynamic(){
        $dynamic=new Dynamic();
        $dynamic->foo();
		//解除绑定的'mybehavior3'行为
        $dynamic->detachBehavior('mybehavior3');
        $dynamic->bar();
        echo "<br>prop2:".$dynamic->prop2;
    }
测试结果:
/*
this is MyBehavior's foo()
this is YouBehaviors bar()
It's owner:frontend\models\Dynamic

prop2:var2
*/

//--------------- //实例,使用组件配置来绑定行为 D:\phpwork\advanced\frontend\controllers\PostController.php

    public function actionDynamic(){
        $config=[
			//为组件定义了一个绑定的行为MyBehavior,并为参数prop2赋了初值
            'as behavior'=>[
                'class'=>MyBehavior::className(),
                'prop2'=>'hello behavior',
            ],
        ];
		//调用配置信息初始化Dynamic组件
        $dynamic=new Dynamic($config);
        $dynamic->foo();
    }

D:\phpwork\advanced\frontend\models\Dynamic.php

<?php
namespace frontend\models;
use yii\base\Component;
class Dynamic extends Component{
	//Dynamic是个空组件
}

D:\phpwork\advanced\frontend\components\MyBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
class MyBehavior extends Behavior{
    public $prop1;
    private $_prop2;
    public function setProp2($value){
        $this->_prop2=$value;
    }
    public function getProp2(){
        return $this->_prop2;
    }
    public function foo(){
        echo "this is MyBehavior's foo()<br>";
        $class=$this->owner;
        echo "It's owner:".$class->className()."<br>";
        echo "prop2:".$this->prop2."<br>";
    }
}
测试结果:
http://localhost:8082/post/dynamic
/*
this is MyBehavior's foo()
It's owner:frontend\models\Dynamic
prop2:hello behavior
*/

//--------------- //实例,绑定一个行为到组件的事件中 D:\phpwork\advanced\frontend\controllers\PostController.php

    public function actionEventBehavior(){
        echo "actionEventBehavior<br>";
        $eventModel=new EventModel();
		//触发行为
        $eventModel->trigger(DanceEventInterface::EVENT_DANCE);
    }

D:\phpwork\advanced\frontend\models\EventModel.php

<?php
namespace frontend\models;
use frontend\components\EventBehavior;
use yii\base\Component;
class EventModel extends Component{
    public function init(){
        parent::init();
        //绑定行为到配置数组
        $this->attachBehavior('mybehavior3',[
            'class'=>EventBehavior::className(),
        ]);
    }
}

D:\phpwork\advanced\frontend\components\EventBehavior.php

<?php
namespace frontend\components;
use frontend\models\DanceEventInterface;
use yii\base\Behavior;
class EventBehavior extends Behavior implements DanceEventInterface{
    public function events(){
        $parent=parent::events();
		//将自定义的事件处理器'dowith'与事件EVENT_DANCE绑定
        $events=[
            DanceEventInterface::EVENT_DANCE=>'dowith',
        ];
        return array_merge($parent,$events);
    }
	//事件处理器代码:
    public function dowith($event){
        echo "EVENT_DANCE handler";
    }
    public function foo(){
        echo "this is EventBehavior's foo()<br>";
    }
}
测试结果:
http://localhost:8082/post/event-behavior
/*
actionEventBehavior
EVENT_DANCE handler
*/

//--------------- //实例,绑定behavior的实例,绑定多个行为到一个组件 D:\phpwork\advanced\frontend\controllers\PostController.php

    public function actionDynamic(){
        $dynamic=new Dynamic();
        $dynamic->foo();
        $dynamic->bar();
    }

D:\phpwork\advanced\frontend\models\Dynamic.php

<?php
namespace frontend\models;
use yii\base\Component;
use frontend\components\MyBehavior;
use frontend\components\YouBehavior;
class Dynamic extends Component{
    public function init(){
        parent::init();
		//针对于同一个行为的配置不应重复,以下代码采用其中一种即可
        //绑定到一个行为类
        $this->attachBehavior('mybehavior1',MyBehavior::className());
        //绑定到一个对象
        $this->attachBehavior('mybehavior2',new MyBehavior());
        //绑定到配置数组
        $this->attachBehavior('mybehavior3',[
            'class'=>MyBehavior::className(),
            'prop1'=>'var1',
            'prop2'=>'var2',
        ]);
		//一次绑定到多个行为
        $this->attachBehaviors([
            'mybehavior3'=>[
                'class'=>MyBehavior::className(),
                'prop1'=>'var1',
                'prop2'=>'var2',
            ],
            YouBehavior::className(),
        ]);
    }
}

D:\phpwork\advanced\frontend\components\MyBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
class MyBehavior extends Behavior{
    public $prop1;
    private $_prop2;
    public function setProp2($value){
        $this->_prop2=$value;
    }
    public function getProp2(){
        return $this->_prop2;
    }
    public function foo(){
        echo "this is MyBehavior's foo()<br>";
        $class=$this->owner;
        echo "It's owner:".$class->className()."<br>";
        echo "prop2:".$this->prop2."<br>";
    }
}

D:\phpwork\advanced\frontend\components\YouBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
class YouBehavior extends Behavior{
    public $prop3;
    private $_prop4;
    public function setProp4($value){
        $this->_prop4=$value;
    }
    public function getProp4(){
        return $this->_prop4;
    }
    public function bar(){
        echo "this is YouBehavior's bar()<br>";
        $class=$this->owner;
        echo "It's owner:".$class->className()."<br>";
    }
}
测试结果:
http://localhost:8082/post/dynamic
/*
this is MyBehavior's foo()
It's owner:frontend\models\Dynamic
prop2:
this is YouBehavior's bar()
It's owner:frontend\models\Dynamic
*/

//--------------- //实例:behavior的基础应用,绑定一个行为到一个组件 D:\phpwork\advanced\frontend\controllers\PostController.php

    public function actionComModel(){
		//创建一个组件
        $comModel=new ComModel();
		//调用组件绑定的behavior的方法:
        $comModel->foo();
    }

D:\phpwork\advanced\frontend\components\MyBehavior.php

<?php
namespace frontend\components;
use yii\base\Behavior;
class MyBehavior extends Behavior{
    public $prop1;
    private $_prop2;
    public function setProp2($value){
        $this->_prop2=$value;
    }
    public function getProp2(){
        return $this->_prop2;
    }
    public function foo(){
        echo "this is MyBehavior's foo()<br>";
		//获取调用本behavior的component
        $class=$this->owner;
		//显示组件的类名称
        echo "It's owner:".$class->className()."<br>";
        echo "prop2:".$this->prop2."<br>";
    }
}

D:\phpwork\advanced\frontend\models\ComModel.php

<?php
namespace frontend\models;
use yii\base\Component;
use frontend\components\MyBehavior;
class ComModel extends Component{
    public function behaviors(){
        return [
			//针对于同一个行为的配置不应重复,以下1-4配置代码采用其中一种即可
            //1、声明一个匿名行为,只有行为的类名称
            MyBehavior::className(),
            //2、声明一个命名的行为,仅指定行为的类名称
            'myBehavior2'=>MyBehavior::className(),
            //3、声明一个匿名行为,配置行为数组
            [
                'class'=>MyBehavior::className(),
                'prop1'=>'value1',
                'prop2'=>'value2',
            ],
            //4、声明一个命名的行为,配置行为数组
            'myBehavior3'=>[
                'class'=>MyBehavior::className(),
                'prop1'=>'value3',
                'prop2'=>'value4',
            ],
        ];
    }
}

//在behaviors()中重复配置同一个Behavior时的生效规则: 类名称》数组配置,同时存在时,“类名称”生效 匿名行为》命名行为,同时存在时,“匿名行为”生效 根据以上规则,本例中最终MyBehavior::className()配置生效,所以prop2为空。

测试结果:
http://localhost:8082/post/com-model
/*
this is MyBehavior's foo()
It's owner:frontend\models\ComModel
prop2:
*/

//仅配置一次行为时,prop2被设置为'value4',可以生效

    public function behaviors(){
        return [
            //声明一个命名的行为,配置行为数组
            'myBehavior3'=>[
                'class'=>MyBehavior::className(),
                'prop1'=>'value3',
                'prop2'=>'value4',
            ],
        ];
    }
测试结果:
/*
this is MyBehavior's foo()
It's owner:frontend\models\ComModel
prop2:value4
*/

(全文完)

  • 回复于 2017-12-12 11:41 举报

    你好,我在model里面配置了TimestampBehavior,更新的时候,update_time字段确实可以自动更新。那么在某些时候,我想临时取消TimestampBehavior行为,应该怎么操作呢?试过了detachBehavior,最终save的时候,update_time字段还是会自动更新
    `

    public function behaviors()
    {
        return [
            [
                'class'=>TimestampBehavior::className(),
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT=>['add_time', 'update_time'],
                    ActiveRecord::EVENT_BEFORE_UPDATE=>['update_time'],
                ],
            ],
            [
                'class'=>ProjectBehavior::className()
            ],
        ];
    }
    

    `

  • 回复于 2018-03-07 17:06 举报

    你好,我也写了关于行为的文章,
    Yii2基本概念之——行为(Behavior)(http://www.yiichina.com/tutorial/1629)
    可以相互学习下!

您需要登录后才可以回复。登录 | 立即注册