【问题标题】:Injecting parameter based service into other service将基于参数的服务注入其他服务
【发布时间】:2014-03-13 00:57:10
【问题描述】:

我有一项服务,需要司机来完成实际工作。驱动程序本身在 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的值现在等于我在parameters.yml中设置的my_scope.mailer_driver_smtp,也就是说,按照我的理解是正确的,只是一个字符串。

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

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

【问题讨论】:

  • 你需要改用$container->setAlias
  • 有趣。您指向我之前提出的问题的公认答案。您认为您可以在答案中添加上面的链接,以便我可以接受它作为正确答案吗?谢谢。

标签: symfony configuration dependency-injection


【解决方案1】:

这个问题有类似的答案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 参数,服务就会发生变化。
请注意,如果服务不存在,扩展程序将抛出异​​常。

【讨论】:

    【解决方案2】:

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

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

    所以要将参数名称转换为服务引用,您需要这样的东西:

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

    【讨论】:

    • 我刚刚注意到这一点。这是一个非常好的方法。下次我必须做这样的事情时会尝试。
    【解决方案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)));
        }
    }
    

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 谢谢。我试过这个,但它不起作用。我收到一个非常相似的错误:`服务“my_scope.mailer”依赖于不存在的服务“%my_scope.mailer.driver%”`(注意百分比符号)
      • 去掉 % 符号周围的引号
      • 它周围没有引号,引号来自异常消息。我正在使用这个:[setDriver, [@%my_scope.mailer.driver%]],它给出了我的评论中显示的错误。
      • @%my_scope.mailer.driver% 语法目前不起作用。 alias: %my_scope.mailer.driver% 也不起作用。 (在 Symfony 2.4.2 上检查)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-21
      • 2014-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-07
      相关资源
      最近更新 更多