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

11 篇文章 0 订阅
1 篇文章 0 订阅

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

  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.  
  7. public function init()
  8. {
  9. parent::init();
  10. $this -> modules = [
  11. 'actical' => [
  12. 'class' => 'app\modules\actical\modules\category\Category',
  13. ]
  14. ];
  15. }
  16. }

生成模块的子模块

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

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

事件

用on绑定事件

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

用off解除事件

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

绑定多个事件处理器

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

类级别的事件处理器

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

全局事件

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

行为

行为是 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. // 命名行为,只有行为类名
  8. 'myBehavior2' => MyBehavior::className(),
  9.  
  10. // 匿名行为,配置数组
  11. [
  12. 'class' => MyBehavior::className(),
  13. 'prop1' => 'value1',
  14. 'prop2' => 'value2', // 可以为行为类中的属性赋值
  15. ],
  16.  
  17. // 命名行为,配置数组
  18. 'myBehavior4' => [
  19. 'class' => MyBehavior::className(),
  20. 'prop1' => 'value1',
  21. 'prop2' => 'value2',
  22. ]
  23. ];
  24. }

动态混合到类中

要动态附加行为,在对应组件里调用 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. # 获得所有行为(是一个数组里面包含了该类所有的行为对象)
  7. $behaviors = $component->getBehaviors();

删除行为

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

自动更新时间戳的行为TimestampBehavior

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

依赖注入容器

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

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

构造方法注入

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

Setter 和属性注入

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

PHP 回调注入

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

注册依赖关系

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

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

Example

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

解决依赖关系

 
  1. // "db" 是前面定义过的一个别名
  2. $db = $container->get('db');
  3.  
  4. // 等价于: $engine = new \app\components\SearchEngine($arg1,$arg2,$arg3 );
  5. $engine = $container->get('app\components\SearchEngine', [$arg1,$arg2,$arg3], ['type' => 1]);
  6. # 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.  
  20. // 一旦组件被注册成功,你可以任选以下两种方式之一,通过它的 ID 访问它:
  21. $cache = $locator->get('cache');
  22. // 或者
  23. $cache = $locator->cache;
  24.  
  25. # 你可以通过 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.  
  6. //然后通过全局DI容器设置依赖关系(非接口情况下可以省略)
  7. \Yii::$container -> set('vendor\driver\Driver','vendor\driver\WoManDriver');
  8. //$car = $locator -> get('car');
  9. $car = \Yii::$app -> car;
  10. $car -> run();

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

参考文献:

https://www.zybuluo.com/a5635268/note/264248

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值