老刘 Yii2 源码学习笔记之 Component 类
2018-04-16 23:04 掸尘 阅读(754) 评论(0) 编辑 收藏 举报类图关系
属性与方法
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, 非常方便的像调用属性一样调用方法.