带有service / di引用的自定义配置

时间:2016-09-19 11:48:43

标签: php symfony dependency-injection

对于我的项目,我想指定一些自定义配置。我有一堆' mappers'有一些属性,并将参考其他服务。

例如,我希望我的配置看起来像这样:

self_service:
  mappers:
    branche_vertalingen:
      data_collector: "@self_service.branche_vertalingen.data_collector"
      data_loader: "@self_service.branche_vertalingen.data_loader"
      map_data: SelfServiceBundle\Entity\BrancheVertalingMapData

self_service是捆绑名称,mappers是'容器'所有映射器都被定义的地方。并且branche_vertalingen是已定义的映射器之一,可以(并且将会)更多。目前,每个映射器都有data_collectordata_loader,它们引用了捆绑包services.yml中定义的服务,以及map_data属性,它指的是实体的班级名称。

我已将此配置放在SelfServiceBundle/Resources/config/config.yml中并将其导入app/config/config.yml 我根据this article创建了一个SelfServiceExtension类。在扩展程序的load()方法中,我将我定义的配置作为数组接收。到目前为止,非常好。

我遇到的问题是我收到的data_collector的值只是定义的字符串,而不是我期望的服务。没问题,我想。我有一个$container可用,我会查一查,但我无法在那里获得服务。

问题:如何确保我可以获得我在配置中引用的服务?

我已经尝试在parameters块中执行相同操作,这样我甚至不需要捆绑扩展,但这样做我得到了这个错误:You cannot dump a container with parameters that contain references to other services。所以在那之后我尝试通过扩展来实现。

1 个答案:

答案 0 :(得分:2)

正如我在评论中所写,我认为标记服务非常适合这一点。它允许您通过标记服务非常轻松地添加或删除映射器。这样,所有地图制作者都不需要在同一个地方或类似地点生活

使用接口确保所有内容连接也可以轻松扩展。

要了解它如何能够包含您的初步想法,请参阅本答案末尾的示例。

使用

/** @var $mapperManager MapperManager */
$mapperManager = $this->get('app.mapper_manager');

dump($mapperManager);
foreach ($mapperManager->getMappers('branche_vertalingen') as $mapper) {
    dump($mapper);
}

实施

(紧跟official docs):

<强> service.yml

services:
    app.mapper_manager:
        class: AppBundle\Mapper\Manager

    # mappers

    app.mapper_1:
        public: false
        class: AppBundle\Mapper\DefaultMapper
        arguments:
            - "a"
            - "b"
            - SelfServiceBundle\Entity\BrancheVertalingMapData
        tags:
            - { name: app.mapper, branch: branche_vertalingen }

    app.mapper_2:
        public: false
        class: AppBundle\Mapper\DefaultMapper
        arguments:
            - "c"
            - "d"
            - SelfServiceBundle\Entity\BrancheVertalingMapData
        tags:
            - { name: app.mapper, branch: branche_vertalingen }

    app.mapper_3:
        public: false
        class: AppBundle\Mapper\DefaultMapper
        arguments:
            - "e"
            - "f"
            - SelfServiceBundle\Entity\BrancheVertalingMapData
        tags:
            - { name: app.mapper, branch: other_branch }

编译器传递

<?php
// src/AppBundle/DependencyInjection/Compiler/MapperCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;

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

class MapperCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container->has('app.mapper_manager')) {
            return;
        }

        $definition = $container->findDefinition('app.mapper_manager');
        $taggedServices = $container->findTaggedServiceIds('app.mapper');

        foreach ($taggedServices as $id => $tags) {
            foreach ($tags as $attributes) {
                $definition->addMethodCall('addMapper', [$attributes['branch'], new Reference($id)]);
            }
        }
    }
}

使用编译器传递

<?php
// src/AppBundle/AppBundle.php
namespace AppBundle;

use AppBundle\DependencyInjection\Compiler\MapperCompilerPass;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class AppBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new MapperCompilerPass());
    }
}

一个简单的经理类(称之为更合适的任何东西):

<?php
// src/AppBundle/Mapper/Manager.php
namespace AppBundle\Mapper;


class Manager
{
    private $mappers = [];

    public function addMapper($branch, MapperInterface $mapper)
    {
        if (!array_key_exists($branch, $this->mappers)) {
            $this->mappers[$branch] = [];
        }

        $this->mappers[$branch][] = $mapper;
    }

    public function getMappers($branch)
    {
        if (!array_key_exists($branch, $this->mappers)) {
            // handle invalid access
            // throw new \InvalidArgumentException('%message%');
        }

        return $this->mappers[$branch];
    }
}

默认的映射器类(这实际上不是必需的,但可以让事情更容易开始):

<?php
// src/AppBundle/Mapper/DefaultMapper.php
namespace AppBundle\Mapper;


class DefaultMapper implements MapperInterface
{
    private $dataCollector;
    private $dataLoader;
    private $mapData;

    public function __construct($dataCollector, $dataLoader, $mapData)
    {
        $this->dataCollector = $dataCollector;
        $this->dataLoader = $dataLoader;
        $this->mapData = $mapData;
    }

    public function getDataCollector()
    {
        return $this->dataCollector;
    }

    public function getDataLoader()
    {
        return $this->dataLoader;
    }

    public function getMapData()
    {
        return $this->mapData;
    }
}

最终用于数据映射器的简单界面

<?php
// src/AppBundle/Mapper/MapperInterface.php
namespace AppBundle\Mapper;


interface MapperInterface
{
    public function getDataCollector();
    public function getDataLoader();
    public function getMapData();
}

一点额外

使用额外的编译器传递(或仅参见代码注释),您还可以扩展上述解决方案:

使用额外的编译器传递,例如

<?php
// src/AppBundle/DependencyInjection/Compiler/MapperCollectionCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;

use AppBundle\Mapper\DefaultMapper;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Definition;


class MapperCollectionCompilerPass implements CompilerPassInterface
{
    private $parameterName;

    public function __construct($parameterName)
    {
        $this->parameterName = $parameterName;
    }

    public function process(ContainerBuilder $container)
    {
        if (!$container->has('app.mapper_manager')) {
            return;
        }

        if (!$container->hasParameter($this->parameterName)) {
            return;
        }

        $definition = $container->findDefinition('app.mapper_manager');
        $mappers = $container->getParameter($this->parameterName);

        foreach ($mappers as $branch => $meta) {
            $mapper = new Definition(DefaultMapper::class, [
                new Reference($meta['data_collector']),
                new Reference($meta['data_loader']),
                $meta['map_data'],
            ]);

            $mapper
                ->setPublic(false)
                ->addTag('app.mapper', ['branch' => $branch])
            ;

            $container->addDefinitions([$mapper]);

            // If you don't want to use tags, simply add the 'addMethodCall'
            // from MapperCompilerPass here
            // $definition->addMethodCall('addMapper', [$branch, $mapper]);
        }
    }
}

将其添加到捆绑

<?php
// src/AppBundle/AppBundle.php
namespace AppBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use AppBundle\DependencyInjection\Compiler\MapperCompilerPass;
use AppBundle\DependencyInjection\Compiler\MapperCollectionCompilerPass;


class AppBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new MapperCollectionCompilerPass('mappers'));
        $container->addCompilerPass(new MapperCompilerPass());
    }
}

并添加配置

# app/config/services.yml
parameters:
    mappers:
        branche_vertalingen:
            # !note the missing @
            data_collector: app.some_service
            data_loader: app.some_service
            map_data: SelfServiceBundle\Entity\BrancheVertalingMapData