默认装饰所有实现相同接口的服务?

Decorate all services that implement the same interface by default?

我有越来越多的服务 类 共享一个公共接口(假设 BarServiceBazService,实现 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。在你的 KernelAcmeAwesomeBundle

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());
    }
}