【问题标题】:Symfony setter injection for polymorphic service用于多态服务的 Symfony setter 注入
【发布时间】: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


【解决方案1】:

为 FooInterface 和 FooService 创建父类(例如 FooObject)。

使 FooInterface 和 FooService ...从这个新类扩展

将 $fooDependency 定义为 FooObject

【讨论】:

  • 那么 Symfony 如何知道要注入的 FooInterface 的正确实现呢?
  • 我不知道 Symfony 是否知道,如果它是 FooService 的实例,也许你应该检查你的方法,如果不是,则抛出错误。希望对你有帮助。
【解决方案2】:

我想要的是:

  • 强制子类使用 setter 方法(或其他注入依赖的方式)
  • 允许每个子类注入不同的 FooInterface 实现
  • 使用自动装配,这样我就不会弄乱我的服务文件或让未来的开发人员感到困惑

有多种方法可以在 PHP 中实现多态性,但似乎您可以通过定义一个抽象方法来检索 FooInterface 实例而不是 setter 方法来解决这个问题。这将使您能够将具体的 FooInterface 实现直接注入到您的构造函数中,同时它保持不可变。

interface FooInterface
{
    public function getName(): string;
}

class ConcreteFoo implements FooInterface
{
    public function getName(): string
    {
        return 'a string';
    }
}

abstract class Father
{
    public function doSomething(): void
    {
        $name = $this->getFoo()->getName();
        
        // ...
    }

    abstract protected function getFoo(): FooInterface;
}

class Child extends Father
{
    private ConcreteFoo $foo;
    
    public function __construct(ConcreteFoo $foo) 
    {
        $this->foo = $foo;
    }
    
    protected function getFoo(): ConcreteFoo
    {
        return $this->foo;
    }
}

通过使用这种方法,您的父类将不依赖于属性,而是依赖于每个子类都必须实现的方法。这将允许更大的灵活性、可维护性,并且 DI 自动装配不会成为问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-24
    • 1970-01-01
    • 2018-01-22
    • 1970-01-01
    • 1970-01-01
    • 2012-08-23
    • 2017-12-05
    • 2017-01-23
    相关资源
    最近更新 更多