理解Yii2 中的 behavior 过程
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