[关闭]
@a5635268 2016-02-08T20:18:58.000000Z 字数 10969 阅读 2299

YII框架之模块,事件,行为,依赖注入容器,服务定位器

源码分析与使用笔记 待研究


本篇内容中有以下问题待研究:

  1. 依赖注入容器中的 Setter 和属性注入 以及 PHP 回调注入 的应用场景
  2. 然后行为,事件,依赖注入容器等实现的原理是什么?
  3. 事件 on的第三参数有什么用?
  4. 自动更新时间戳的行为TimestampBehavior是什么原理?

模块

可以通过 r=gii%2Fdefault%2Fview&id=module 来生成

Module Class:app\modules\actical\Actical
Module ID: actical

  1. # 添加config信息,注意是放在config中作为$config的子数组,别瞎放;
  2. 'modules' => [
  3. 'actical' => [
  4. 'class' => 'app\modules\actical\Actical',
  5. # 这个类文件是每个模块的初始化类,可以在里面做初始化操作以及配置加载操作
  6. ],
  7. ]
  1. # 在模块内配置,一般都是做子模块的配置
  2. namespace app\modules\actical;
  3. class Actical extends \yii\base\Module
  4. {
  5. public $controllerNamespace = 'app\modules\actical\controllers';
  6. public function init()
  7. {
  8. parent::init();
  9. $this -> modules = [
  10. 'actical' => [
  11. 'class' => 'app\modules\actical\modules\category\Category',
  12. ]
  13. ];
  14. }
  15. }

生成模块的子模块

Module Class:app\modules\actical\modules\category\Category
Module ID: category

  1. // 获取ID为 "forum" 的模块
  2. $module = \Yii::$app->getModule('forum');
  3. // 获取处理当前请求控制器所属的模块
  4. $module = \Yii::$app->controller->module;
  5. // 可以去拿params.php里面的数组
  6. $maxPostCount = $module->params['maxPostCount'];
  7. # 在c层调用子模块操作
  8. $module -> runAction('default/index');
  9. # 通过url来调用
  10. r = forum/default/index

事件

用on绑定事件

  1. $foo = new Foo;
  2. // yii\base\Component::on()
  3. // 处理器是全局函数
  4. $foo->on(Foo::EVENT_HELLO, 'function_name');
  5. // 处理器是对象方法
  6. $foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
  7. // 处理器是静态类方法
  8. $foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
  9. // 处理器是匿名函数
  10. $foo->on(Foo::EVENT_HELLO, function ($event) {
  11. //事件处理逻辑
  12. });
  13. //↑↑ 而每个处理器都可以如下处理
  14. function ($event) {
  15. }

用off解除事件

  1. // 处理器是全局函数
  2. $foo->off(Foo::EVENT_HELLO, 'function_name');
  3. // 处理器是对象方法
  4. $foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);
  5. // 处理器是静态类方法
  6. $foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
  7. // 处理器是匿名函数
  8. $foo->off(Foo::EVENT_HELLO, $anonymousFunction);
  9. //注意当匿名函数附加到事件后一般不要尝试移除匿名函数,除非你在某处存储了它。以上示例中,假设匿名函数存储为变量 $anonymousFunction 。
  10. yii\base\Component::off() //不需要第二个参数就移除全部

绑定多个事件处理器

  1. $foo->on(Foo::EVENT_HELLO, function ($event) {
  2. // 这个处理器将被插入到处理器队列的第一位...传递第四个参数 $append 为假
  3. }, $data, false);
  4. $foo->on(Foo::EVENT_HELLO, function ($event) {
  5. $event->handled = true; //设置 $event 参数的 [yii\base\Event::handled]] 属性为真,其后的处理器调用被停止;
  6. });

类级别的事件处理器

  1. use Yii;
  2. use yii\base\Event;
  3. use yii\db\ActiveRecord;
  4. Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
  5. Yii::trace(get_class($event->sender) . ' is inserted');
  6. });
  7. # 类级别的触发器只有类级别的才能捕获,当然类级别的捕获也能捕获普通的触发;
  8. Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
  9. echo $event->sender; // 显示 "app\models\Foo", 指向触发事件的类名而不是对象实例。
  10. });
  11. Event::trigger(Foo::className(), Foo::EVENT_HELLO); //通过Event触发的事件只有Event来捕获

