将 config.yml 中指定的 Symfony 服务注入到包的服务中

Inject a Symfony service specified in config.yml into a bundle's service

我有一个 config.yml 允许开发人员传递缓存服务名称的地方。

file_repository:
    cache_service: "cache"

现在我有了包配置

<?php

namespace Wolnosciowiec\FileRepositoryBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * @package Wolnosciowiec\FileRepositoryBundle\DependencyInjection
 */
class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('file_repository');

        $rootNode
            ->children()
            ->scalarNode('cache_service')
                ->isRequired()
                ->end()
            ->end()
        ;

        return $treeBuilder;
    }
}

我有扩展名:

<?php

namespace Wolnosciowiec\FileRepositoryBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

/**
 * @package Wolnosciowiec\FileRepositoryBundle\DependencyInjection
 */
class FileRepositoryExtension extends Extension
{
    /**
     * Load configuration definition from "Configuration.php"
     *
     * @param array $configs
     * @param ContainerBuilder $container
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

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

        // inject the cache service into the Comrade Reader
        $comrade = $container->getDefinition('wolnosciowiec.comrade.reader');
        $comrade->addMethodCall('setCache', $container->get($config['cache_service']));
    }
}

在最后几行中,我在 setCache() 方法中将服务(在配置中指定)注入到包的内部服务中。

但我明白了:

ContainerBuilder.php 行 816 中的 ServiceNotFoundException: 您请求了一项不存在的服务 "cache"。

即使在config/services.yml中我已经定义:

services:
    cache:
        class: Doctrine\Common\Cache\ApcuCache

文件首先加载。

如何将 switchable/configurable 服务正确地注入到捆绑服务中?

谢谢! :-)

在 Compiler Pass 而不是 Extension 中执行。 Extension 仅加载配置,尚未将其处理到 Container Builder 中。 Compiler Passes 负责操纵服务定义,如 in the documentation.

所述

首先,在扩展中,必须读取配置入口并将其作为参数存储在容器中:

public function load(array $configs, ContainerBuilder $container)
{
    // ...
    $container->setParameter('cache_service', $config['cache_service']);
}

然后,创建您的 Compiler Pass 并在那里配置您的服务。由于服务尚未实例化,因此您必须使用对您的服务的引用。从容器中检索服务的名称:

// Wolnosciowiec\DependencyInjection\CacheCompilerPass:

use Symfony\Component\DependencyInjection\Reference;

class CacheCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        // get the service name from the container
        $id = $container->getParameter('cache_service');
        // inject the cache service into the Comrade Reader
        $comrade = $container->getDefinition('wolnosciowiec.comrade.reader');
        $comrade->addMethodCall('setCache', new Reference($id)));
    }
}

然后,在您的 Bundle 中注册您的 Compiler Pass class:

// Wolnosciowiec\FileRepositoryBundle:

public function build(ContainerBuilder $container)
{
    parent::build($container);

    $container->addCompilerPass(new CacheCompilerPass());
}

我在这里找到了一条线索: Alter service (ClientManager) based on configuration inside bundle extension class

所以,最后通过一个妥协,编译器传递看起来像这样:

<?php

namespace Wolnosciowiec\FileRepositoryBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Serializer\Serializer;

class CacheCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $config = $container->getExtensionConfig('file_repository');

        // inject the cache service into the Comrade Reader
        $comrade = $container->getDefinition('wolnosciowiec.comrade.reader');

        $comrade->addMethodCall('setUrl',        [$config[0]['url']]);
        $comrade->addMethodCall('setToken',      [$config[0]['token']]);

        // serializer
        $serializer = new Definition(Serializer::class, []);
        $serializer->setPublic(false);
        $container->setDefinition('wolnosciowiec.file_repository.serializer', $serializer);
        $comrade->addMethodCall('setSerializer', array($serializer));

        // cache
        $cache = new Definition($config[0]['cache_class'], []);
        $cache->setPublic(false);
        $container->setDefinition('wolnosciowiec.file_repository.cache', $cache);
        $comrade->addMethodCall('setCache', array($cache));
    }
}

妥协我的意思是我必须从 config.yml 传递一个 class 名称而不是服务名称。它工作得很好,但如果我也能够传递服务 ID 会更好。

构建容器时,您无法从容器中获取服务,只能获取服务定义。您要做的是:

use Symfony\Component\DependencyInjection\Reference;

...
$comrade->addMethodCall('setCache', new Reference($config['cache_service']));
...

实例化服务时会解析此类引用,必要时实例化缓存。