老刘 Yii2 源码学习笔记之 Component 类 - 掸尘 - 博客园
代码改变世界

老刘 Yii2 源码学习笔记之 Component 类

2018-04-16 23:04 by 掸尘, ... 阅读, ... 评论, 收藏, 编辑

 类图关系

 属性与方法

class Component extends BaseObject
{
    
    private $_events = [];
    private $_eventWildcards = [];
    private $_behaviors;

    
    public function __get($name)
    public function __set($name, $value)
    public function __isset($name)
    public function __unset($name)
    public function __call($name, $params)
    public function __clone()

    
    public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
    public function hasMethod($name, $checkBehaviors = true)
    public function behaviors()
    public function hasEventHandlers($name)
        
    public function on($name, $handler, $data = null, $append = true)
    public function off($name, $handler = null)
    public function trigger($name, Event $event = null)
    public function getBehavior($name)
    public function getBehaviors()
    public function attachBehavior($name, $behavior)
    public function attachBehaviors($behaviors)
    public function detachBehavior($name)
    public function detachBehaviors()
    public function ensureBehaviors()
    private function attachBehaviorInternal($name, $behavior)
}

除了className() 方法,BaseObject 父类的方法已经全部重写,因为 BaseObject 只是一个单独的基类,Component 类与 Event 和 Behavior 有更复杂的关联。

 事件

 事件的代码可以先看 on 方法。[$handler, $data] 数组 0 是 handler  2 是 data 参数 

    /**
     * @param $name 事件名称
     * @param $handler 处理回调
     * @param null $data 参数
     * @param bool $append 是否追加
     */
    public function on($name, $handler, $data = null, $append = true)
    {
        //初始化行为
        $this->ensureBehaviors();

        if (strpos($name, '*') !== false) {
            if ($append || empty($this->_eventWildcards[$name])) {
                $this->_eventWildcards[$name][] = [$handler, $data];
            } else {
                array_unshift($this->_eventWildcards[$name], [$handler, $data]);
            }
            return;
        }
        // 如果已经存在事件名称追加
        if ($append || empty($this->_events[$name])) {
            $this->_events[$name][] = [$handler, $data];
        } else {
            array_unshift($this->_events[$name], [$handler, $data]);
        }
    }

 事件的执行,下面的代码可以看到 如果 $event 为 NULL, 会 new Event, 并设置 sender 属性为当前 类, $event->data 为 on 的data 参数,  方法的最后一行, Event::trigger($this, $name, $event)  会执行类级别的事件

 public function trigger($name, Event $event = null)
    {
        $this->ensureBehaviors();

        $eventHandlers = [];
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
            if (StringHelper::matchWildcard($wildcard, $name)) {
                $eventHandlers = array_merge($eventHandlers, $handlers);
            }
        }

        if (!empty($this->_events[$name])) {
            $eventHandlers = array_merge($eventHandlers, $this->_events[$name]);
        }

        if (!empty($eventHandlers)) {
            if ($event === null) {
                $event = new Event();
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            foreach ($eventHandlers as $handler) {
                $event->data = $handler[1];
                call_user_func($handler[0], $event);
                // stop further handling if the event is handled
                if ($event->handled) {
                    return;
                }
            }
        }

        // invoke class-level attached handlers
        Event::trigger($this, $name, $event);
    }

可以举个例子, 可以看一下,全部都执行了,可以把 $event 打印一下看一下data

namespace app\events;
use yii\base\Event;

class MyEvent extends Event {
    public $message;

    function __construct($message)
    {
        parent::__construct();
        $this->message = $message;
    }
}


class SiteController extends Controller
{
    const EVENT_TEST = 'test';

    /**
     * {@inheritdoc}
     */
    function init()
    {
        parent::init(); // TODO: Change the autogenerated stub
        $this->on(self::EVENT_TEST, [new \app\models\EventTest(), 'add'],  ['a', 'b']);
        Event::on(SiteController::className(), self::EVENT_TEST,  function () { echo 'class level event';});
    }
public function actionTest() { $event = new MyEvent('implements'); $this->trigger(self::EVENT_TEST, $event); echo 'done'; } }

  行为

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

class MyClass extends yii\base\Component
{
    // 空的
}

// Step 2: 定义一个行为类,他将绑定到MyClass上
class MyBehavior extends yii\base\Behavior
{
    // 行为的一个属性
    public $property1 = 'This is property in MyBehavior.';

    // 行为的一个方法
    public function method1()
    {
        return 'Method in MyBehavior is called.';
    }
}

$myClass = new MyClass();
$myBehavior = new MyBehavior();

// Step 3: 将行为绑定到类上
$myClass->attachBehavior('myBehavior', $myBehavior);

// Step 4: 访问行为中的属性和方法,就和访问类自身的属性和方法一样
echo $myClass->property1;
echo $myClass->method1();

以上是怎么做到呢, 可以看 Component  里的魔术方法 set、get、call ,对行为对象的属性和方法进行了注入。

public function __get($name)
{
    //其他省略
    // behavior property
    $this->ensureBehaviors();
    foreach ($this->_behaviors as $behavior) {
        if ($behavior->canGetProperty($name)) {
            return $behavior->$name;
        }
    }
    //其他省略
}

public function __set($name, $value)
{
    //其他省略
    // behavior property 
    $this->ensureBehaviors();
    foreach ($this->_behaviors as $behavior) {
        if ($behavior->canSetProperty($name)) {
            $behavior->$name = $value;
            return;
        }
    }
    //其他省略
}

public function __call($name, $params)
{
    $this->ensureBehaviors();
    foreach ($this->_behaviors as $object) {
        if ($object->hasMethod($name)) {
            return call_user_func_array([$object, $name], $params);
        }
    }
    throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
}

 Behavior 类中可以绑定事件, 如下面的代码,继承 Behavior并设置 events 属性. 

public function attach($owner)
    {
        $this->owner = $owner;
        foreach ($this->events() as $event => $handler) {
            $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
        }
    }

举个具体的例子,下面的代码

namespace app\component;

use Yii;
use yii\base\Controller;
use yii\base\Behavior;

class MyBehavior extends Behavior
{
    public $param;

    public function events()
    {
        return [
            Controller::EVENT_BEFORE_ACTION => 'handlerBeforeAction'
        ];
    }

    public function handlerBeforeAction()
    {
        echo '由行为注册的组件事件, 执行 beforeAction <br>';
    }

    public function behaviorMethod()
    {
        echo '行为中的定义的方法';
    }
}


namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\component\MyBehavior;



/**
 * Class  CurdController
 * @package app\controllers
 */
class BehaviorController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => MyBehavior::className(),
                'param' => 'behavior param'
            ]
        ];
    }


    public function actionIndex()
    {
        echo '行为中的属性: '.$this->param.'<br>';
        $this->behaviorMethod();
        echo '<br>';
    }
}

执行结果为:

由行为注册的组件事件, 执行 beforeAction 
行为中的属性: behavior param
行为中的定义的方法

行为设计灵活,  遵循设计原则对修改关闭,对扩展开放. 行为与 traits 区别可以参考文档核心概念. 行为的添加删除具体可以阅读类里面的详细内容.

 属性

属性的设置可以参考魔术方法 set 和 get, 非常方便的像调用属性一样调用方法.