理解Yii2 中的 behavior 过程

分类:Yii2 |

controller 里的action 如果去取属性,若前当类里不含此属性,则去取behavior 里的值,参考 /vendor/yiisoft/yii2/base/Component.php 里的   public function __get($name){} 方法:


/**
* Returns the value of a component property.
* This method will check in the following order and act accordingly:
*
*  - a property defined by a getter: return the getter result
*  - a property of a behavior: return the behavior property value
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $component->property;`.
* @param string $name the property name
* @return mixed the property value or the value of a behavior's property
* @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is write-only.
* @see __set()
*/
public function __get($name)
{
    $getter = 'get' . $name;
    if (method_exists($this, $getter)) {
        // read property, e.g. getName()
        return $this->$getter();
    } else {    # 这里就是,如果当前类没此属性,则取behavior 里的 property
        // behavior property
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $behavior) {
            if ($behavior->canGetProperty($name)) {
                return $behavior->$name;
            }
        }
    }
    if (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);
    }
}



controller 里的action 如果去取方法,若前当类里不含此方法,则去取behavior 里的值,参考 /vendor/yiisoft/yii2/base/Component.php 里的 public function __call($name, $params){} 方法:


/**
* Calls the named method which is not a class method.
*
* This method will check if any attached behavior has
* the named method and will execute it if available.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @return mixed the method return value
* @throws UnknownMethodException when calling unknown method
*/
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()");
}




2. controller 里的 public function behaviors() 是基类存在的,可以重写, 调用参考上例

behavior 里的  public function events()  是基类里存在的, 可以重写调用:

vendor/yiisoft/yii2/base/Behavior.php 里的 public function attach($owner) {} 方法:


/**
 * Attaches the behavior object to the component.
 * The default implementation will set the [[owner]] property
 * and attach event handlers as declared in [[events]].
 * Make sure you call the parent implementation if you override this method.
 * @param Component $owner the component that this behavior is to be attached to.
 */
public function attach($owner)
{
    $this->owner = $owner;
    foreach ($this->events() as $event => $handler) {       # 这里的events 即是 behavior 里的 events 方法
        $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
    }
}



behavior 里的 $owner 即是事件行为的主人



像controller, model 里的  after, before 等的使用将不再是以前的直接调用 this->afterXxx() ,或强制调用, 如果要使用则要自己定义行为 behavior 才可以, 行为里定义 EVENT , 如:

public function events()
{
    return [
        Controller::EVENT_BEFORE_ACTION => "handlerBeforeAction",
        Controller::EVENT_AFTER_ACTION => "handlerAfterAction"
    ];
}


返回数据是一个数组, 数组的  key 即为全局的公用 key, 值即为此事件的执行方法


controller 里的 behavior:

vendor/yiisoft/yii2/base/Component.php

/**
 * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
 */
public function ensureBehaviors()
{
    if ($this->_behaviors === null) {
        $this->_behaviors = [];
        foreach ($this->behaviors() as $name => $behavior) {    # 这里的 $this->behaviors() 方法即是调用的 controller 里的  behaviors() 方法
            $this->attachBehaviorInternal($name, $behavior);
        }
    }
}


调用 attachBehaviorInternal:


private function attachBehaviorInternal($name, $behavior)
{
   if (!($behavior instanceof Behavior)) {
       $behavior = Yii::createObject($behavior);
   }
   if (is_int($name)) {
       $behavior->attach($this);
       $this->_behaviors[] = $behavior;
   } else {
       if (isset($this->_behaviors[$name])) {
           $this->_behaviors[$name]->detach();
       }
       $behavior->attach($this);
       $this->_behaviors[$name] = $behavior;
   }
   return $behavior;
}

调用 createObject:createObject 里兼容了  string 的方式,和数组方式

string格式:yii\log\Logger

数组格式: 这里必须有 class 的 key:

Array
(
    [errorAction] => site/error
    [class] => yii\web\ErrorHandler
)
public static function createObject($type, array $params = [])
{
    if (is_string($type)) {
        return static::$container->get($type, $params);
    } elseif (is_array($type) && isset($type['class'])) {
        $class = $type['class'];
        unset($type['class']);
        return static::$container->get($class, $params, $type);     # 这个container 是一个DI
    } elseif (is_callable($type, true)) {
        return static::$container->invoke($type, $params);
    } elseif (is_array($type)) {
        throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
    } else {
        throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
    }
}


如何理解DI和LOC 呢?

yii\di\Container Object
(
    [_singletons:yii\di\Container:private] => Array
        (
        )
    [_definitions:yii\di\Container:private] => Array
        (
        )
    [_params:yii\di\Container:private] => Array
        (
        )
    [_reflections:yii\di\Container:private] => Array
        (
        )
    [_dependencies:yii\di\Container:private] => Array
        (
        )
    [_events:yii\base\Component:private] => Array
        (
        )
    [_behaviors:yii\base\Component:private] =>
)




梳理下要点


1、Component 将绑定的行为实例存放在自己的 $_behaviors 队列中,看似自己 拿到 了行为的方法或属性,其实也只是配合自己的 __set __get __call 方法在寻找不到时去遍历 $_behaviors 中的行为实例,看谁有此属性或方法而已,是老板和员工的关系



2、在绑定行为的时候,Component 存放的是此行为的一个实例(绑定时会进行实例类型检测,故所有的行为都是 Behavior或子类的实例),绑定时,此行为实例会调用自己的 attach 方法,将行为中为组件定义的事件绑定至此组件,这样便实现了事件绑定。






参考资料: https://my.oschina.net/sallency/blog/685314


阅读( 4853 ) |