使用配置参数在DIC中连接Symfony2服务

时间:2011-11-21 10:23:21

标签: symfony

我希望能够从配置参数中连接服务,以便我可以在开发环境中使用Web服务客户端的模拟实现,并且我可以在prod env中使用正确的客户端。

以下是来自services.xml的代码段:

<parameters>
    ...
    <parameter key="api_client.api_service.id">ic_domain_security.api_mock_service</parameter>
    ...
</parameters>

<services>
...
    <service id="ic_domain_security.api_client"
                 class="%api_client.class%"
                 public="true">
            <tag name="monolog.logger" channel="domainApiClient"/>
            <argument type="service" id="%api_client.api_service.id%"/>
            <call method="setLogger">
                <argument type="service" id="logger" on-invalid="null"/>
            </call>
        </service>

        <service id="ic_domain_security.api_restful_webservice"
                 class="IC\DomainSecurityBundle\ApiClient\ApiRestfulWebService"
                 public="false">
            <tag name="monolog.logger" channel="domainApiRestfulWebService"/>
            <argument>%ic_domain_security.service_url%</argument>
            <argument type="service" id="logger" on-invalid="null"/>
        </service>

        <service id="ic_domain_security.api_mock_service"
                 class="IC\DomainSecurityBundle\Tests\Mock\ApiMockService"
                 public="false">
            <call method="setLogger">
                <argument type="service" id="logger" on-invalid="null"/>
            </call>
        </service>
...
</services>

<argument type="service" id="%api_client.api_service.id%"/>正在尝试根据参数定义要传递的服务。所以我最终试图将api_client.api_service.id参数推送到配置文件。

不幸的是,似乎无法使用参数定义服务ID,并且我收到以下错误:

ServiceNotFoundException: The service "ic_domain_security.api_client" has a dependency on a non-existent service "%api_client.api_service.id%".

有没有办法按照上面的说明进行操作,以便可以使用配置参数传递服务ID?

3 个答案:

答案 0 :(得分:3)

执行您尝试执行的操作的最佳方法是为捆绑包定义语义配置。无论如何,我认为如果不编写一些PHP代码就可以做到这一点。

这意味着您应该编写一个Configuration类和一个Dependency Injection Extension类。这是一个非常简单的过程,尽管它没有在Symfony2文档上详尽记录。我的建议是采取一些简单的捆绑(例如,我非常了解FOSUserBundle),看看他们如何定义他们的配置。

Here you can find an introduction到DIC扩展系统。

答案 1 :(得分:2)

要允许在配置中定义服务类,您将使用语义配置(如上所述)。一个例子如下:

这是一个简单的服务(服务可以是任何php类):

<?php

namespace Sample\Bundle\ServiceBundle\Service;

class Awesomeness {
  public function doSomething()
  {
    return 'I did something';
  }
}

有几种方法可以将服务连接到Symfony2服务容器中。在本例中,我们将在ServiceBundle / Resources / config中创建一个services.xml文件(这在整个Symfony2文档中都有描述):

    <?xml version="1.0" ?>

    <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">
        <services>
                <service id="sample_service_bundle.awesomess" class="%sample_service_bundle.awesomeness.class%">
                </service>
        </services>
</container>

请注意,我没有将标准部分添加到services.xml。这是故意的。我们将修改bundle的扩展类和语义配置类,以从config.yml注入参数。

要允许在配置中定义服务类,我们首先将配置添加到DependencyInjection / Configuration.php:

class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('brain_glove_site');

        $rootNode
                ->children()
                        ->scalarNode('awesomeness_class')->cannnotBeEmpty()->defaultValue('Sample\\Bundle\\ServiceBundle\\Service\\Awesomeness')->end()
                ->end()
        ;

        return $treeBuilder;
    }
}

这里我们创建了一个名为'awesomeness_class'的新配置参数,可以在config.yml中设置并为其提供默认值。

但是,我们仍然需要创建服务可以使用的实际参数。这是在您的包将使用的扩展类中完成的。如果您的包名为SampleServiceBundle,那么您编辑的类将是DependencyInjection \ SampleServiceExtension:

class SampleServiceExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));

        $loader->load('services.xml');

        $container->setParameter('sample_service_bundle.awesomeness.class', $config['awesomeness_class']);
    }
}

$ container-&gt; setParameter()方法调用将一个参数注入您的容器,然后由您的服务定义使用。

此时,您无需像往常一样使用您的服务。

如果要更改服务在配置中实例化的类,可以编辑config_dev.yml:

...
sample_service:
    awesomeness_class: SomeOtherBundle/SomeOtherClass

现在您的应用程序将使用不同的类来提供此服务。

答案 2 :(得分:0)

我从来没有弄清楚如何定义动态参数(使用参数作为服务ID)。我找到的唯一方法就是使用工厂和语义配置。

基本上你应该做的是:

  • 使用语义配置
  • 定义参数
  • 创建一个可以根据该参数返回正确服务的工厂(您的工厂本身需要ServiceContainer实例)
  • 定义一个使用工厂进行实例化的服务(这将为您提供一个引用动态创建的对象的服务ID)
  • 使用此服务名称依赖其他服务。

您可以在http://symfony.com/doc/current/components/dependency_injection/factories.html

找到有关DIC和工厂的信息

它有点棘手,因为你需要工厂类,但它有效。我不确定这是唯一的方法,但仍然找不到更好的方法。

在这里,您可以找到我所做的一些小例子:https://gist.github.com/32378f4000df482a8b9b

它不是整个代码库,但您可以看到“vich_uploader.storage”服务,它使用工厂服务来实例化实际服务。 StorageFactory将根据'vich_uploader.storage_service'DIC参数返回正确的服务,该参数由bundle语义配置定义。

所有其他服务将使用“vich_uploader.storage”作为参数。