考虑以下PHP接口:
interface Item {
// some methods here
}
interface SuperItem extends Item {
// some extra methods here, not defined in Item
}
interface Collection {
public function add(Item $item);
// more methods here
}
interface SuperCollection extends Collection {
public function add(SuperItem $item);
// more methods here that "override" the Collection methods like "add()" does
}
我正在使用PHPStorm,当我这样做时,我在IDE中遇到一个错误,它基本上表明add()
中SuperCollection
的定义与它扩展的接口中的定义不兼容,Collection
。
在某种程度上,我可以看到这是一个问题,因为该方法的签名与“覆盖”完全的签名不匹配。但是,我确实认为这是兼容的,因为SuperItem
扩展了Item
,因此我会将add(SuperItem)
视为与add(Item)
相同。
我很好奇PHP是否支持这个版本(5.4或更高版本),并且IDE可能存在一个无法正确识别错误的错误。
答案 0 :(得分:12)
不,我非常确定PHP在任何版本中都不支持这一点,而且它宁愿打败界面。
接口的关键在于它为您提供了与引用相同接口的其他代码的固定合同。
例如,考虑这样的函数:
function doSomething(Collection $loopMe) { ..... }
此函数需要接收实现Collection
接口的对象。
在函数中,程序员可以编写对Collection
中定义的方法的调用,知道该对象将实现这些方法。
如果你有这样的重写接口,那么你就遇到了问题,因为SuperCollection
对象可以传递给函数。它会工作,因为它继承了它也是一个Collection
对象。但是函数中的代码不再能确定它知道add()
方法的定义是什么。
根据定义,接口是固定合同。这是不可改变的。
作为替代方案,您可以考虑使用抽象类而不是接口。这将允许您在非严格模式下覆盖,但如果您使用严格模式仍然会出现错误,原因相同。
答案 1 :(得分:3)
作为一种解决方法,我在接口中使用PHPDoc块。
interface Collection {
/**
* @param Item $item
*/
public function add($item);
// more methods here
}
interface SuperCollection extends Collection {
/**
* @param SuperItem $item
*/
public function add($item);
// more methods here that "override" the Collection methods like "add()" does
}
这样,如果您正确使用接口,IDE应该可以帮助您捕获一些错误。您也可以使用similar technique覆盖返回值类型。
答案 2 :(得分:2)
答案 3 :(得分:0)
问题不在IDE中。在PHP中,您无法覆盖方法。并且兼容性只是相反的方向 - 您可以安全地期望父类的实例并接收子类。但是当您期望一个子类时,如果您收到父类,则无法安全 - 子类可能会定义父类中不存在的方法。但是,你仍然无法覆盖方法
答案 4 :(得分:0)
当我有一个我可能需要重载的方法(PHP不支持)时,我确保其中一个方法参数(通常是最后一个)是一个数组。通过这种方式,我可以通过我需要的任何东西。然后我可以在函数内测试各种数组元素,告诉我需要执行的方法中的例程,通常是在select / case中。
答案 5 :(得分:0)
不允许扩展接口更改方法定义。如果您的SuperItem正在扩展Item,它应该通过实现Collection接口的类传递而没有问题。
但根据你真正想做的事情,你可以尝试:
使用稍微不同的SuperItem方法创建界面并实现:
interface SuperCollection extends Collection {
public function addSuper(SuperItem $superItem);
}
使用装饰器模式创建几乎相同的界面而不扩展:
interface Collection {
public function add(Item $item);
// more methods here
}
interface SuperCollection {
public function add(SuperItem $item);
// more methods here that "override" the Collection methods like "add()" does
}
然后decorator(抽象)类,它将使用这个接口:
class BasicCollection implements Collection {
public function add(Item $item)
{
}
}
class DecoratingBasicCollection implements SuperCollection {
protected $collection;
public function __construct(Collection $collection)
{
$this->collection = $collection;
}
public function add(SuperItem $item)
{
$this->collection->add($item);
}
}
答案 6 :(得分:0)
如果PHP 7.4计划以improved type variance发布,那么答案将在今年晚些时候(2019年)改变。
问题代码将仍然无效:
interface Item {
// some methods here
}
interface SuperItem extends Item {
// some extra methods here, not defined in Item
}
interface Collection {
public function add(Item $item);
// more methods here
}
interface SuperCollection extends Collection {
public function add(SuperItem $item); // This will still be a compile error
// more methods here that "override" the Collection methods like "add()" does
}
因为Collection
接口保证实现该接口的任何对象都可以接受Item
类型的任何对象作为add
的参数。
但是,以下代码在PHP 7.4中有效:
interface Item {
// some methods here
}
interface SuperItem extends Item {
// some extra methods here, not defined in Item
}
interface Collection {
public function add(SuperItem $item);
// more methods here
}
interface SuperCollection extends Collection {
public function add(Item $item); // no problem
// more methods here that "override" the Collection methods like "add()" does
}
在这种情况下,Collection
保证它可以接受任何SuperItem
。由于所有SuperItem
都是Item
,因此SuperCollection
也会做出此保证,同时还要保证它可以接受任何其他类型的Item
。这就是所谓的Contravariant method parameter type。
当前版本的PHP中类型差异的形式很有限。假设其他接口与问题相同,则SuperCollection
可以定义为:
interface SuperCollection extends Collection {
public function add($item); // no problem
// more methods here that "override" the Collection methods like "add()" does
}
这可以解释为意味着可以将任何值传递给add
方法。那当然包括所有Item
,所以这仍然是类型安全的,或者可以解释为意味着可以传递通常以mixed
形式记录的未指定值类,并且程序员需要使用确切了解该功能可以使用的其他知识。