DependencyInjection组件

3.3 version
维护中的版本

DI组件能够让你把程序中构建的对象以标准化和中心化的方式来呈现。

关于Dependency Injection(依赖注入)和service container(服务容器),请参考中文BOOK中的Service Container(服务容器)

安装 

你可以通过下述两种方式安装:

基本用法 

你可能会有如下例的简单Mailer类,并希望把它设为服务:

1
2
3
4
5
6
7
8
9
10
11
class Mailer
{
    private $transport;
 
    public function __construct()
    {
        $this->transport = 'sendmail';
    }
 
    // ...
}

现在你可将该类注册到容器中令其成为服务:

1
2
3
4
use Symfony\Component\DependencyInjection\ContainerBuilder;
 
$container = new ContainerBuilder();
$container->register(‘mailer’, ‘Mailer’);

对这个类进行一些改进,可以使它更灵活地让container来设置transport属性。修改类的代码,将该属性注入构造器。

1
2
3
4
5
6
7
8
9
10
11
class Mailer
{
    private $transport;
 
    public function __construct($transport)
    {
        $this->transport = $transport;
    }
 
    // ...
}

这时你可以在容器中设置transport:

1
2
3
4
5
6
use Symfony\Component\DependencyInjection\ContainerBuilder;
 
$container = new ContainerBuilder();
$container
    ->register('mailer', 'Mailer')
    ->addArgument('sendmail');

现在这个类变得更有适应性,因为你从代码的执行过程中分离出transport,并将其转入容器。

你指定的transport传输方式,可能需要被其他服务知道。你可以避免在程序中“多处修改”这个值,只需把transport设为容器中的参数,然后引用这个“参数”到mailer服务中的构造器参数中即可:

1
2
3
4
5
6
7
use Symfony\Component\DependencyInjection\ContainerBuilder;
 
$container = new ContainerBuilder();
$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');

现在,你可以对服务容器中的mailer服务,注入其他的类。假设你有一个NewsletterManager类:

1
2
3
4
5
6
7
8
9
10
11
class NewsletterManager
{
    private $mailer;
 
    public function __construct(\Mailer $mailer)
    {
        $this->mailer = $mailer;
    }
 
    // ...
}

在定义newsletter_manager服务时,mailer服务还不存在(译注:请参考下面代码,这句话是指,虽然“订阅管理器”这个服务定义了,但要传给它的构造参数,却不能是“mailer服务定义”,而必须是一个类实例)。使用Reference类来通知容器,在newsletter manager初始化时,将mailer服务注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
 
$container = new ContainerBuilder();
 
$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');
 
$container
    ->register('newsletter_manager', 'NewsletterManager')
    ->addArgument(new Reference('mailer'));

如果NewsletterManager并不需要Mailer,那么注入Mailer服务可设为“可选”,你可以用setter注入来替代原有的constructor注入:

1
2
3
4
5
6
7
8
9
10
11
class NewsletterManager
{
    private $mailer;
 
    public function setMailer(\Mailer $mailer)
    {
        $this->mailer = $mailer;
    }
 
    // ...
}

现在你可以选择不将Mailer注入到NewsletterManager中。如果是这样的话,你可以在容器中调用setter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
 
$container = new ContainerBuilder();
 
$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');
 
$container
    ->register('newsletter_manager', 'NewsletterManager')
    ->addMethodCall('setMailer', array(new Reference('mailer')));

然后你可以从容器中得到newsletter_manager服务:

1
2
3
4
5
6
7
use Symfony\Component\DependencyInjection\ContainerBuilder;
 
$container = new ContainerBuilder();
 
// ...
 
$newsletterManager = $container->get('newsletter_manager');

避免你的代码依赖于container 

尽管你可以从容器中直接取出服务,但最好避免这样做。例如,在NewsletterManager中,你注入mailer服务要好过从container中取出它来。如果你注入container并从中取出mailer,这样会与这个特定容器绑定,导致类难以在别处复用。

若你基于某种原因,非要从注入的容器中来取得某个服务,从程序面来讲应该尽可能少地这样做。

通过配置文件来设置容器 

前面例子中都是用PHP方式来设置的服务,你也可以使用配置文件来对Service进行设置。你可以使用Xml或Yaml来写入服务定义,而不是像上面那样用PHP来定义服务。通过把这些定义转移到一个或多个配置文件中,在多数情况下(除了极小的程序)这样做都有利于管理服务定义。为了实现这个,你需要安装Config组件

加载一个xml配置文件:

1
2
3
4
5
6
7
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
 
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');

如果你要加载YAML配置文件,你需要安装Yaml组件

如果你确实需要用PHP创建服务,你也要把配置文件独立出来,并用类似的办法加载它:

1
2
3
4
5
6
7
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
 
$container = new ContainerBuilder();
$loader = new PhpFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.php');

现在你可以用配置文件来设置newsletter_manager和mailer服务了:

1
2
3
4
5
6
7
8
9
10
11
12
parameters:
    # ...
    mailer.transport: sendmail

services:
    mailer:
        class:     Mailer
        arguments: ['%mailer.transport%']
    newsletter_manager:
        class:     NewsletterManager
        calls:
            - [setMailer, ['@mailer']]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <parameters>
        <!-- ... -->
        <parameter key="mailer.transport">sendmail</parameter>
    </parameters>
 
    <services>
        <service id="mailer" class="Mailer">
            <argument>%mailer.transport%</argument>
        </service>
 
        <service id="newsletter_manager" class="NewsletterManager">
            <call method="setMailer">
                <argument type="service" id="mailer" />
            </call>
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\DependencyInjection\Reference;
 
// ...
$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');
 
$container
    ->register('newsletter_manager', 'NewsletterManager')
    ->addMethodCall('setMailer', array(new Reference('mailer')));

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)