将基于参数的服务注入其他服务

时间:2014-03-13 00:57:10

标签: symfony configuration dependency-injection

我有一个服务,让司机做实际的工作。驱动程序本身在Symfony 2的上下文中只是另一项服务。

为了说明简化版本:

services:
  # The driver services.
  my_scope.mailer_driver_smtp:
    class: \My\Scope\Service\Driver\SmtpDriver

  my_scope.mailer_driver_mock:
    class: \My\Scope\Service\Driver\MockDriver

  # The actual service.
  my_scope.mailer:
    class: \My\Scope\Service\Mailer
    calls:
      - [setDriver, [@my_scope.mailer_driver_smtp]]

如上所示,我可以将两个驱动程序服务中的任何一个注入到Mailer服务中。问题当然是注入的驱动程序服务是硬编码的。所以,我想参数化@my_scope.mailer_driver_smtp

我是通过向parameters.yml

添加条目来实现此目的的
my_scope_mailer_driver: my_scope.mailer_driver_smtp

然后我可以在我的config.yml中使用它并将参数分配给语义暴露配置[1]:

my_scope:
  mailer:
    driver: %my_scope_mailer_driver%

最后,在我的包的Configuration类中,我在容器上设置了一个参数:

$container->setParameter('my_scope.mailer.driver', $config['mailer']['driver'] );

容器参数my_scope.mailer.driver的值现在等于我在my_scope.mailer_driver_smtp中设置的parameters.yml,因为我对它的理解是正确的,只是一个字符串。

如果我现在使用容器中的参数名称,我会收到一个错误,抱怨没有这样的服务。 E.g:

services:
  my_scope.mailer:
    class: \My\Scope\Service\Mailer
    calls:
      - [setDriver, [@my_scope.mailer.driver]]

以上将导致错误:

[Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]                                          
The service "my_scope.mailer" has a dependency on a non-existent service "my_scope.mailer.driver"

现在的问题是,注入这个基于容器参数的服务的正确语法是什么?

[1] http://symfony.com/doc/current/cookbook/bundles/extension.html

4 个答案:

答案 0 :(得分:5)

  

此问题的回答类似here

我认为使用此类定义的最佳方法是使用service aliasing 这可能看起来像这样

的Acme \ FooBundle \ DependencyInjection \ AcmeFooExtension

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration;
    $config = $this->processConfiguration($configuration, $configs);

    $loader = new Loader\YamlFileLoader(
        $container,
        new FileLocator(__DIR__.'/../Resources/config')
    );
    $loader->load('services.yml');

    $alias = $config['mailer']['driver'];
    $container->setAlias('my_scope.mailer_driver', $alias);
}

这会将您在my_scope.mailer.driver中定义的服务与my_scope.mailer_driver混淆,您可以将其用作任何其他服务

services.yml

services:
    my_scope.mailer_driver:
        alias: my_scope.mailer_driver_smtp # Fallback

    my_scope.mailer_driver_smtp:
        class: My\Scope\Driver\Smtp

    my_scope.mailer_driver_mock:
        class: My\Scope\Driver\Mock

    my_scope.mailer:
        class: My\Scope\Mailer
        arguments:
            - @my_scope.mailer_driver

通过这样的设计,只要您更改 config.yml 中的my_scope.mailer_driver参数,服务就会发生变化。
请注意,如果服务不存在,扩展将引发异常。

答案 1 :(得分:5)

使用service container expression language,您可以访问配置文件中的以下两个功能:

  • service - 返回给定服务(请参阅下面的示例);
  • 参数 - 返回特定参数值(语法就像服务一样)

因此,要将参数名称转换为服务引用,您需要这样的内容:

parameters:
  my_scope_mailer_driver: my_scope.mailer_driver_smtp

services:
  my_scope.mailer:
    class: \My\Scope\Service\Mailer
    calls:
      - [setDriver, [@=service(parameter('my_scope_mailer_driver'))]]

答案 2 :(得分:3)

起初我认为这只是让@符号正确传递的问题。但我尝试了各种组合,并得出结论,你不能将实际服务作为参数传递。也许其他人会插话并展示如何做到这一点。

那么我认为只是使用服务定义并将其传递给引用的问题。起初我在通常的扩展中尝试了这个,但容器还没有包含所有的服务定义。

所以我使用了编译器传递:http://symfony.com/doc/current/cookbook/service_container/compiler_passes.html

Pass类看起来像:

namespace Cerad\Bundle\AppCeradBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class Pass1 implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        // Set in the Extension: my_scope.mailer_driver_smtp
        $mailerDriverId = $container->getParameter('my_scope.mailer.driver');

        $def = $container->getDefinition('my_scope.mailer');

        $def->addMethodCall('setDriver', array(new Reference($mailerDriverId)));
    }
}

从服务文件中取出调用部分,它应该可以工作。我怀疑有一种更简单的方法,但也许不是。

答案 3 :(得分:0)

@my_scope.mailer.driver需要是服务,但未定义为服务。要检索名为my_scope.mailer.driver的字符串参数,您需要使用%包裹它:%my_scope.mailer.driver%

因此,您需要将@%my_scope.mailer.driver%作为参数传递给服务。 Yml解析器将使用适当的参数值替换%my_scope.mailer.driver%,然后才会将其作为服务调用。