跳到内容

DependencyInjection 组件

编辑此页

DependencyInjection 组件实现了一个 PSR-11 兼容的服务容器,允许您标准化和集中化在应用程序中构建对象的方式。

有关依赖注入和服务容器的介绍,请参阅 服务容器

安装

1
$ composer require symfony/dependency-injection

注意

如果您在 Symfony 应用程序之外安装此组件,则必须在您的代码中引入 vendor/autoload.php 文件,以启用 Composer 提供的类自动加载机制。阅读这篇文章了解更多详情。

基本用法

另请参阅

本文解释了如何在任何 PHP 应用程序中将 DependencyInjection 功能用作独立组件。阅读 服务容器 文章,了解如何在 Symfony 应用程序中使用它。

您可能有一个如下所示的 Mailer 类,您想将其作为服务提供

1
2
3
4
5
6
7
8
9
10
11
class Mailer
{
    private string $transport;

    public function __construct()
    {
        $this->transport = 'sendmail';
    }

    // ...
}

您可以在容器中将其注册为服务

1
2
3
4
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->register('mailer', 'Mailer');

为了使该类更灵活,一个改进是允许容器设置使用的 transport。如果您更改该类,使其传入构造函数

1
2
3
4
5
6
7
8
9
class Mailer
{
    public function __construct(
        private string $transport,
    ) {
    }

    // ...
}

然后您可以在容器中设置传输方式的选择

1
2
3
4
5
6
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container
    ->register('mailer', 'Mailer')
    ->addArgument('sendmail');

这个类现在更加灵活,因为您已将传输方式的选择从实现中分离出来,放入容器中。

您选择的邮件传输方式可能是其他服务需要知道的。您可以通过将其设为容器中的参数,然后在 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
class NewsletterManager
{
    public function __construct(
        private \Mailer $mailer,
    ) {
    }

    // ...
}

在定义 newsletter_manager 服务时,mailer 服务尚不存在。使用 Reference 类告诉容器在初始化 newsletter 管理器时注入 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,并且注入它是可选的,那么您可以改用 setter 注入

1
2
3
4
5
6
7
8
9
10
11
class NewsletterManager
{
    private \Mailer $mailer;

    public function setMailer(\Mailer $mailer): void
    {
        $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', [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');

获取不存在的服务

默认情况下,当您尝试获取不存在的服务时,您会看到一个异常。您可以按如下方式覆盖此行为

1
2
3
4
5
6
7
8
9
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;

$containerBuilder = new ContainerBuilder();

// ...

// the second argument is optional and defines what to do when the service doesn't exist
$newsletterManager = $containerBuilder->get('newsletter_manager', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);

以下是所有可能的行为

  • ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE:在编译时抛出异常(这是默认行为);
  • ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE:在运行时尝试访问丢失的服务时抛出异常;
  • ContainerInterface::NULL_ON_INVALID_REFERENCE:返回 null
  • ContainerInterface::IGNORE_ON_INVALID_REFERENCE:忽略请求引用的包装命令(例如,如果服务不存在,则忽略 setter);
  • ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE:忽略/返回未初始化服务或无效引用的 null

避免你的代码变得依赖于容器

虽然您可以直接从容器中检索服务,但最好尽量减少这种情况。例如,在 NewsletterManager 中,您注入了 mailer 服务,而不是从容器中请求它。您可以注入容器并从中检索 mailer 服务,但这会将其绑定到这个特定的容器,使得在其他地方重用该类变得困难。

您需要在某个时候从容器中获取服务,但这应该在应用程序的入口点尽可能少地进行。

使用配置文件设置容器

除了像上面那样使用 PHP 设置服务之外,您还可以使用配置文件。这允许您使用 XML 或 YAML 来编写服务的定义,而不是像上面的示例那样使用 PHP 来定义服务。在任何规模较大的应用程序中,将服务定义组织起来,并将它们移动到一个或多个配置文件中是有意义的。要做到这一点,您还需要安装 Config 组件

加载 XML 配置文件

1
2
3
4
5
6
7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');

加载 YAML 配置文件

1
2
3
4
5
6
7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yaml');

注意

如果您想加载 YAML 配置文件,那么您还需要安装 Yaml 组件

提示

如果您的应用程序使用非常规的文件扩展名(例如,您的 XML 文件具有 .config 扩展名),您可以将文件类型作为 load() 方法的第二个可选参数传递

1
2
// ...
$loader->load('services.config', 'xml');

如果您确实想使用 PHP 创建服务,那么您可以将其移动到一个单独的配置文件中,并以类似的方式加载它

1
2
3
4
5
6
7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

$container = new ContainerBuilder();
$loader = new PhpFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.php');

您现在可以使用配置文件设置 newsletter_managermailer 服务

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']]
本作品,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本