全局事件

  1. # 所谓全局事件实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例,如应用实例
  2. use Yii;
  3. use yii\base\Event;
  4. use app\components\Foo;
  5. Yii::$app->on('bar', function ($event) {
  6. echo get_class($event->sender); // 显示 "app\components\Foo"
  7. });
  8. Yii::$app->trigger('bar', new Event(['sender' => new Foo]));

总结

事件的玩法就是先设置触发事件,然后再在需要的地方捕获事件

  1. <?php
  2. namespace vendor\animal;
  3. class Mourse extends \yii\base\Component{
  4. public function run($event){
  5. echo $event->message . '<br />';
  6. echo 'i am running <br />';
  7. //设置 $event 参数的 [yii\base\Event::handled]] 属性为真,其后的处理器调用被停止
  8. $event->handled = true;
  9. }
  10. }
  11. ?>
  12. <?php
  13. namespace vendor\animal;
  14. class Dog extends \yii\base\Component{
  15. public function look(){
  16. echo 'i am looking <br />';
  17. }
  18. }
  19. ?>
  20. <?php
  21. namespace vendor\animal;
  22. use \yii\base\Event;
  23. class myEvent extends Event{
  24. /*
  25. * $event 是 yii\base\Event 或其子类的对象里面包含以下内容
  26. * yii\base\Event::name:事件名
  27. * yii\base\Event::sender:调用 trigger() 方法的对象
  28. * yii\base\Event::data:附加事件处理器时传入的数据,默认为空
  29. * 各种属性指定;
  30. */
  31. public $message;
  32. }
  33. class Cat extends \yii\base\Component{
  34. const EVENT_MIAO = 'miao';
  35. public function shot(){
  36. $Mourse = new Mourse();
  37. echo 'miaomiaomiao~<br />';
  38. $me = new myEvent();
  39. $me->message = '小猫叫了';
  40. //这里的$me可以做为事件处理器的参数传进去
  41. $this->trigger(self::EVENT_MIAO , $me);
  42. //全局级别的触发
  43. \Yii::$app->trigger(self::EVENT_MIAO , new Event(['sender' => new self]));
  44. //类级别的触发
  45. Event::trigger(self::className() , self::EVENT_MIAO , $me);
  46. }
  47. }
  48. ?>
  49. <?php
  50. namespace app\controllers;
  51. use yii\web\Controller;
  52. use vendor\animal\Mourse;
  53. use vendor\animal\Cat;
  54. use vendor\animal\Dog;
  55. use yii\base\Event;
  56. class IndexController extends Controller{
  57. const EVENT_MIAO = 'miao';
  58. public function actionIndex(){
  59. $Mourse = new Mourse();
  60. $Cat = new Cat();
  61. $Dog = new Dog();
  62. $Cat2 = new Cat();
  63. //为cat绑定一个miao事件,当这个miao事件被触发的时候,就执行$Mourse对象里面的run方法以及$Dog对象的方法
  64. $Cat->on(self::EVENT_MIAO , [$Mourse , 'run']);
  65. //设置第4参数为假,就放在第一个执行;
  66. $Cat->on(self::EVENT_MIAO , [$Dog , 'look'] , null , false);
  67. //类级别的捕获
  68. Event::on($Cat::className() , self::EVENT_MIAO , [$Mourse , 'run']);
  69. //全局级别的捕获,全局级别点的火,只能全局级别来灭;
  70. \Yii::$app->on(self::EVENT_MIAO , [$Mourse , 'run']);
  71. //当Cat里面的shot方法被调用的时候,那shot方法里面就会触发self::EVENT_MIAO事件;
  72. $Cat->shot();
  73. $Cat2->shot();
  74. }
  75. }

行为

行为是 yii\base\Behavior 或其子类的实例。行为,也称为 mixins,也就是类和对象的混合,用新的类混合到当前类(静态)或当前对象(动态)中;
但凡是想在类中注册行为的话,该类必须直接或间接继承自\yii\base\Component;

