@a5635268
2016-02-08T12:18:58.000000Z
字数 10969
阅读 2541
源码分析与使用笔记 待研究
本篇内容中有以下问题待研究:
可以通过 r=gii%2Fdefault%2Fview&id=module 来生成
Module Class:app\modules\actical\Actical
Module ID: actical
# 添加config信息,注意是放在config中作为$config的子数组,别瞎放;'modules' => ['actical' => ['class' => 'app\modules\actical\Actical',# 这个类文件是每个模块的初始化类,可以在里面做初始化操作以及配置加载操作],]
# 在模块内配置,一般都是做子模块的配置namespace app\modules\actical;class Actical extends \yii\base\Module{public $controllerNamespace = 'app\modules\actical\controllers';public function init(){parent::init();$this -> modules = ['actical' => ['class' => 'app\modules\actical\modules\category\Category',]];}}
生成模块的子模块
Module Class:app\modules\actical\modules\category\Category
Module ID: category
// 获取ID为 "forum" 的模块$module = \Yii::$app->getModule('forum');// 获取处理当前请求控制器所属的模块$module = \Yii::$app->controller->module;// 可以去拿params.php里面的数组$maxPostCount = $module->params['maxPostCount'];# 在c层调用子模块操作$module -> runAction('default/index');# 通过url来调用r = forum/default/index
$foo = new Foo;// yii\base\Component::on()// 处理器是全局函数$foo->on(Foo::EVENT_HELLO, 'function_name');// 处理器是对象方法$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);// 处理器是静态类方法$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);// 处理器是匿名函数$foo->on(Foo::EVENT_HELLO, function ($event) {//事件处理逻辑});//↑↑ 而每个处理器都可以如下处理function ($event) {}
// 处理器是全局函数$foo->off(Foo::EVENT_HELLO, 'function_name');// 处理器是对象方法$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);// 处理器是静态类方法$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);// 处理器是匿名函数$foo->off(Foo::EVENT_HELLO, $anonymousFunction);//注意当匿名函数附加到事件后一般不要尝试移除匿名函数,除非你在某处存储了它。以上示例中,假设匿名函数存储为变量 $anonymousFunction 。yii\base\Component::off() //不需要第二个参数就移除全部
$foo->on(Foo::EVENT_HELLO, function ($event) {// 这个处理器将被插入到处理器队列的第一位...传递第四个参数 $append 为假}, $data, false);$foo->on(Foo::EVENT_HELLO, function ($event) {$event->handled = true; //设置 $event 参数的 [yii\base\Event::handled]] 属性为真,其后的处理器调用被停止;});
use Yii;use yii\base\Event;use yii\db\ActiveRecord;Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {Yii::trace(get_class($event->sender) . ' is inserted');});# 类级别的触发器只有类级别的才能捕获,当然类级别的捕获也能捕获普通的触发;Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {echo $event->sender; // 显示 "app\models\Foo", 指向触发事件的类名而不是对象实例。});Event::trigger(Foo::className(), Foo::EVENT_HELLO); //通过Event触发的事件只有Event来捕获
# 所谓全局事件实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例,如应用实例use Yii;use yii\base\Event;use app\components\Foo;Yii::$app->on('bar', function ($event) {echo get_class($event->sender); // 显示 "app\components\Foo"});Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
事件的玩法就是先设置触发事件,然后再在需要的地方捕获事件
<?phpnamespace vendor\animal;class Mourse extends \yii\base\Component{public function run($event){echo $event->message . '<br />';echo 'i am running <br />';//设置 $event 参数的 [yii\base\Event::handled]] 属性为真,其后的处理器调用被停止$event->handled = true;}}?><?phpnamespace vendor\animal;class Dog extends \yii\base\Component{public function look(){echo 'i am looking <br />';}}?><?phpnamespace vendor\animal;use \yii\base\Event;class myEvent extends Event{/** $event 是 yii\base\Event 或其子类的对象里面包含以下内容* yii\base\Event::name:事件名* yii\base\Event::sender:调用 trigger() 方法的对象* yii\base\Event::data:附加事件处理器时传入的数据,默认为空* 各种属性指定;*/public $message;}class Cat extends \yii\base\Component{const EVENT_MIAO = 'miao';public function shot(){$Mourse = new Mourse();echo 'miaomiaomiao~<br />';$me = new myEvent();$me->message = '小猫叫了';//这里的$me可以做为事件处理器的参数传进去$this->trigger(self::EVENT_MIAO , $me);//全局级别的触发\Yii::$app->trigger(self::EVENT_MIAO , new Event(['sender' => new self]));//类级别的触发Event::trigger(self::className() , self::EVENT_MIAO , $me);}}?><?phpnamespace app\controllers;use yii\web\Controller;use vendor\animal\Mourse;use vendor\animal\Cat;use vendor\animal\Dog;use yii\base\Event;class IndexController extends Controller{const EVENT_MIAO = 'miao';public function actionIndex(){$Mourse = new Mourse();$Cat = new Cat();$Dog = new Dog();$Cat2 = new Cat();//为cat绑定一个miao事件,当这个miao事件被触发的时候,就执行$Mourse对象里面的run方法以及$Dog对象的方法$Cat->on(self::EVENT_MIAO , [$Mourse , 'run']);//设置第4参数为假,就放在第一个执行;$Cat->on(self::EVENT_MIAO , [$Dog , 'look'] , null , false);//类级别的捕获Event::on($Cat::className() , self::EVENT_MIAO , [$Mourse , 'run']);//全局级别的捕获,全局级别点的火,只能全局级别来灭;\Yii::$app->on(self::EVENT_MIAO , [$Mourse , 'run']);//当Cat里面的shot方法被调用的时候,那shot方法里面就会触发self::EVENT_MIAO事件;$Cat->shot();$Cat2->shot();}}
行为是 yii\base\Behavior 或其子类的实例。行为,也称为 mixins,也就是类和对象的混合,用新的类混合到当前类(静态)或当前对象(动态)中;
但凡是想在类中注册行为的话,该类必须直接或间接继承自\yii\base\Component;
要静态附加行为,覆写行为要附加的组件类的 yii\base\Component::behaviors() 方法即可。yii\base\Component::behaviors() 方法应该返回行为配置列表。每个行为配置可以是行为类名也可以是配置数组。
Component是controller和model都会继承的类,所以在模型和控制器中可以绑定行为;一旦该类绑定了行为,该类的实例就可以应用行为类中的方法或者属性;而且行为方法是在控制器和模型里面最先调用的方法。
下列示例中的$component是指一个继承了Component的类实例
public function behaviors(){return [// 匿名行为,只有行为类名MyBehavior::className(),// 命名行为,只有行为类名'myBehavior2' => MyBehavior::className(),// 匿名行为,配置数组['class' => MyBehavior::className(),'prop1' => 'value1','prop2' => 'value2', // 可以为行为类中的属性赋值],// 命名行为,配置数组'myBehavior4' => ['class' => MyBehavior::className(),'prop1' => 'value1','prop2' => 'value2',]];}
要动态附加行为,在对应组件里调用 yii\base\Component::attachBehavior() 方法即可
use app\components\MyBehavior;// 附加行为对象$component->attachBehavior('myBehavior1', new MyBehavior);// 附加行为类$component->attachBehavior('myBehavior2', MyBehavior::className());// 附加配置数组$component->attachBehavior('myBehavior3', ['class' => MyBehavior::className(),'prop1' => 'value1','prop2' => 'value2',]);
//该方法在行为类中....public function events(){/** 这里可以捕获事件(on)* 1. 指向行为类的方法名的字符串* 2. 对象或类名和方法名的数组,如 [$object, 'methodName']* 3. 匿名方法*/return [# 捕获到self::EVENT_MIAO事件后就执行本行为类里面的mood方法# 凡是继承自Component类的,只要触发到该事件,就会执行本类中的mood;self::EVENT_MIAO => 'mood'];}
# 除了混合了行为的类,可以使用行为中的属性和方法以外,还可以使用以下方法来使用行为:// 上面定义的行为名称在这个地方就有用了$behavior = $component->getBehavior('myBehavior');$behavior -> foo(); //foo为行为中的方法;# 获得所有行为(是一个数组里面包含了该类所有的行为对象)$behaviors = $component->getBehaviors();
$component->detachBehavior('myBehavior1');$component->detachBehaviors();
namespace app\models\User;use yii\db\ActiveRecord;use yii\behaviors\TimestampBehavior;class User extends ActiveRecord{public function behaviors(){return [['class' => TimestampBehavior::className(),'attributes' => [ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],],],];}}
依赖注入(Dependency Injection,DI)容器就是一个对象use yii\di\Container,它知道怎样初始化并配置对象及其依赖的所有对象。
依赖注入和服务定位器都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。
class Foo{public function __construct(Bar $bar){}}$container = new Container();$foo = $container->get('Foo');//get里面是类名注意使用命名空间// 上面的代码等价于:$bar = new Bar;$foo = new Foo($bar);
use yii\base\Object;class Foo extends Object{public $bar;private $_qux;public function getQux(){return $this->_qux;}public function setQux(Qux $qux){$this->_qux = $qux;}}$container->get('Foo', [], ['bar' => $container->get('Bar'),'qux' => $container->get('Qux'),]);
$container->set('Foo', function () {return new Foo(new Bar);});$foo = $container->get('Foo');
可以用 yii\di\Container::set() 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。
$container = new \yii\di\Container;// 注册一个同类名一样的依赖关系,这个可以省略。$container->set('yii\db\Connection');// 注册一个接口// 当一个类依赖这个接口时,相应的类会被初始化作为依赖对象。$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');// 注册一个别名。// 你可以使用 $container->get('foo') 创建一个 Connection 实例$container->set('foo', 'yii\db\Connection');// 通过配置注册一个类// 通过 get() 初始化时,配置将会被使用。$container->set('yii\db\Connection', ['dsn' => 'mysql:host=127.0.0.1;dbname=demo','username' => 'root','password' => '','charset' => 'utf8',]);// 通过类的配置注册一个别名// 这种情况下,需要通过一个 “class” 元素指定这个类$container->set('db', ['class' => 'yii\db\Connection','dsn' => 'mysql:host=127.0.0.1;dbname=demo','username' => 'root','password' => '','charset' => 'utf8',]);// 注册一个 PHP 回调// 每次调用 $container->get('db') 时,回调函数都会被执行。$container->set('db', function ($container, $params, $config) {return new \yii\db\Connection($config);});// 注册一个组件实例// $container->get('pageCache') 每次被调用时都会返回同一个实例。$container->set('pageCache', new FileCache);//注册一个单例的依赖关系$container->setSingleton('yii\db\Connection', ['dsn' => 'mysql:host=127.0.0.1;dbname=demo','username' => 'root','password' => '','charset' => 'utf8',]);
<?phpnamespace vendor\driver;class Car{private $driver;//其必须是通过Driver这个接口(类)来实例的,如果通过接口的话在注入的时候就可以达到解耦的目的public function __construct(Driver $driver){$this -> driver = $driver;}public function run(){$this -> driver -> run();}}?><?phpnamespace vendor\driver;interface Driver{public function run();}?><?phpnamespace vendor\driver;class WoManDriver implements Driver{public function run(){echo '当心,这是一个女司机';}}?><?phpnamespace vendor\driver;class ManDriver implements Driver{public function run(){echo '放心,这是一个男司机';}}?><?phpnamespace app\controllers;use yii\web\Controller;use yii\di\Container;class IndexController extends Controller{public function actionIndex(){$container = new Container();//设置一个别名$container -> set('car','vendor\driver\Car');//如果car中的构造方法传入的对象必须是由某个接口而实例的,就还需要使用set方法,否则不需要(如果是类的话注意命名空间的规范既可);$container -> set('vendor\driver\Driver','vendor\driver\WoManDriver');//↓↓ 先找到别名,然后实例别名,如果别名不能实例(是个接口),那再通过set注册其依赖关系为接口下面的某个具体的类(究竟是哪个具体的类,可以根据业务逻辑来判断)$car = $container -> get('car');$car -> run();}}
// "db" 是前面定义过的一个别名$db = $container->get('db');// 等价于: $engine = new \app\components\SearchEngine($arg1,$arg2,$arg3 );$engine = $container->get('app\components\SearchEngine', [$arg1,$arg2,$arg3], ['type' => 1]);# question: 但是这里的 ['type' => 1] ??是什么?无解啊
服务定位器是在应用主体中的一个属性对象,该对象是 yii\di\ServiceLocator 或其子类的一个实例。
最常用的服务定位器是 application(应用)对象,可以通过 \Yii::$app 访问。它所提供的服务被称为 application components(应用组件),比如: request 、 response 、 urlManager 组件。这些组件在 config/web.php中components中配置
除了 application 对象,每个模块对象本身也是一个服务定位器
use yii\di\ServiceLocator;use yii\caching\FileCache;$locator = new ServiceLocator;// 通过一个可用于创建该组件的类名,注册 "cache" (缓存)组件。$locator->set('cache', 'yii\caching\ApcCache');// 通过一个可用于创建该组件的配置数组,注册 "db" (数据库)组件。$locator->set('db', ['class' => 'yii\db\Connection','dsn' => 'mysql:host=localhost;dbname=demo','username' => 'root','password' => '',]);// 通过一个能返回该组件的匿名函数,注册 "search" 组件。$locator->set('search', function () {return new app\components\SolrService;});// 用组件注册 "pageCache" 组件$locator->set('pageCache', new FileCache);// 一旦组件被注册成功,你可以任选以下两种方式之一,通过它的 ID 访问它:$cache = $locator->get('cache');// 或者$cache = $locator->cache;# 你可以通过 yii\di\ServiceLocator::has() 检查某组件 ID 是否被注册。若你用一个无效的 ID 调用yii\di\ServiceLocator::get(),则会抛出一个异常。
$locator = new yii\di\ServiceLocator;//设置一个别名//locator中的set只负责设置别名$locator -> set('car','vendor\driver\Car');//然后通过全局DI容器设置依赖关系\Yii::$container -> set('vendor\driver\Driver','vendor\driver\WoManDriver');//$car = $locator -> get('car');$car = $locator -> car;$car -> run();
直接配置到web.php中
return[// ...'components' => ['db' => ['class' => 'yii\db\Connection','dsn' => 'mysql:host=localhost;dbname=demo','username' => 'root','password' => '',],'cache' => 'yii\caching\ApcCache','search' => function () {return new app\components\SolrService;},],];
// 首先在web.php中的components数组加上以下元素'car' => ['class' => 'vendor\driver\Car']//然后通过全局DI容器设置依赖关系(非接口情况下可以省略)\Yii::$container -> set('vendor\driver\Driver','vendor\driver\WoManDriver');//$car = $locator -> get('car');$car = \Yii::$app -> car;$car -> run();
依赖注入与服务定位器其实都是一个东西的两种不同表现形式而已,在类似的编程环境中,如果是组件类的话,推荐用服务定位器;如果一些非组件类的话可以用依赖注入;