yii2面向对象三大特性---属性(Property)、事件(Event)、行为(Behavior)

转自:http://www.digpage.com/property.html

一、属性

变量与属性、函数与方法的区别:

class A {
	public $name;
	function b(){
		$otherName = 1;
		$this->name = $otherName;
	}
}
(1)类中的有主的变量叫属性。$name就是A的属性,$otherName无主,就叫变量。你可以$a->name,但不能$a->otherName
(2)函数是单独存在的,面向过程;方法是依赖于类存在的,面向对象。方法是类里的"函数"。

如果要使你的类支持属性,必须继承自yii\base\Object。Yii中属性是通过PHP的魔法函数 __get() __set() 来产生作用的。在读取和写入对象的一个不存在的成员属性时, __get() __set() 会被自动调用。(PS:取和写入对象的一个不存在的成员方法用__call()  )
如果访问一个对象的某个属性, Yii会调用名为get属性名()的函数。如果修改某个属性,会调用名为get属性名()的函数

public function __get($name)              // 这里$name是属性名
{
    $getter = 'get' . $name;              // getter函数的函数名
    if (method_exists($this, $getter)) {
        return $this->$getter();          // 调用了getter函数
    } elseif (method_exists($this, 'set' . $name)) {
        throw new InvalidCallException('Getting write-only property: '
            . get_class($this) . '::' . $name);
    } else {
        throw new UnknownPropertyException('Getting unknown property: '
            . get_class($this) . '::' . $name);
    }
}

// $name是属性名,$value是拟写入的属性值
public function __set($name, $value)
{
    $setter = 'set' . $name;             // setter函数的函数名
    if (method_exists($this, $setter)) {
        $this->$setter($value);          // 调用setter函数
    } elseif (method_exists($this, 'get' . $name)) {
        throw new InvalidCallException('Setting read-only property: ' .
            get_class($this) . '::' . $name);
    } else {
        throw new UnknownPropertyException('Setting unknown property: '
            . get_class($this) . '::' . $name);
    }
}

实现属性的3步走

继承自 yii\base\Object 。
声明一个用于保存该属性的私有成员变量。
提供getter或setter函数,或两者都提供,用于访问、修改上面提到的私有成员变量。 如果只提供了getter,那么该属性为只读属性,只提供了setter,则为只写

class Post extends yii\base\Object    // 第一步:继承自 yii\base\Object
{
    private $_title;                 // 第二步:声明一个私有成员变量

    public function getTitle()       // 第三步:提供getter和setter
    {
        return $this->_title;
    }

    public function setTitle($value)
    {
        $this->_title = trim($value);
    }
}
一个提高效率的小技巧就是:使用 $pro = $object->getPro() 来代替 $pro = $object->pro, 用 $objcect->setPro($value) 来代替 $object->pro = $value 。 这在功能上是完全一样的效果,但是避免了使用 __get() 和 __set() ,相当于绕过了遍历的过程。

注意的是:
由于自动调用 __get() __set() 的时机仅仅发生在访问不存在的成员变量时。 因此,如果定义了成员变量 public $title 那么,就算定义了 getTitle() setTitle() , 他们也不会被调用。因为 $post->title 时,会直接指向该 pulic $title , __get() __set() 是不会被调用的。从根上就被切断了。
由于 PHP对于类方法不区分大小写,即大小写不敏感, $post->getTitle() 和 $post->gettitle() 是调用相同的函数。 因此, $post->title 和 $post->Title 是同一个属性。即属性名也是不区分大小写的。
由于 __get() __set() 都是public的, 无论将 getTitle() setTitle() 声明为 public, private, protected, 都没有意义,外部同样都是可以访问。所以,所有的属性都是public的。
由于 __get() __set() 都不是static的,因此,没有办法使用static 的属性。

Object的与属性相关的7个方法
__get() 

__set()
__isset() 用于测试属性值是否不为 null
__unset() 用于将属性值设为 null
hasProperty() 用于测试是否有某个属性。
canGetProperty() 测试一个属性是否可读,只要定义了getter,属性即可读。
canSetProperty() 测试一个属性是否可写,只要定义了setter,属性即可写。 

Object和Component
yii\base\Component 继承自 yii\base\Object。Object具有属性功能,Component具有属性、事件、行为的功能。
Yii定位于一个基于组件的框架。Yii几乎所有的核心类都派生于(继承自) yii\base\Component

Application通过配置数组注册组件的过程详见 SL部分https://blog.csdn.net/wuhuagu_wuhuaguo/article/details/80158100