静态混合到类中

要静态附加行为,覆写行为要附加的组件类的 yii\base\Component::behaviors() 方法即可。yii\base\Component::behaviors() 方法应该返回行为配置列表。每个行为配置可以是行为类名也可以是配置数组。

Component是controller和model都会继承的类,所以在模型和控制器中可以绑定行为;一旦该类绑定了行为,该类的实例就可以应用行为类中的方法或者属性;而且行为方法是在控制器和模型里面最先调用的方法。

下列示例中的$component是指一个继承了Component的类实例

  1. public function behaviors()
  2. {
  3. return [
  4. // 匿名行为,只有行为类名
  5. MyBehavior::className(),
  6. // 命名行为,只有行为类名
  7. 'myBehavior2' => MyBehavior::className(),
  8. // 匿名行为,配置数组
  9. [
  10. 'class' => MyBehavior::className(),
  11. 'prop1' => 'value1',
  12. 'prop2' => 'value2', // 可以为行为类中的属性赋值
  13. ],
  14. // 命名行为,配置数组
  15. 'myBehavior4' => [
  16. 'class' => MyBehavior::className(),
  17. 'prop1' => 'value1',
  18. 'prop2' => 'value2',
  19. ]
  20. ];
  21. }

动态混合到类中

要动态附加行为,在对应组件里调用 yii\base\Component::attachBehavior() 方法即可

  1. use app\components\MyBehavior;
  2. // 附加行为对象
  3. $component->attachBehavior('myBehavior1', new MyBehavior);
  4. // 附加行为类
  5. $component->attachBehavior('myBehavior2', MyBehavior::className());
  6. // 附加配置数组
  7. $component->attachBehavior('myBehavior3', [
  8. 'class' => MyBehavior::className(),
  9. 'prop1' => 'value1',
  10. 'prop2' => 'value2',
  11. ]);

行为处理事件

  1. //该方法在行为类中....
  2. public function events(){
  3. /*
  4. * 这里可以捕获事件(on)
  5. * 1. 指向行为类的方法名的字符串
  6. * 2. 对象或类名和方法名的数组,如 [$object, 'methodName']
  7. * 3. 匿名方法
  8. */
  9. return [
  10. # 捕获到self::EVENT_MIAO事件后就执行本行为类里面的mood方法
  11. # 凡是继承自Component类的,只要触发到该事件,就会执行本类中的mood;
  12. self::EVENT_MIAO => 'mood'
  13. ];
  14. }

行为的使用

  1. # 除了混合了行为的类,可以使用行为中的属性和方法以外,还可以使用以下方法来使用行为:
  2. // 上面定义的行为名称在这个地方就有用了
  3. $behavior = $component->getBehavior('myBehavior');
  4. $behavior -> foo(); //foo为行为中的方法;
  5. # 获得所有行为(是一个数组里面包含了该类所有的行为对象)
  6. $behaviors = $component->getBehaviors();

删除行为

  1. $component->detachBehavior('myBehavior1');
  2. $component->detachBehaviors();

自动更新时间戳的行为TimestampBehavior

  1. namespace app\models\User;
  2. use yii\db\ActiveRecord;
  3. use yii\behaviors\TimestampBehavior;
  4. class User extends ActiveRecord
  5. {
  6. public function behaviors()
  7. {
  8. return [
  9. [
  10. 'class' => TimestampBehavior::className(),
  11. 'attributes' => [
  12. ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
  13. ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
  14. ],
  15. ],
  16. ];
  17. }
  18. }

依赖注入容器

依赖注入(Dependency Injection,DI)容器就是一个对象use yii\di\Container,它知道怎样初始化并配置对象及其依赖的所有对象。

依赖注入和服务定位器都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。

构造方法注入

  1. class Foo
  2. {
  3. public function __construct(Bar $bar)
  4. {
  5. }
  6. }
  7. $container = new Container();
  8. $foo = $container->get('Foo');//get里面是类名注意使用命名空间
  9. // 上面的代码等价于:
  10. $bar = new Bar;
  11. $foo = new Foo($bar);

