在 Symfony 中基于动态值注入服务

Inject service based on dynamic value in Symfony

我有 2 个服务,BlueWorkerServiceYellowWorkerService,都实现相同的接口,WorkerServiceInterface。这些服务中的每一个都使用相同的实体,但具有不同的所需逻辑。

我需要注入其中一个,而不是两个,类 并在 ProcessorService 中使用它们,以便在正确的 Worker 上调用接口方法。使用哪个 Worker 服务取决于当前正在处理的 Worker。我会分解它:

Class WorkerProcessor {

  private $workerService;

  public function __construct(WorkerServiceInterface $workerServiceInterface)
  {
    $this->workerService = $workerServiceInterface;
  }

  public function getMixedColourWithRed() {
    return $this->workerService->mixWithRed();
  }
}

正在使用的工作人员服务将基于正在处理的工作人员是否具有 BlueYellowcolour 属性。

我知道我可能可以使用 Factory 来实现这个 as described here 但我的问题是如何告诉工厂我正在处理哪种 Worker 颜色?

运行 在 Symfony 上 3.4

如果您需要更多信息,请提问,我会更新问题。

注意:我使用的是 Symfony 4.3.1。我会 post 那样做,然后我会帮助您将所有代码从该架构移至 Symfony 3.4。

我正在使用类似的概念在我的项目中加载不同的 classes。我先解释一下,然后我会在这段文字下添加代码。

首先,我在 src/Kernel.php 下加载自定义编译器传递(您的文件是 app/AppKernel.php):

/**
 * {@inheritDoc}
 */
public function build(ContainerBuilder $container)
{
    $container->addCompilerPass(new BannerManagerPass());
}

BannerManagerPass 它是在 src/DependencyInjection/Compiler 下创建的(在您的情况下应该是 src/BUNDLE/DependencyInjection/Compiler`)。

class BannerManagerPass implements CompilerPassInterface
{
    /**
     * {@inheritDoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->has(BannerManager::class)) {
            return;
        }

        $definition     = $container->findDefinition(BannerManager::class);
        $taggedServices = $container->findTaggedServiceIds('banner.process_banners');

        foreach (array_keys($taggedServices) as $id) {
            $definition->addMethodCall('addBannerType', [new Reference($id)]);
        }
    }
}

如您所见,这个 class 应该实现 CompilerPassInterface。您可以观察到我正在寻找标记为 banner.process_banners 的特定服务。稍后我将展示如何标记服务。然后,我从 BannerManager.

调用 addBannerType 方法

App\Service\BannerManager.php:(在你的情况下 src/BUNDLE/Service/BannerManager.php)

class BannerManager
{
    /**
     * @var array
     */
    private $bannerTypes = [];

    /**
     * @param BannerInterface $banner
     */
    public function addBannerType(BannerInterface $banner)
    {
        $this->bannerTypes[$banner->getType()] = $banner;
    }

    /**
     * @param string $type
     *
     * @return BannerInterface|null
     */
    public function getBannerType(string $type)
    {
        if (!array_key_exists($type, $this->bannerTypes)) {
            return null;
        }

        return $this->bannerTypes[$type];
    }

    /**
     * Process request and return banner.
     *
     * @param string  $type
     * @param Server  $server
     * @param Request $request
     *
     * @return Response
     */
    public function process(string $type, Server $server, Request $request)
    {
        return $this->getBannerType($type)->process($request, $server);
    }
}

这个 class 有一个名为 process() 的自定义方法(由我创建)。您可以随意命名,但我认为这很冗长。所有参数都是我发过来的,不用介意。想发什么就发什么

现在我们有了我们的管理器并设置了编译器通道。是时候设置我们的横幅类型(基于我的示例)并标记它们了!

我的横幅类型在 src/Service/Banner/Types 下(在你的情况下应该是 src/BUNDLE/Service/WhateverYouWant/Type。这不是重要!您可以稍后更改它 services.yaml).

这些类型正在实现我的 BannerInterface。在本例中 class 下的代码无关紧要。还有一件事我应该警告你!您应该会在 BannerManager 下看到 addBannerType() 我正在调用 $banner->getType()。在我的例子中,这是从 BannerInterface 继承的一种方法,它有一个唯一的字符串(在我的例子中,我有三种横幅类型:小、普通、大)。此方法可以有任何名称,但不要忘记在您的管理器中更新它。

我们快准备好了!我们应该标记它们,然后我们准备尝试它们!

转到您的 services.yaml 并添加这些行:

  App\Service\Banner\Types\:
    resource: '../src/Service/Banner/Types/'
    tags: [banner.process_banners]

请看标签!

无论我想显示自定义横幅,我都使用一个简单的 URL 和 $_GET,我保留我的横幅类型,然后像这样加载它:

public function view(?Server $server, Request $request, BannerManager $bannerManager)
{
   ...

    return $bannerManager->getBannerType($request->query->get('slug'))->process($request, $server);
}