理解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