|
|
|
@ -1,13 +1,13 @@
|
|
|
|
|
依赖注入容器 |
|
|
|
|
========== |
|
|
|
|
依赖注入容器(Dependency Injection Container) |
|
|
|
|
=========================================== |
|
|
|
|
|
|
|
|
|
依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。 |
|
|
|
|
[Martin 的文章](http://martinfowler.com/articles/injection.html) 已经解释了 DI 容器为什么很有用。 |
|
|
|
|
这里我们主要讲解 Yii 提供的 DI 容器的使用方法。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
依赖注入 <span id="dependency-injection"></span> |
|
|
|
|
------- |
|
|
|
|
依赖注入(Dependency Injection) <span id="dependency-injection"></span> |
|
|
|
|
----------------------------- |
|
|
|
|
|
|
|
|
|
Yii 通过 [[yii\di\Container]] 类提供 DI 容器特性。 |
|
|
|
|
它支持如下几种类型的依赖注入: |
|
|
|
@ -18,7 +18,7 @@ Yii 通过 [[yii\di\Container]] 类提供 DI 容器特性。
|
|
|
|
|
* PHP 回调注入. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 构造方法注入 <span id="constructor-injection"></span> |
|
|
|
|
### 构造方法注入(Constructor Injection) <span id="constructor-injection"></span> |
|
|
|
|
|
|
|
|
|
在参数类型提示的帮助下,DI 容器实现了构造方法注入。当容器被用于创建一个新对象时, |
|
|
|
|
类型提示会告诉它要依赖什么类或接口。 |
|
|
|
@ -40,7 +40,7 @@ $foo = new Foo($bar);
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 方法注入 <span id="method-injection"></span> |
|
|
|
|
### 方法注入(Method Injection) <span id="method-injection"></span> |
|
|
|
|
|
|
|
|
|
通常,类的依赖关系传递给构造函数,并且在整个生命周期中都可以在类内部使用。 |
|
|
|
|
通过方法注入,可以提供仅由类的单个方法需要的依赖关系, |
|
|
|
@ -70,7 +70,7 @@ $obj = new MyClass(/*...*/);
|
|
|
|
|
Yii::$container->invoke([$obj, 'doSomething'], ['param1' => 42]); // $something will be provided by the DI container |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### Setter 和属性注入 <span id="setter-and-property-injection"></span> |
|
|
|
|
### Setter 和属性注入(Setter and Property Injection) <span id="setter-and-property-injection"></span> |
|
|
|
|
|
|
|
|
|
Setter 和属性注入是通过[配置](concept-configurations.md)提供支持的。 |
|
|
|
|
当注册一个依赖或创建一个新对象时,你可以提供一个配置, |
|
|
|
@ -109,7 +109,7 @@ $container->get('Foo', [], [
|
|
|
|
|
否则,将在创建对象*后*应用该配置。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### PHP 回调注入 <span id="php-callable-injection"></span> |
|
|
|
|
### PHP 回调注入(PHP Callable Injection) <span id="php-callable-injection"></span> |
|
|
|
|
|
|
|
|
|
在这种情况下,容器将使用已注册的 PHP 回调来构建类的新实例。 |
|
|
|
|
每次调用 [[yii\di\Container::get()]] ,相应的回调将被调用。 |
|
|
|
@ -119,7 +119,7 @@ $container->get('Foo', [], [
|
|
|
|
|
```php |
|
|
|
|
$container->set('Foo', function () { |
|
|
|
|
$foo = new Foo(new Bar); |
|
|
|
|
// ... other initializations ... |
|
|
|
|
// ... 其他初始化 ... |
|
|
|
|
return $foo; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -134,7 +134,7 @@ class FooBuilder
|
|
|
|
|
public static function build() |
|
|
|
|
{ |
|
|
|
|
$foo = new Foo(new Bar); |
|
|
|
|
// ... other initializations ... |
|
|
|
|
// ... 其他初始化 ... |
|
|
|
|
return $foo; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -147,8 +147,8 @@ $foo = $container->get('Foo');
|
|
|
|
|
这样做的话,想要配置 `Foo` 类的人不再需要知道它是如何构建的。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注册依赖关系 <span id="registering-dependencies"></span> |
|
|
|
|
------------------------ |
|
|
|
|
注册依赖关系(Registering Dependencies) <span id="registering-dependencies"></span> |
|
|
|
|
------------------------------------ |
|
|
|
|
|
|
|
|
|
可以用 [[yii\di\Container::set()]] 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。 |
|
|
|
|
依赖关系名称可以是一个类名,一个接口名或一个别名。 |
|
|
|
@ -216,8 +216,8 @@ $container->setSingleton('yii\db\Connection', [
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
解决依赖关系 <span id="resolving-dependencies"></span> |
|
|
|
|
---------------------- |
|
|
|
|
解决依赖关系(Resolving Dependencies) <span id="resolving-dependencies"></span> |
|
|
|
|
---------------------------------- |
|
|
|
|
|
|
|
|
|
注册依赖关系后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系, |
|
|
|
|
将依赖实例化并注入新创建的对象。依赖关系的解决是递归的, |
|
|
|
@ -307,8 +307,8 @@ $lister = new UserLister($finder);
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
实践中的运用 <span id="practical-usage"></span> |
|
|
|
|
--------------- |
|
|
|
|
实践中的运用(Practical Usage) <span id="practical-usage"></span> |
|
|
|
|
--------------------------- |
|
|
|
|
|
|
|
|
|
当在应用程序的[入口脚本](structure-entry-scripts.md)中引入 `Yii.php` 文件时, |
|
|
|
|
Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 [[Yii::$container]] 访问。 |
|
|
|
@ -372,16 +372,16 @@ class HotelController extends Controller
|
|
|
|
|
现在如果你再次访问这个控制器,一个 `app\components\BookingService` |
|
|
|
|
的实例就会被创建并被作为第三个参数注入到控制器的构造器中。 |
|
|
|
|
|
|
|
|
|
高级实用性 <span id="advanced-practical-usage"></span> |
|
|
|
|
--------- |
|
|
|
|
高级实用性(Advanced Practical Usage) <span id="advanced-practical-usage"></span> |
|
|
|
|
----------------------------------- |
|
|
|
|
|
|
|
|
|
比如说我们在 API 应用方面工作: |
|
|
|
|
|
|
|
|
|
- `app\components\Request` class that extends `yii\web\Request` and provides additional functionality |
|
|
|
|
- `app\components\Response` class that extends `yii\web\Response` and should have `format` property |
|
|
|
|
set to `json` on creation |
|
|
|
|
- `app\storage\FileStorage` and `app\storage\DocumentsReader` classes that implement some logic on |
|
|
|
|
working with documents that are located in some file storage: |
|
|
|
|
- `app\components\Request` 类继承了 `yii\web\Request` 并提供了额外的功能 |
|
|
|
|
- `app\components\Response` 类继承了 `yii\web\Response` 并且在创建时应该将 `format` |
|
|
|
|
属性设置为 `json` |
|
|
|
|
- `app\storage\FileStorage` 和 `app\storage\DocumentsReader` |
|
|
|
|
用于处理位于某些文件存储中的文档的某些逻辑: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
class FileStorage |
|
|
|
@ -399,18 +399,18 @@ class HotelController extends Controller
|
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
It is possible to configure multiple definitions at once, passing configuration array to |
|
|
|
|
[[yii\di\Container::setDefinitions()|setDefinitions()]] or [[yii\di\Container::setSingletons()|setSingletons()]] method. |
|
|
|
|
Iterating over the configuration array, the methods will call [[yii\di\Container::set()|set()]] |
|
|
|
|
or [[yii\di\Container::setSingleton()|setSingleton()]] respectively for each item. |
|
|
|
|
可以一次配置多个定义,将配置数组传递给 |
|
|
|
|
[[yii\di\Container::setDefinitions()|setDefinitions()]] 或 [[yii\di\Container::setSingletons()|setSingletons()]] 方法。 |
|
|
|
|
遍历配置数组,将分别为每个对象分别调用 [[yii\di\Container::set()|set()]] |
|
|
|
|
或 [[yii\di\Container::setSingleton()|setSingleton()]] 方法。 |
|
|
|
|
|
|
|
|
|
配置数组格式为: |
|
|
|
|
|
|
|
|
|
- `key`: class name, interface name or alias name. The key will be passed to the |
|
|
|
|
[[yii\di\Container::set()|set()]] method as a first argument `$class`. |
|
|
|
|
- `value`: the definition associated with `$class`. Possible values are described in [[yii\di\Container::set()|set()]] |
|
|
|
|
documentation for the `$definition` parameter. Will be passed to the [[set()]] method as |
|
|
|
|
the second argument `$definition`. |
|
|
|
|
- `key`:类名称,接口名称或别名。 该 key 将作为第一个参数 |
|
|
|
|
`$class` 传递给 [[yii\di\Container::set()|set()]] 方法。 |
|
|
|
|
- `value`:与 `$class` 关联的定义。`$definition` 参数的值可能在 [[yii\di\Container::set()|set()]] |
|
|
|
|
文档中描述。`$definition` 将作为第二个参数传递给 [[set()]] |
|
|
|
|
方法。 |
|
|
|
|
|
|
|
|
|
例如,让我们配置我们的容器以遵循上述要求: |
|
|
|
|
|
|
|
|
@ -428,32 +428,32 @@ $container->setDefinitions([
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
$reader = $container->get('app\storage\DocumentsReader); |
|
|
|
|
// Will create DocumentReader object with its dependencies as described in the config |
|
|
|
|
// 将按照配置中的描述创建 DocumentReader 对象及其依赖关系 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
> Tip: Container may be configured in declarative style using application configuration since version 2.0.11. |
|
|
|
|
Check out the [Application Configurations](concept-configurations.md#application-configurations) subsection of |
|
|
|
|
the [Configurations](concept-configurations.md) guide article. |
|
|
|
|
> Tip: 自 2.0.11 版以后,可以使用应用程序配置以声明式风格配置容器。 |
|
|
|
|
查看[配置](concept-configurations.md)指南文章的 |
|
|
|
|
[应用程序配置](concept-configurations.md#application-configurations)小节。 |
|
|
|
|
|
|
|
|
|
Everything works, but in case we need to create `DocumentWriter` class, |
|
|
|
|
we shall copy-paste the line that creates `FileStorage` object, that is not the smartest way, obviously. |
|
|
|
|
一切正常,但如果我们需要创建 `Document Writer` 类, |
|
|
|
|
我们将复制粘贴创建 `FileStorage` 对象的行,这显然不是最聪明的方式。 |
|
|
|
|
|
|
|
|
|
As described in the [Resolving Dependencies](#resolving-dependencies) subsection, [[yii\di\Container::set()|set()]] |
|
|
|
|
and [[yii\di\Container::setSingleton()|setSingleton()]] can optionally take dependency's constructor parameters as |
|
|
|
|
a third argument. To set the constructor parameters, you may use the following configuration array format: |
|
|
|
|
如 [解决依赖关系](#resolving-dependencies) 子节中所述,[[yii\di\Container::set()|set()]] |
|
|
|
|
和 [[yii\di\Container::setSingleton()|setSingleton()]] 可以选择将依赖项的构造函数参数作为第三个参数。 |
|
|
|
|
要设置构造函数参数,可以使用以下配置数组格式: |
|
|
|
|
|
|
|
|
|
- `key`: class name, interface name or alias name. The key will be passed to the |
|
|
|
|
[[yii\di\Container::set()|set()]] method as a first argument `$class`. |
|
|
|
|
- `value`: array of two elements. The first element will be passed to the [[yii\di\Container::set()|set()]] method as the |
|
|
|
|
second argument `$definition`, the second one — as `$params`. |
|
|
|
|
- `key`:类名称,接口名称或别名。该 key 将作为第一个参数 |
|
|
|
|
`$class` 传递给 [[yii\di\Container::set()|set()]] 方法。 |
|
|
|
|
- `value`:两个元素的数组。第一个元素将传递给 [[yii\di\Container::set()|set()]] 方法 |
|
|
|
|
作为第二个参数 `$definition`,第二个元素为 `$params`。 |
|
|
|
|
|
|
|
|
|
让我们来修改我们的例子: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
$container->setDefinitions([ |
|
|
|
|
'tempFileStorage' => [ // we've created an alias for convenience |
|
|
|
|
'tempFileStorage' => [ // 我们为了方便创建了一个别名 |
|
|
|
|
['class' => 'app\storage\FileStorage'], |
|
|
|
|
['/var/tempfiles'] // could be extracted from some config files |
|
|
|
|
['/var/tempfiles'] // 可以从一些配置文件中获取 |
|
|
|
|
], |
|
|
|
|
'app\storage\DocumentsReader' => [ |
|
|
|
|
['class' => 'app\storage\DocumentsReader'], |
|
|
|
@ -466,7 +466,7 @@ $container->setDefinitions([
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
$reader = $container->get('app\storage\DocumentsReader); |
|
|
|
|
// Will behave exactly the same as in the previous example. |
|
|
|
|
// 将与前面示例中的行为完全相同。 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
你可能会注意到 `Instance::of('tempFileStorage')` 符号。这意味着,[[yii\di\Container|Container]] |
|
|
|
@ -507,8 +507,8 @@ $container->setDefinitions([
|
|
|
|
|
$reader = $container->get('app\storage\DocumentsReader'); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
什么时候注册依赖关系 <span id="when-to-register-dependencies"></span> |
|
|
|
|
----------------------------- |
|
|
|
|
什么时候注册依赖关系(When to Register Dependencies) <span id="when-to-register-dependencies"></span> |
|
|
|
|
------------------------------------------------ |
|
|
|
|
|
|
|
|
|
由于依赖关系在创建新对象时需要解决,因此它们的注册应该尽早完成。 |
|
|
|
|
如下是推荐的实践: |
|
|
|
@ -520,8 +520,8 @@ $reader = $container->get('app\storage\DocumentsReader');
|
|
|
|
|
你可以将依赖关系注册到扩展的引导类中。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
总结 <span id="summary"></span> |
|
|
|
|
------- |
|
|
|
|
总结(Summary) <span id="summary"></span> |
|
|
|
|
------------- |
|
|
|
|
|
|
|
|
|
依赖注入和[服务定位器](concept-service-locator.md)都是流行的设计模式, |
|
|
|
|
它们使你可以用充分解耦且更利于测试的风格构建软件。 |
|
|
|
|