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