二、事件

(1)事件的定义:在特定的时点,触发执行预先设定的一段代码。一个事件handler本质上就是一段PHP代码,即一个PHP函数。事件是代码解耦的一种方式
当需要使用事件时,请从 yii\base\Component 进行继承。同时,Yii中还有一个与事件紧密相关的yii\base\Event

(2)事件handler-----事件处理程序,负责事件触发后怎么办的问题。
一个事件handler必须具有以下形式:
function ($event) {//$event就是前面提到的 yii\base\Event
}

(3)事件的绑定-----yii\base\Component::on()告诉Yii这个handler是负责处理哪种事件的。把事件和事件handler这绑在一起,当事件被触发时,事件handler执行。
假设被绑定的类继承yii\base\Component,该类的实例化对象是$Component
$Component->on(事件名,事件handler,$data=null,$apppend=true);//$data表示$event->data,$pppend=true则表示该事件被最先执行

(4)事件的触发-----yii\base\Component::trigger()
$Component->trigger(事件名,$event=null);

(5)事件的解除-----yii\base\Component::off()

//移除某个事件
 $Component->off(事件名);
 //移除全部事件
 yii\base\Component::off();


(6)多个事件handler的顺序
要使后加上的事件handler先运行,只需在调用 yii\base\Component::on() 进行绑定时,将第四个参数设为 $append 设为 false 那么这个handler就会被放在数组的最前面了,它就会被最先执行

(7)事件的级别
<7.1>实例级别事件
事件的触发、处理全部都在实例范围内,不与类的其他实例发生关系,也不与其他类、其他实例发生关系。


<7.2>类级别事件用于响应所有类实例的事件。

<7.3>全局级别事件
全局事件,本质上只是一个实例事件罢了。他只是利用了Application实例在整个应用的生命周期中全局可访问的特性,来实现这个全局事件的。
全局事件一个最大优势在于:在任意需要的时候,都可以触发全局事件,也可以在任意必要的时候绑定,或解除一个事件:

Yii::$app->on('bar', function ($event) {
    echo get_class($event->sender);        // 显示当前触发事件的对象的类名称
});
Yii::$app->trigger('bar', new Event(['sender' => $this]));
上面的 Yii::$app->on() 可以在任何地方调用,就可以完成事件的绑定。而 Yii::$app->trigger() 只要在绑定之后的任何时候调用就OK了。


三、行为

(1)行为的定义
使用行为可以在不修改现有类的情况下,对类的功能进行扩充。 通过将行为绑定到一个类,可以使类具有行为本身所定义的属性和方法,就好像类本来就有这些属性和方法一样。 而且不需要写一个新的类去继承或包含现有类。
Yii中的行为,其实是 yii\base\Behavior 类的实例。

(2)写一个行为
定义一个行为,就是写一个 Behavior的子类,子类中包含了所要注入的属性和方法

class MyBehavior extends \yii\base\Behavior
{
    public $prop1;

    public function method1()
    {
        return 'aaa';
    }
}

(3)行为的绑定---静态绑定与动态绑定 
静态绑定2种方式:通过配置文件绑定 或者 在被绑定的类中加behaviors方法

动态绑定 

$Component->attachBehaviors([
    'myBehavior1' => new MyBehavior,  // 这是一个命名行为
    'MyBehavior::className(),          // 这是一个匿名行为
]);

(4)行为的解除 
//解除名为myBehavior的行为
$Component->detachBehavior('myBehavior');
//解除所有绑定好的行为
$Component->detachBehaviors();


四、注意及实例

1、注意

(1)事件/行为可以绑定到任何一个类上,这个类可以是控制器类、模型类、组件类。。只要是类,就可以绑定行为/事件
(2)事件、行为、组件的区别:
看config/web.php的components这块
[
    'class' => 'ClassName',
    'propertyName' => 'propertyValue',
    'on eventName' => $eventHandler,//事件
    'as behaviorName' => $behaviorConfig,//行为
]
说明一个组件里可以绑定事件或行为。一个行为里可以封装属性、方法、事件。组件包括行为,行为包括事件。

2、实例

假设我想在组件类request和普通类Test分别添加自定义的事件和行为,使用静态绑定和动态绑定两种方式。然后在commond命令行下测试效果

config/console文件----------组件类request

