php 调用未声明接口的方法时可能出现多态调用

uinbv5nw  于 2023-03-28  发布在  PHP
关注(0)|答案(1)|浏览(176)

假设我有一个类C1,它在PHP 8中实现了两个接口(I1,I2)。
我将类C1注入到另一个类C2的构造函数中,并使用其中一个接口(I1)对其进行类型提示。
现在,当我调用I2接口的一个方法时,Sonarlint会抱怨消息:
潜在多态性调用。
我能以某种方式删除这些消息吗?

hsvhsicv

hsvhsicv1#

让我们把它做得更具体一点:

interface I1 {
   public function one();
}

class C2 {
   public function __construct( I1 $injected ) {
       $injected->one();
       $injected->two();
   }
}

请注意,我在这里没有显示接口I2或类C1,因为类C2中没有任何内容表明它们是相关的。
这个工具告诉你的是,* 只看类C2的定义 *,没有办法知道$injected->two()是一个有效的方法调用-我们唯一确定$injected拥有的方法是one(),因为我们知道它实现了接口I1
在实践中,你总是传递一个有更多方法的对象,并实现一个额外的接口,这一事实在C2类中根本没有记录。
解决这个问题的方法是向C2添加更多信息,即传递的对象必须实现 * I1 * 和 * I2

interface I1 {
   public function one();
}
interface I2 {
   public function two();
}

class C2 {
   public function __construct( I1&I2 $injected ) {
       // Both calls are guaranteed to be valid
       $injected->one();
       $injected->two();
   }
}

class C1 implements I1, I2 {
   public function one() { ... }
   public function two() { ... }
}
new C2( new C1 ); // passes the check, because it implements both interfaces

这种类型(I1&I2)中包含&的语法被称为“交集类型”,与更常见的“联合类型”语法相反,例如I1|I2意味着“必须实现I2I2中的至少一个”。
交叉类型是在PHP 8.1中添加的。如果你需要你的代码在旧版本上工作,这有点麻烦,但可以通过引入一个继承两个现有接口的新接口来实现。区别在于类C2必须显式实现这个新接口才能通过检查:

interface I1 {
   public function one();
}
interface I2 {
   public function two();
}
interface I1and2 extends I1, I2 {}

class C2 {
   public function __construct( I1and2 $injected ) {
       // Both calls are guaranteed to be valid
       $injected->one();
       $injected->two();
   }
}

class C1 implements I1and2 {
   public function one() { ... }
   public function two() { ... }
}
new C2( new C1 ); // passes the check

或者,如果你的两个接口实际上代表了类C2恰好覆盖的两个单独的功能,但是 * 可能 * 被单独的对象覆盖,你可以只使用两个参数,每个参数定义一个必需的接口:

interface I1 {
   public function one();
}
interface I2 {
   public function two();
}

class C2 {
   public function __construct( I1 $injected1, I2 $injected2 ) {
       // Valid call on any I1 instance
       $injected1->one();
       // Valid call on any I2 instance
       $injected2->two();
   }
}

class C1 implements I1, I2 {
   public function one() { ... }
   public function two() { ... }
}
$something = new C1;
new C2( $something, $something ); // passes both checks

请记住,在每种情况下,首先使用接口的目的是记录代码的“不变属性”,而不是你碰巧拥有的实现。

相关问题