Setter 和属性注入

  1. use yii\base\Object;
  2. class Foo extends Object
  3. {
  4. public $bar;
  5. private $_qux;
  6. public function getQux()
  7. {
  8. return $this->_qux;
  9. }
  10. public function setQux(Qux $qux)
  11. {
  12. $this->_qux = $qux;
  13. }
  14. }
  15. $container->get('Foo', [], [
  16. 'bar' => $container->get('Bar'),
  17. 'qux' => $container->get('Qux'),
  18. ]);

PHP 回调注入

  1. $container->set('Foo', function () {
  2. return new Foo(new Bar);
  3. });
  4. $foo = $container->get('Foo');

注册依赖关系

可以用 yii\di\Container::set() 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。

  1. $container = new \yii\di\Container;
  2. // 注册一个同类名一样的依赖关系,这个可以省略。
  3. $container->set('yii\db\Connection');
  4. // 注册一个接口
  5. // 当一个类依赖这个接口时,相应的类会被初始化作为依赖对象。
  6. $container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');
  7. // 注册一个别名。
  8. // 你可以使用 $container->get('foo') 创建一个 Connection 实例
  9. $container->set('foo', 'yii\db\Connection');
  10. // 通过配置注册一个类
  11. // 通过 get() 初始化时,配置将会被使用。
  12. $container->set('yii\db\Connection', [
  13. 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
  14. 'username' => 'root',
  15. 'password' => '',
  16. 'charset' => 'utf8',
  17. ]);
  18. // 通过类的配置注册一个别名
  19. // 这种情况下,需要通过一个 “class” 元素指定这个类
  20. $container->set('db', [
  21. 'class' => 'yii\db\Connection',
  22. 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
  23. 'username' => 'root',
  24. 'password' => '',
  25. 'charset' => 'utf8',
  26. ]);
  27. // 注册一个 PHP 回调
  28. // 每次调用 $container->get('db') 时,回调函数都会被执行。
  29. $container->set('db', function ($container, $params, $config) {
  30. return new \yii\db\Connection($config);
  31. });
  32. // 注册一个组件实例
  33. // $container->get('pageCache') 每次被调用时都会返回同一个实例。
  34. $container->set('pageCache', new FileCache);
  35. //注册一个单例的依赖关系
  36. $container->setSingleton('yii\db\Connection', [
  37. 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
  38. 'username' => 'root',
  39. 'password' => '',
  40. 'charset' => 'utf8',
  41. ]);

Example

  1. <?php
  2. namespace vendor\driver;
  3. class Car{
  4. private $driver;
  5. //其必须是通过Driver这个接口(类)来实例的,如果通过接口的话在注入的时候就可以达到解耦的目的
  6. public function __construct(Driver $driver){
  7. $this -> driver = $driver;
  8. }
  9. public function run(){
  10. $this -> driver -> run();
  11. }
  12. }
  13. ?>
  14. <?php
  15. namespace vendor\driver;
  16. interface Driver{
  17. public function run();
  18. }
  19. ?>
  20. <?php
  21. namespace vendor\driver;
  22. class WoManDriver implements Driver{
  23. public function run(){
  24. echo '当心,这是一个女司机';
  25. }
  26. }
  27. ?>
  28. <?php
  29. namespace vendor\driver;
  30. class ManDriver implements Driver{
  31. public function run(){
  32. echo '放心,这是一个男司机';
  33. }
  34. }
  35. ?>
  36. <?php
  37. namespace app\controllers;
  38. use yii\web\Controller;
  39. use yii\di\Container;
  40. class IndexController extends Controller{
  41. public function actionIndex(){
  42. $container = new Container();
  43. //设置一个别名
  44. $container -> set('car','vendor\driver\Car');
  45. //如果car中的构造方法传入的对象必须是由某个接口而实例的,就还需要使用set方法,否则不需要(如果是类的话注意命名空间的规范既可);
  46. $container -> set('vendor\driver\Driver','vendor\driver\WoManDriver');
  47. //↓↓ 先找到别名,然后实例别名,如果别名不能实例(是个接口),那再通过set注册其依赖关系为接口下面的某个具体的类(究竟是哪个具体的类,可以根据业务逻辑来判断)
  48. $car = $container -> get('car');
  49. $car -> run();
  50. }
  51. }

