向服务注入任意参数

时间:2018-12-28 13:43:41

标签: symfony dependency-injection

我想知道以下情况是否有最佳实践。

例如,我有几个服务,并将它们全部作为数组注入“工厂”服务中。然后,我调用该工厂的方法,并希望仅根据某些条件获得一项服务。之后,我执行此服务并获得结果...

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

当然,我可以使用此字符串作为参数来调用服务的方法,但是一些服务不需要此字符串,并且在该方法中我会得到“未使用的变量”。

我想我可以从工厂获得服务,然后致电设置员以将此字符串添加到服务中。但这看起来并不像无状态服务。

是否存在一种更优雅的解决方案来传递无法注入服务或无法使用设置器的参数?

这是我的代码中的样子

首先,我具有要检查的所有服务器的接口。该服务应支持客户,然后应从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;
            }
        }
    }
}

就像我使用factory及其结果

$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注入服务。

1 个答案:

答案 0 :(得分:1)

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

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

1。单独的提供商

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

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

2。通过工厂直接注射

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

3。使用界面直接注入

您可以创建单独的接口(例如RequestParamAwareInterface),该接口将包含setRequestParam()方法,并让所有需要该$requestParam的类来实现此接口。之后,您将需要编写单独的compiler pass来收集所有此类(通过遍历ContainerBuilder并通过服务definition中的类寻找特定接口的实现)并传递这些数组为您的RequestParamListener提供服务。相应地,侦听器将必须为每个给定服务传递$requestParam

这种方法将使您的应用程序能够增长而无需同步$requestParam注入逻辑。但是,这将花费所有受影响服务的初步实例化费用,而不管所创建实例的实际进一步使用如何。