默认装饰所有实现相同接口的服务?
Decorate all services that implement the same interface by default?
我有越来越多的服务 类 共享一个公共接口(假设 BarService
和 BazService
,实现 FooInterface
)。
所有这些都需要用同一个装饰器来装饰。阅读 docs,我知道我可以做到:
services:
App\BarDecorator:
# overrides the App\BarService service
decorates: App\BarService
由于我必须为不同的服务使用相同的装饰器,我想我需要这样做:
services:
bar_service_decorator:
class: App\BarDecorator
# overrides the App\BarService service
decorates: App\BarService
baz_service_decorator:
class: App\BarDecorator
# overrides the App\BazService service
decorates: App\BazService
问题是:这很快就会重复。并且每次创建 FooInterface
的新实现时,都需要将另一组添加到配置中。
如何声明我想自动装饰所有实现 FooInterface
的服务,而不必单独声明每个服务?
有意思!我认为这会很棘手......但也许这里有一些提示,你可能会想出一个适合你需要的解决方案
- 找到所有装饰器...不确定在这种情况下是否有更简单的方法,但我为此使用了标签。所以创建一个
DecoratorInterface
添加自动标记它...
- 遍历定义并修改和设置装饰服务
e。 G。在你的 Kernel
或 AcmeAwesomeBundle
做
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(DecoratorInterface::class)
->addTag('my.decorator.tag');
$decoratorIds = $container->findTaggedServiceIds('my.decorator.tag');
foreach ($decoratorIds as $decoratorId) {
$definition = $container->getDefinition($decoratorId);
$decoratedServiceId = $this->getDecoratedServiceId($definition);
$definition->setDecoratedService($decoratedServiceId);
}
}
private function getDecoratedServiceId(Definition $decoratorDefinition): string
{
// todo
// maybe u can use the arguments here
// e.g. the first arg is always the decoratedService
// might not work because the arguments are not resolved yet?
$arg1 = $decoratorDefinition->getArgument(0);
// or use a static function in your DecoratorInterface like
// public static function getDecoratedServiceId():string;
$class = $decoratorDefinition->getClass();
$decoratedServiceId = $class::getDecoratedServiceId();
return 'myDecoratedServiceId';
}
我很确定这还没有完成,但请告诉我们您是如何解决的
compiler pass 允许以编程方式修改容器,更改服务定义或添加新服务定义。
首先,您需要一种方法来定位 FooInterface
的所有实现。您可以在 autoconfigure:
的帮助下完成此操作
services:
_instanceof:
App\FooInterface:
tags: ['app.bar_decorated']
然后您需要创建收集所有 FooServices
的编译器传递并创建一个新的修饰定义:
// src/DependencyInjection/Compiler/FooInterfaceDecoratorPass.php
namespace App\DependencyInjection\Compiler;
use App\BarDecorator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class FooInterfaceDecoratorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has(BarDecorator::class)) {
// If the decorator isn't registered in the container you could register it here
return;
}
$taggedServices = $container->findTaggedServiceIds('app.bar_decorated');
foreach ($taggedServices as $id => $tags) {
// skip the decorator, we do it's not self-decorated
if ($id === BarDecorator::class) {
continue;
}
$decoratedServiceId = $this->generateAliasName($id);
// Add the new decorated service.
$container->register($decoratedServiceId, BarDecorator::class)
->setDecoratedService($id)
->setPublic(true)
->setAutowired(true);
}
}
/**
* Generate a snake_case service name from the service class name
*/
private function generateAliasName($serviceName)
{
if (false !== strpos($serviceName, '\')) {
$parts = explode('\', $serviceName);
$className = end($parts);
$alias = strtolower(preg_replace('/[A-Z]/', '_\0', lcfirst($className)));
} else {
$alias = $serviceName;
}
return $alias . '_decorator';
}
}
最后,在内核中注册编译器传递:
// src/Kernel.php
use App\DependencyInjection\Compiler\FooInterfaceDecoratorPass;
class Kernel extends BaseKernel
{
// ...
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new FooInterfaceDecoratorPass());
}
}
我有越来越多的服务 类 共享一个公共接口(假设 BarService
和 BazService
,实现 FooInterface
)。
所有这些都需要用同一个装饰器来装饰。阅读 docs,我知道我可以做到:
services:
App\BarDecorator:
# overrides the App\BarService service
decorates: App\BarService
由于我必须为不同的服务使用相同的装饰器,我想我需要这样做:
services:
bar_service_decorator:
class: App\BarDecorator
# overrides the App\BarService service
decorates: App\BarService
baz_service_decorator:
class: App\BarDecorator
# overrides the App\BazService service
decorates: App\BazService
问题是:这很快就会重复。并且每次创建 FooInterface
的新实现时,都需要将另一组添加到配置中。
如何声明我想自动装饰所有实现 FooInterface
的服务,而不必单独声明每个服务?
有意思!我认为这会很棘手......但也许这里有一些提示,你可能会想出一个适合你需要的解决方案
- 找到所有装饰器...不确定在这种情况下是否有更简单的方法,但我为此使用了标签。所以创建一个
DecoratorInterface
添加自动标记它... - 遍历定义并修改和设置装饰服务
e。 G。在你的 Kernel
或 AcmeAwesomeBundle
做
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(DecoratorInterface::class)
->addTag('my.decorator.tag');
$decoratorIds = $container->findTaggedServiceIds('my.decorator.tag');
foreach ($decoratorIds as $decoratorId) {
$definition = $container->getDefinition($decoratorId);
$decoratedServiceId = $this->getDecoratedServiceId($definition);
$definition->setDecoratedService($decoratedServiceId);
}
}
private function getDecoratedServiceId(Definition $decoratorDefinition): string
{
// todo
// maybe u can use the arguments here
// e.g. the first arg is always the decoratedService
// might not work because the arguments are not resolved yet?
$arg1 = $decoratorDefinition->getArgument(0);
// or use a static function in your DecoratorInterface like
// public static function getDecoratedServiceId():string;
$class = $decoratorDefinition->getClass();
$decoratedServiceId = $class::getDecoratedServiceId();
return 'myDecoratedServiceId';
}
我很确定这还没有完成,但请告诉我们您是如何解决的
compiler pass 允许以编程方式修改容器,更改服务定义或添加新服务定义。
首先,您需要一种方法来定位 FooInterface
的所有实现。您可以在 autoconfigure:
services:
_instanceof:
App\FooInterface:
tags: ['app.bar_decorated']
然后您需要创建收集所有 FooServices
的编译器传递并创建一个新的修饰定义:
// src/DependencyInjection/Compiler/FooInterfaceDecoratorPass.php
namespace App\DependencyInjection\Compiler;
use App\BarDecorator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class FooInterfaceDecoratorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has(BarDecorator::class)) {
// If the decorator isn't registered in the container you could register it here
return;
}
$taggedServices = $container->findTaggedServiceIds('app.bar_decorated');
foreach ($taggedServices as $id => $tags) {
// skip the decorator, we do it's not self-decorated
if ($id === BarDecorator::class) {
continue;
}
$decoratedServiceId = $this->generateAliasName($id);
// Add the new decorated service.
$container->register($decoratedServiceId, BarDecorator::class)
->setDecoratedService($id)
->setPublic(true)
->setAutowired(true);
}
}
/**
* Generate a snake_case service name from the service class name
*/
private function generateAliasName($serviceName)
{
if (false !== strpos($serviceName, '\')) {
$parts = explode('\', $serviceName);
$className = end($parts);
$alias = strtolower(preg_replace('/[A-Z]/', '_\0', lcfirst($className)));
} else {
$alias = $serviceName;
}
return $alias . '_decorator';
}
}
最后,在内核中注册编译器传递:
// src/Kernel.php
use App\DependencyInjection\Compiler\FooInterfaceDecoratorPass;
class Kernel extends BaseKernel
{
// ...
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new FooInterfaceDecoratorPass());
}
}