Symfony 动态服务使用

Symfony 动态服务使用,symfony,Symfony,我希望使用服务处理表中的数据,并在记录中指定要使用的正确服务。假设我有一个包含以下数据的表 | id | value1 | value2 | service | result | |----|---------|---------|----------------|--------| | 1 | string1 | string2 | string_version | | | 2 | int1 | int2 | int_version |

我希望使用服务处理表中的数据,并在记录中指定要使用的正确服务。假设我有一个包含以下数据的表

| id | value1  | value2  | service        | result |
|----|---------|---------|----------------|--------|
| 1  | string1 | string2 | string_version |        |
| 2  | int1    | int2    | int_version    |        |
| 3  | string3 | string3 | string_version |        |
我想处理父服务
计算器
中的每一行,它循环每个记录并根据
服务
的值获取服务,服务使用
值1
值2
计算结果,然后将结果存储在
结果

目前,我已经创建了
计算器
服务,其中一个参数是服务容器-然后我可以使用
get
获得我需要的实际服务:

class Calculator {
  private $container;

  public function __construct(ContainerInterface $container) {
     $this->container = $container;
  }

  public function calcResult($record) {
    $service = $this->container->get($record->getService());
    $result = $service->process($record->getValue1(),$result->getValue2());
    $record->setResult($result);
  }
}

但是。。。这不是很容易测试的——因为我正在传递整个服务容器。我还希望将来能够添加新服务,这样第三方就可以添加具有特定服务名称的捆绑包,并使用该捆绑包处理表中的记录

这是我尝试做的一个非常简化的版本,但是能够动态地从另一个服务传递/获取服务是我尝试做的


我如何修改此代码,以便获得基于数据库中动态值的服务,并允许第三方添加“处理”服务-当然,这将实现一个接口-以便确保存在正确的方法

您有两个明显的候选者:

  • a(在OOP中被视为反模式)
  • (战略模式)
我个人(总是)在这样的场景中使用标记服务(策略模式),但仍然会为每个场景提供示例,所以由您自己决定

注意:如果使用如下所示的服务定位器,您的服务中会出现重复和一些难看的代码

服务定位器

interface ServiceLocatorInterface
{
    public function locate(string $id);
}
-

-

-

用法:

class YourService
{
    private $serviceLocator;

    public function __construct(\App\ServiceLocatorInterface $serviceLocator)
    { 
        $this->serviceLocator = $serviceLocator;
    }

    public function yourMethod()
    {
        /** @var StringCalculator $calculator */
        $calculator = $this->serviceLocator->locate('string_version');
        $result = $calculator->calculate('1', '2'); // result: 1 - 2

        /** @var IntCalculator $calculator */
        $calculator = $this->serviceLocator->locate('int_version');
        $result = $calculator->calculate(1, 2); // result: 3
    }
}
class YourService
{
    private $calculator;

    public function __construct(\App\Strategy\Calculator $calculator)
    { 
        $this->calculator = $calculator;
    }

    public function yourMethod()
    {
        // result: 1 - 2
        $result = $this->calculator->calculate('string_version', 1, 2);
        // result: 3
        $result = $this->calculator->calculate('int_version', 1, 2);
    }
}
标记服务

service:
    App\Strategy\Calculator:
        arguments: [!tagged calculator]

    App\Strategy\StringCalculatorStrategy:
        tags:
            - { name: calculator }

    App\Strategy\IntCalculatorStrategy:
        tags:
            - { name: calculator }
-

-

-

-

用法:

class YourService
{
    private $serviceLocator;

    public function __construct(\App\ServiceLocatorInterface $serviceLocator)
    { 
        $this->serviceLocator = $serviceLocator;
    }

    public function yourMethod()
    {
        /** @var StringCalculator $calculator */
        $calculator = $this->serviceLocator->locate('string_version');
        $result = $calculator->calculate('1', '2'); // result: 1 - 2

        /** @var IntCalculator $calculator */
        $calculator = $this->serviceLocator->locate('int_version');
        $result = $calculator->calculate(1, 2); // result: 3
    }
}
class YourService
{
    private $calculator;

    public function __construct(\App\Strategy\Calculator $calculator)
    { 
        $this->calculator = $calculator;
    }

    public function yourMethod()
    {
        // result: 1 - 2
        $result = $this->calculator->calculate('string_version', 1, 2);
        // result: 3
        $result = $this->calculator->calculate('int_version', 1, 2);
    }
}

A基本上是一个容器,其中包含数量有限的服务,这些服务实现了给定的接口。@Cerad谢谢-这正是我想要的,就像上面提到的@Cerad一样,您可以使用服务定位器。然而,有一个更好的版本是标记服务。请参阅下面的详细答案。您是正确的,有几种方法可以解决这个常见问题,但是您的服务定位器示例不必要地复杂。当您事先确切知道需要哪些服务时,请使用订户。通过调整您的订户代码来添加附加服务会有点困难。当您需要更动态的东西时,扩展Symfony ServiceLocator类。不幸的是!标记符号目前不适用于服务定位器。但最大的区别是,策略方法将实际实例化所有可用的服务,而定位器模式仅在您实际访问给定服务时实例化。这真的有区别吗?可能/可能不是,但作为一项规则,如果不需要,不创建对象是一件好事。您可能还想通读我在链接答案中的辩论。我同意,服务定位器示例不是一个成熟的示例,而是一个初始的知识共享示例。
!标记的
版本并不适用于服务定位器。这纯粹是针对标记服务的例子,我宁愿尽可能地将我的代码与我正在使用的框架解耦。我也尽我所能避免反模式的东西,并坚持正确的模式,如战略模式。战略模式是开放/封闭原则的一个很好的例子。仅仅因为服务定位器选项仅在需要时实例化对象,我不觉得我应该忽略OOP原则和最佳实践。如果我不在乎,那么是的,服务定位器将是理想的。我倾向于遵循OOP的最佳实践,而不是方便。PS-
不必要的复杂
-这就是Symfony doc如何做到的,而不是我:)
use Traversable;

class Calculator
{
    private $calculators;

    public function __construct(Traversable $calculators)
    {
        $this->calculators = $calculators;
    }

    public function calculate(string $serviceName, $value1, $value2)
    {
        /** @var CalculatorStrategyInterface $calculator */
        foreach ($this->calculators as $calculator) {
            if ($calculator->canProcess($serviceName)) {
                return $calculator->process($value1, $value2);
            }
        }
    }
}
interface CalculatorStrategyInterface
{
    public function canProcess(string $serviceName): bool;

    public function process($value1, $value2);
}
class StringCalculatorStrategy implements CalculatorStrategyInterface
{
    public function canProcess(string $serviceName): bool
    {
        return $serviceName === 'string_version';
    }

    public function process($value1, $value2)
    {
        return $value1.' '.$value2;
    }
}
class IntCalculatorStrategy implements CalculatorStrategyInterface
{
    public function canProcess(string $serviceName): bool
    {
        return $serviceName === 'int_version';
    }

    public function process($value1, $value2)
    {
        return $value1 + $value2;
    }
}
class YourService
{
    private $calculator;

    public function __construct(\App\Strategy\Calculator $calculator)
    { 
        $this->calculator = $calculator;
    }

    public function yourMethod()
    {
        // result: 1 - 2
        $result = $this->calculator->calculate('string_version', 1, 2);
        // result: 3
        $result = $this->calculator->calculate('int_version', 1, 2);
    }
}