阿江 2017-10-12 15:51:34 89次浏览 0条回复 0 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
*/

(全文完)

    没有找到数据。
您需要登录后才可以回复。登录 | 立即注册