@a5635268
2016-02-08T20:18:58.000000Z
字数 10969
阅读 2299
源码分析与使用笔记
待研究
本篇内容中有以下问题待研究:
可以通过 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]));
事件的玩法就是先设置触发事件,然后再在需要的地方捕获事件
<?php
namespace 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;
}
}
?>
<?php
namespace vendor\animal;
class Dog extends \yii\base\Component{
public function look(){
echo 'i am looking <br />';
}
}
?>
<?php
namespace 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);
}
}
?>
<?php
namespace 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',
]);
<?php
namespace vendor\driver;
class Car{
private $driver;
//其必须是通过Driver这个接口(类)来实例的,如果通过接口的话在注入的时候就可以达到解耦的目的
public function __construct(Driver $driver){
$this -> driver = $driver;
}
public function run(){
$this -> driver -> run();
}
}
?>
<?php
namespace vendor\driver;
interface Driver{
public function run();
}
?>
<?php
namespace vendor\driver;
class WoManDriver implements Driver{
public function run(){
echo '当心,这是一个女司机';
}
}
?>
<?php
namespace vendor\driver;
class ManDriver implements Driver{
public function run(){
echo '放心,这是一个男司机';
}
}
?>
<?php
namespace 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();
依赖注入与服务定位器其实都是一个东西的两种不同表现形式而已,在类似的编程环境中,如果是组件类的话,推荐用服务定位器;如果一些非组件类的话可以用依赖注入;