对于我的项目,我想指定一些自定义配置。我有一堆' 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_collector
和data_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
。所以在那之后我尝试通过扩展来实现。
答案 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