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_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']]