PHP 8.0: 类魔术方法签名严格执行
PHP 中的魔术方法是特殊的类方法名称,如果声明了这些名称,就会为类带来特殊的功能。
PHP 中有几种神奇的方法 __construct()
魔术方法在用 new Foo()
模式实例化新的类对象时调用,__get()
方法在请求不存在的类属性时调用,___toString()
魔术方法在类对象被强制为字符串时调用,依此类推。
尽管所有这些神奇的方法都有语义含义,但直到 PHP8.0 才通过编程实现。例如,__toString()
方法在语义上希望返回一个字符串,但可以返回任何数据类型,包括不能强制为字符串的数据类型。
class Foo {
public function __toString(): object {
}
}
在 PHP 8.0 及更高版本中,如果声明了一个魔术方法,那么它必须遵守函数签名,如果也声明了返回类型。
自 PHP 7.0 中引入返回类型以来,对其强制执行了一些签名检查。也就是说,__construct
、__destruct
和 __clone
根本不允许声明返回类型,甚至不允许声明 void
(void
是在PHP 7.1中添加的)。
类型是可选的
于 PHP 类 interface
不同,为了强制参数和返回类型出现在所有实现中,魔术方法强制允许根本不声明类型。
这允许实现魔术方法的现有类继续运行,而不必声明显式类型。
但是,如果声明了一个类型,那么它必须遵循签名。
签名不匹配的致命错误
PHP 8.0 在接口实现和类继承违法 LSP 时抛出致命错误。魔术方法遵循这一相同的模式。
- 返回类型允许变窄。
- 方法参数允许加宽参数类型。
允许方差
类似于标准的类/接口继承,魔术方法的方差也是允许的。
例如,应该返回混合类型的 __get()
方法可以声明其返回类型为 bool
,因为 bool
已经包含在混合类型中。
此外, 如果函数也能处理 CacheEntry
对象,__set()
魔术方法可以将它的方法参数范围从 __set(string $name, $value)
到 __set(string|CacheEntry $name, $value)
。
魔术方法签名
class GoodFoo {
public function __isset(string $name): bool {}
public function __get(string $name): mixed {}
public function __set(string $name, mixed $value): void {}
public function __unset(string $name): void {}
public function __set_state(array $properties): object {}
public function __call(string $name, array $arguments): mixed {}
public function __callStatic(string $name, array $arguments): mixed {}
public function __invoke(mixed $arguments): mixed {}
public function __clone(): void {}
public function __serialize(): array {}
public function __unserialize(array $data): void {}
public function __sleep(): array {}
public function __wakeup(): void {}
public function __debugInfo(): ?array {}
}
任何魔术方法实现都必须在其签名中包含所有参数,并且如果声明了返回类型,那么它也必须是子类型的相同类型。
否则,任何声明都会触发致命错误:
class BadFoo {
public function __isset(string $name): array {}
// mismatch ^^^^^
}
// Fatal error: BadFoo::__isset(): Return type must be bool when declared in ... on line ...
class BadBar {
public function __call(array $name, array $arguments): mixed {}
// mismatch ^^^^^
}
// Fatal error: BadBar::__call(): Parameter #1 ($name) must be of type string when declared in /in/JpKPd on line 4
魔术方法允许不带返回类型
在 PHP8.0 之前不允许声明任何返回类型的魔术方法继续强制执行这一点。
class BadBar {
public function __construct(): void {}
public function __destruct(): void {}
}
这两种声明在 PHP 8.0 和早期版本中都是不允许的。它们会导致一个致命错误:
Fatal error: Method BadBar::__construct() cannot declare a return type in ... on line ...
Fatal error: Method BadBar::__destruct() cannot declare a return type in ... on line ...
Stringable
接口
PHP 8.0 引入新的 Stringable
接口,它自动添加到任何实现 __toString()
魔术方法的类中。如果 Stringable
接口显示声明(如 Foo implements Stringable
),__toString()
方法签名用接口规则强制执行。
private
魔术方法
当在子类中重新声明 private
方法时,PHP 8.0 放松了签名强制。魔术方法遵守签名,即使它们被声明为 private
。
class Foo {
private function __get(): mixed {}
}
// Fatal error: Method Foo::__get() must take exactly 1 argument in ... on line ...
向后兼容性影响
这是向后兼容性的破坏性更改。然而,强制签名在语义上是正确的,并且允许方差,缓解了潜在的兼容性问题。
如果没有类型强制,则不会检查它们的类型。但是,这些方法必须接受相同数量的参数。
mixed
类型包括PHP中除 void
之外的所有类型,这使得任何返回类型都能实现返回类型签名。