编程

PHP 8.0: 类魔术方法签名严格执行

935 2023-09-09 11:48:00

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 根本不允许声明返回类型,甚至不允许声明 voidvoid是在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 之外的所有类型,这使得任何返回类型都能实现返回类型签名。

 

PHP