依赖注入容器
依赖注入(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);
Setter 和属性注入
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'),
]);
PHP 回调注入
$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',
]);
Example
<?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();
依赖注入与服务定位器其实都是一个东西的两种不同表现形式而已,在类似的编程环境中,如果是组件类的话,推荐用服务定位器;如果一些非组件类的话可以用依赖注入;