【问题标题】:Injecting an arbitrary parameter to a service向服务注入任意参数
【发布时间】:2018-12-28 13:43:40
【问题描述】:

我想知道是否有针对以下案例的最佳实践。

例如,我有几个服务并将它们作为一个数组注入到“工厂”服务中。然后我调用这个工厂的一个方法,并且想要只获得一个服务取决于某些条件。之后我执行此服务并获得结果...

但是,其中一些服务需要我从客户请求中获得的随机字符串。

当然,我可以用这个字符串作为参数调用服务的方法,但是有几个服务不需要这个字符串,我会在方法中得到“未使用的变量”。

我想我可以从工厂获得服务,然后调用 setter 将此字符串添加到服务中。但它看起来不像是无状态服务。

是否有更优雅的解决方案来传递我无法注入服务或使用 setter 的参数?

这是我的代码中的样子

首先,我有一个要检查的所有服务器的接口。该服务应该支持客户,然后它应该呈现来自 DTO 的信息。

interface Renderable {
     public function supports(Customer $customer);
     public function render(CustomerDTO $dto);
}

接下来,我有几个服务。这个使用 DTO 来渲染数据。

class ServiceOne implements Renderable
{  
    public function suppots(Customer $customer)
    {
        return $customer->getPriority() === 1;
    }

    public function render(CustomerDTO $dto)
    {
        return 'One: '.$dto->getName();
    }
}

但是,有些服务不需要任何 DTO 来呈现,它们只是提供一个硬编码的值。

class ServiceTwo implements Renderable
{
    public function suppots(Customer $customer)
    {
        return $customer->getPriority() !== 1;
    }
    // service does not use DTO, it simply output result
    // so, I'll get a notice about unused variable
    // and I can not remove it from the method since it is in interface
    public function render(CustomerDTO $dto)
    {
        return 'Two';
    }
}

这是一家工厂。它将所有服务作为数组注入。然后它检查并返回支持客户实例的第一个服务。

class ServiceFactory
{
    /** @var Renderable[] */
    private $services;

    public function __construct(iterable $services)
    {
        $this->services = $services;
    }

    public function getRenderer(Customer $customer)
    {
        foreach ($this->services as $service)
        {
            if ($service->supports($customer)
            {
                return $service;
            }
        }
    }
}

这里就像我使用工厂及其结果

$customer = ...; // it comes from a database
$request = ...; // it comes from a http request

$renderService = $factory->getRenderer($customer);

$customerDTO = CustomerDTO::createFromData([
    'customerUid' => $customer->getUid(),
    'date' => new \DateTime(),
    'name' => $request->getSheetUid(),
    'tags' => $request->getTags(),
]);

$renderService->render($customerDTO);

所以,我必须使用 DTO 实例调用 Renderer::render。但有些服务不使用它来“渲染”数据。我也不能将它注入到渲染器服务中,因为这个对象(DTO)是在所有服务都已经注入时在运行时中构建的。我也无法将 RequestStack 注入服务。

【问题讨论】:

  • 如何确定哪些服务需要参数,哪些不需要?你能分享prototype-PHP代码吗?回答你会更容易
  • 如果一个服务需要基于请求的信息,那么根据定义,它不再是无状态的。话虽如此,您似乎可以将 RequestStack 注入需要它的服务中,并让它处理随机参数。
  • 我同意@Cerad,RequestStack 以干净且可预期的方式解决随机参数。获取方法见Symfony docs
  • @TomášVotruba,添加了一个代码示例。
  • 我明白了。我认为最好的选择是使 render() 参数可以为空。其他选项也是可能的,但增加的复杂性多于价值。

标签: symfony dependency-injection


【解决方案1】:

由于您的参数来自请求 - 它不能直接注入服务。根据您的服务的实际逻辑,您可以考虑下面列出的方法之一。我们将您的“来自客户请求的随机字符串”称为$requestParam 以供进一步参考。

在这两种情况下,您都需要从实际的 Request 对象中获取您的 $requestParam 并将其传递到其他地方。它可以通过不同的方式完成,我建议为kernel.request 事件创建侦听器(例如RequestParamListener),并在此处放置一段代码,该代码从Request 获取参数并将其进一步传递给该侦听器。对于下面列出的方法,我将假设 $requestParam 将以这种方式传递。

1.单独的提供者

您可以创建单独的类(例如RequestParamProvider),作为其他服务的$requestParam 的提供者。它将从RequestParamListener 接收$requestParam,而其他需要获取$requestParam 的服务将需要注入此提供程序并使用其getRequestParam() 方法来获取所需的参数。

从我的角度来看,这是最简单的方法,我会推荐它。

2。工厂直喷

由于您有一些工厂服务 - 您可以将这个 $requestParam 直接传递到工厂并让它初始化其他服务。不太灵活,因为您需要自己实现初始化逻辑并在项目发展时对其进行维护。

3。使用接口直接注入

您可以创建包含setRequestParam() 方法的单独接口(例如RequestParamAwareInterface),并让所有需要此$requestParam 的类实现此接口。之后,您将需要编写单独的compiler pass 来收集所有此类类(通过迭代ContainerBuilder 并按服务definition 中的类查找特定接口的实现)并将这些服务的数组传递给您的RequestParamListener .监听者将有义务为每个给定的服务传递$requestParam

这种方法可以让您的应用程序在不需要同步$requestParam 注入逻辑的情况下增长。然而,无论实际进一步使用创建的实例如何,都会以所有受影响服务的初步实例化为代价。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-30
    • 2018-10-11
    • 2021-06-07
    相关资源
    最近更新 更多