解决依赖关系

  1. // "db" 是前面定义过的一个别名
  2. $db = $container->get('db');
  3. // 等价于: $engine = new \app\components\SearchEngine($arg1,$arg2,$arg3 );
  4. $engine = $container->get('app\components\SearchEngine', [$arg1,$arg2,$arg3], ['type' => 1]);
  5. # question: 但是这里的 ['type' => 1] ??是什么?无解啊

服务定位器

服务定位器是在应用主体中的一个属性对象,该对象是 yii\di\ServiceLocator 或其子类的一个实例。

最常用的服务定位器是 application(应用)对象,可以通过 \Yii::$app 访问。它所提供的服务被称为 application components(应用组件),比如: request 、 response 、 urlManager 组件。这些组件在 config/web.php中components中配置

除了 application 对象,每个模块对象本身也是一个服务定位器

动态注册

  1. use yii\di\ServiceLocator;
  2. use yii\caching\FileCache;
  3. $locator = new ServiceLocator;
  4. // 通过一个可用于创建该组件的类名,注册 "cache" (缓存)组件。
  5. $locator->set('cache', 'yii\caching\ApcCache');
  6. // 通过一个可用于创建该组件的配置数组,注册 "db" (数据库)组件。
  7. $locator->set('db', [
  8. 'class' => 'yii\db\Connection',
  9. 'dsn' => 'mysql:host=localhost;dbname=demo',
  10. 'username' => 'root',
  11. 'password' => '',
  12. ]);
  13. // 通过一个能返回该组件的匿名函数,注册 "search" 组件。
  14. $locator->set('search', function () {
  15. return new app\components\SolrService;
  16. });
  17. // 用组件注册 "pageCache" 组件
  18. $locator->set('pageCache', new FileCache);
  19. // 一旦组件被注册成功,你可以任选以下两种方式之一,通过它的 ID 访问它:
  20. $cache = $locator->get('cache');
  21. // 或者
  22. $cache = $locator->cache;
  23. # 你可以通过 yii\di\ServiceLocator::has() 检查某组件 ID 是否被注册。若你用一个无效的 ID 调用yii\di\ServiceLocator::get(),则会抛出一个异常。
  1. $locator = new yii\di\ServiceLocator;
  2. //设置一个别名
  3. //locator中的set只负责设置别名
  4. $locator -> set('car','vendor\driver\Car');
  5. //然后通过全局DI容器设置依赖关系
  6. \Yii::$container -> set('vendor\driver\Driver','vendor\driver\WoManDriver');
  7. //$car = $locator -> get('car');
  8. $car = $locator -> car;
  9. $car -> run();

静态注册

直接配置到web.php中

  1. return
  2. [
  3. // ...
  4. 'components' => [
  5. 'db' => [
  6. 'class' => 'yii\db\Connection',
  7. 'dsn' => 'mysql:host=localhost;dbname=demo',
  8. 'username' => 'root',
  9. 'password' => '',
  10. ],
  11. 'cache' => 'yii\caching\ApcCache',
  12. 'search' => function () {
  13. return new app\components\SolrService;
  14. },
  15. ],
  16. ];
  1. // 首先在web.php中的components数组加上以下元素
  2. 'car' => [
  3. 'class' => 'vendor\driver\Car'
  4. ]
  5. //然后通过全局DI容器设置依赖关系(非接口情况下可以省略)
  6. \Yii::$container -> set('vendor\driver\Driver','vendor\driver\WoManDriver');
  7. //$car = $locator -> get('car');
  8. $car = \Yii::$app -> car;
  9. $car -> run();

依赖注入与服务定位器其实都是一个东西的两种不同表现形式而已,在类似的编程环境中,如果是组件类的话,推荐用服务定位器;如果一些非组件类的话可以用依赖注入;

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注