背景:返回类型和协方差
自 PHP 7.0 起,可以指定函数或方法的返回类型,例如function example(): string
指示返回字符串的函数。这形成了其他代码可以依赖的合约。
例如,此类承诺该getList
方法将返回某种Iterator
:
class Base {
public function getList(): Iterator {
// ...
}
}
可以编写调用代码,知道如果$foo instanceOf Base
为真,那么$foo->getList() instanceOf Iterator
也为真。
如果扩展类,可以指定相同的返回类型,或更具体的返回类型(称为“协方差”的规则),调用代码的假设仍然成立:
class SubClass extends Base {
public function getList(): DirectoryIterator {
// ...
}
}
$foo = new SubClass;
var_dump($foo instanceOf Base); // true
var_dump($foo->getList() instanceOf Iterator); // true
但是如果你指定一个不同的返回类型,或者根本没有返回类型,假设就会失效,所以 PHP 不允许你这样做:
class NotPossible extends Base {
public function getList(): bool {
return false;
}
}
// Fatal error: Declaration of NotPossible::getList(): bool must be compatible with Base::getList(): Iterator
// If the error didn't happen...
$foo = new NotPossible;
var_dump($foo instanceOf Base); // would be true
var_dump($foo->getList() instanceOf Iterator); // would be false!
向后兼容性和弃用
如果将返回类型添加到现有类或接口,则扩展或实现的每个类也必须更改,否则将给出与上NotPossible
例相同的错误。
在 PHP 8.0中加入了Union Types,可以指定大部分内部函数和方法的返回类型;但是为任何未标记的类或接口方法指定它final
会立即破坏大量代码。
因此,相反,添加了“暂定”返回类型的概念:记录了正确的返回类型,但通常会出现错误的是问题中显示的弃用通知。
#[\ReturnTypeWillChange]
属性_
额外的问题是很多代码需要能够在多个 PHP 版本上运行,并且一些添加的返回类型在 8.0 之前的版本中无效。因此,要在代码中指示对返回类型的计划更改,您可以添加特殊属性 #[\ReturnTypeWillChange]
。这看起来像是对旧版本 PHP 的注释,但告诉 PHP 8.1 不要提出弃用通知。然后,一旦您不需要支持旧版本的 PHP,您就可以修复返回类型。
你现在应该怎么做?
首先,仔细阅读消息,找出需要更改的方法,以及正确的返回类型是什么。在上面的例子中:
Example::count() 的返回类型 ...
这说类的count
方法Example
需要改变......
... 应该与 Countable::count(): int 兼容
...并且预期的返回类型是int
,如Countable
接口上定义的那样
接下来,决定你能做什么:
- 您的课程是否已经返回正确的类型?在我们的示例中,是的,
42
对于返回类型为int
.
- 你能安全地改变方法的返回类型吗?如果您正在开发自己的应用程序,答案可能是“是”,只要您更新该类
extend
。如果您正在处理用户可能扩展了此类的库代码,则需要考虑对他们的影响。
如果您认为它是安全的,您可以简单地添加返回类型,如通知中所示:
class Example implements Countable {
public function count(): int {
return 42;
}
}
如果您需要支持旧版本的 PHP,或者尚未更新代码的用户,您可以暂时取消通知:
class Example implements Countable {
#[\ReturnTypeWillChange]
public function count() {
return 42;
}
}
重要的是要注意内部返回类型可能会在 PHP 9.0 中强制执行,因此请确保您有一个可靠的计划来更改使用此属性标记的方法。