【发布时间】:2023-01-04 04:15:25
【问题描述】:
我在默认服务配置下使用 Symfony 5.4 和 PHP 7.4。我有这样的抽象服务:
abstract class Parent
{
protected FooInterface $fooDependency;
public abstract function setFooDependency(FooInterface $fooDependency): void;
}
在我的子类中,我想为每个孩子注入 FooInterface 的不同实现。问题是,如果参数的类型不是FooInterface,PHP 会正确地抱怨子方法的声明不兼容:
class Child extends Parent
{
public abstract function setFooDependency(FooService $fooDependency): void {}
}
我可以通过不在抽象父类中定义 setFooDependency() 来解决这个问题,但是这种做法违背了首先拥有抽象类的目的(我想执行合同!)
另一种解决方法是在父类中实现setFooDependency()(而不是将其抽象化),然后在services.yaml中手动指定每个子类的参数:
services:
App\Service\Child:
calls:
- setFooDependency: [App\Service\FooService]
但我不喜欢这样,因为任何扩展抽象类的人都必须神奇地知道去添加服务定义。
我希望我错过了一个简单的解决方案?我想要的是:
- 强制子类使用 setter 方法(或其他注入依赖的方式)
- 允许每个子类注入
FooInterface的不同实现 - 使用自动装配,这样我就不会弄乱我的服务文件或让未来的开发人员感到困惑
【问题讨论】:
-
这是一个烦人的
feature。你可以制作一个 FooInterface service locator 并注入它。然后孩子会提取它需要的具体实现。这让每个人都对额外的代码行感到高兴。另一方面,您确实需要考虑 PHP 这样做的原因。你说你想要一个合同,但实际上你在概念上违反了你的子类中的合同。 -
@Cerad 我不同意我违反合同。我正在使用多态性。例如,
FooInterface定义了一个约定,其中有一个getName()方法返回一个字符串。在我的孩子班里,我需要打电话给getName()。只要我知道该方法存在并返回一个字符串,我不关心我得到的是哪种getName()实现。我违反合同的唯一方法是,如果我的一个子类依赖于合同中未定义的行为,在这种情况下,我的 IDE 将抱怨未知方法,等等。 -
Named aliases 可能是最简单的方法。您的子类签名将是
FooInterface $fooService1,其中参数名称将映射到 FooInterface 的特定实现。 Symfony 通过诸如 LoggerInterface 之类的东西做了相当多的事情,所以它应该是未来的开发者证明。您甚至可以使用编译器传递来创建基于服务类名的别名。 -
我仍然会说你原来的做法是违规的。父母说将注入 FooInterface。孩子说不,我会接受 FooService,它当然可以有额外的公共方法。如果您未来的开发人员之一没有意识到向 FooInterface 实现添加方法是不行的,那最终可能会成为一个问题,因为特定的子类所做的事情。我知道这是题外话,但 PHP 似乎同意我的看法。
标签: php symfony dependency-injection soa