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

时间:2019-06-24 06:45:31

标签: symfony dependency-injection symfony-3.4

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

我需要注入这些类之一,但不能同时注入两者,并在ProcessorService中使用它们,以便在正确的Worker上使用调用接口方法。使用哪种工作程序服务取决于当前正在处理的工作程序。我将其分解:

Class WorkerProcessor {

  private $workerService;

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

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

正在使用的工作程序服务将基于正在处理的工作程序具有colourBlue的{​​{1}}属性。

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

在Symfony 3.4

上运行

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

1 个答案:

答案 0 :(得分:1)

注意:我正在使用Symfony 4.3.1。我将这样发布,然后帮助您将所有代码从该体系结构移至Symfony 3.4。

我正在使用类似的概念在项目中加载不同的类。首先让我解释一下,然后在该文本下添加代码。

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

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

BannerManagerPasssrc/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)]);
        }
    }
}

如您所见,此类应实现CompilerPassInterface。您会发现我在寻找标记为 banner.process_banners 的特定服务。稍后我将展示如何标记服务。然后,我从addBannerType调用BannerManager方法。

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

此类具有一个自定义方法(由我创建),称为process()。您可以随意命名,但是我认为这很冗长。所有参数都是我发送的,所以不要介意。您可以发送任何内容。

现在,我们有了Manager,并设置了编译器密码。是时候设置横幅类型(根据我的示例)并标记它们了!

我的横幅类型在 src / Service / Banner / Types (您的情况下应为 src / BUNDLE / Service / WhateverYouWant / Type )。这没关系!您可以稍后在services.yaml中进行更改。

这些类型正在实现我的BannerInterface。在这种情况下,该类下的代码无关紧要。我还要警告您一件事!您应该在addBannerType()内的BannerManager下看到我正在呼叫$banner->getType()。在我的情况下,这是从BannerInterface继承的一种方法,它具有唯一的字符串(在我的示例中,我有三种横幅类型:small,normal,large)。此方法可以有任何名称,但不要忘记在您的管理器中也进行更新。

我们快要准备好了!我们应该给它们加上标签,然后就可以尝试了!

转到您的services.yaml并添加以下行:

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

请查看标签!

无论我想显示自定义横幅,我都使用带有$ _GET的简单URL来保持横幅类型,然后按以下方式加载它:

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

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