'components' => [
    'request' => [
        'on myeventname' =>  ['app\common\MyEvent','event1'],
        'as mybehaviorname' => [
            'class' => \app\common\MyBehavior::class,
            'property1' =>'request-property',//重写自定义行为里的属性,但无法重写行为里的方法
        ],
    ],
]
app\common\Test.php文件----------普通类Test
namespace app\common;
use yii\base\Component;
class Test extends Component
{
    const EVENT_XF = 'event-xf';
//    public function __construct()
//    {
//        $this->on(self::EVENT_XF, ['app\common\MyEvent', 'event1'],'hi!!!');
//    }

//    public function behaviors()
//    {
//        return [
//            'myBehavior' => MyBehavior::className(),
//        ];
//    }

}
app\common\MyEvent.php文件----------自定义事件
namespace app\common;
use yii\base\Event;
class MyEvent
{
    static public function event1 (Event $event) {
        echo '自定义事件,事件内容是:'; var_dump($event);
    }
}
app\common\MyEvent.php文件----------自定义行为
namespace app\common;
use yii\base\Behavior;
use yii\base\Event;
use yii\db\ActiveRecord;
class MyBehavior extends Behavior
{
    
    const BEHAVIOR_EVENT = 'behavior-event';//自定义的一个事件名
    public $property1 = 'This is property in MyBehavior.';//行为的一个属性
    public function method1()  //行为的一个方法
    {
        return 'Method in MyBehavior is called.';
    }

    
    public function events() //重载events() 使得在事件触发时,调用行为中的一些方法
    {
//       // 法1:静态绑定的方式,无法指定data
//        return [
//            ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
//            self::BEHAVIOR_EVENT => ['app\common\MyEvent','event1'],
//        ];
        // 法2:静态绑定的方式,可以指定data
        $this->owner->on(ActiveRecord::EVENT_BEFORE_VALIDATE,[$this,'beforeValidate'],'GGGG');
        $this->owner->on(self::BEHAVIOR_EVENT,['app\common\MyEvent','event1'],'HHHH');
        return [];
    }

    // 注意beforeValidate 是行为的成员函数,而不是绑定的类的成员函数。
    public function beforeValidate(Event $event)
    {
        echo '自定义行为里的触发事件,行为里的事件无法传data内容';var_dump($event);
    }
}

app\commands\TestController.php----------命令行测试

namespace app\commands;
use Yii;
use yii\console\Controller;
use yii\di\Container;
use app\common\Test;
use yii\base\Event;
use app\common\MyBehavior;
use yii\db\ActiveRecord;
class TestController extends Controller
{
    public function actionTest1()
    {
        //事件触发--配置文件console的request组件类加事件
        $event = new Event();
        $event->sender = 'aaa';
        $event->data = 'bbb';//不是在on绑定的时候指定的data不算
        Yii::$app->request->trigger('myeventname',$event);
        echo PHP_EOL;
        //事件触发--普通自定义类加事件
        $TestClass = new Test();
        $event2 = new Event();
        $TestClass->on($TestClass::EVENT_XF, ['app\common\MyEvent', 'event1'],'hi!!!');//此句也可在coutry的construct里绑定。并且此处第3个参数为指定的data
        $event2->sender = 'ccc';
        $TestClass->trigger(Test::EVENT_XF, $event2);
    }


    public function actionTest2()
    {
        //行为中的属性、方法、事件--配置文件web的request组件类加行为
        $event = new event();
        $event->sender = 'wuhuaguo';
        echo Yii::$app->request->property1, PHP_EOL;
        echo Yii::$app->request->method1(), PHP_EOL;
        Yii::$app->request->trigger(ActiveRecord::EVENT_BEFORE_VALIDATE, $event);//行为里的事件
        Yii::$app->request->trigger(MyBehavior::BEHAVIOR_EVENT, $event);//行为里调用外部事件


        echo PHP_EOL;
        //行为中的属性、方法、事件--普通类加行为
        $TestClass = new Test();
        $event2 = new Event();
        $TestClass->attachBehaviors(['myBehavior' => new MyBehavior]);//此处可以改成在country类里使用behaviors()函数绑定
        $event2->sender = 'wuhuaguo2';
        echo $TestClass->property1, PHP_EOL;
        echo $TestClass->method1(), PHP_EOL;
        $TestClass->trigger(ActiveRecord::EVENT_BEFORE_VALIDATE, $event2);//行为里的事件
        $TestClass->trigger(MyBehavior::BEHAVIOR_EVENT, $event2);//行为里调用外部事件
    }
}
测试结果:







猜你喜欢

转载自blog.csdn.net/wuhuagu_wuhuaguo/article/details/80185991