重载父方法时,为什么 PHP8.1 转向弃用不兼容的 return 类型?
When overloading a parent method, why did PHP8.1 change towards deprecating incompatible return types?
我像这样重载了 mysqli
class 的 query
方法:
class MySql extends \mysqli
{
function query(string $sql): ?MySqlResult // line #30
{
$result = parent::query($sql);
return new MySqlResult($result);
}
}
in PHP8.0 这不是问题。
但是,从 PHP8.1 开始,我现在收到此错误:
Deprecated: Return type of Repository\MySql\MySql::query($sql, $resultmode = null)
should either be compatible with mysqli::query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool
, or the #[\ReturnTypeWillChange]
attribute should be used to temporarily suppress the notice in repository\src\MySql\MySql.php
on line 30
我知道如何修复错误 - 我可能最终会更改方法的名称,因为我想 return 我自己的自定义对象。
问题
我正在寻找一个答案,从理论和面向对象的角度捕捉这种变化的需要,可能使用语言理论,或者将其与其他语言进行比较。
为什么需要进行此更改?
进行此更改的必要性或原因是什么?
有什么方法可以在扩展 class 时在 PHP 中允许重载 return 类型?
为了使语言保持一致,必须进行更改。如果重写的方法可以 return 不同的类型,它会导致代码非常混乱。它基本上意味着被覆盖的函数做了完全不同的事情。 This behaviour was never allowed in PHP. 这样的代码总是会抛出:
class A {
public function foo():string {
return '';
}
}
class B extends A {
public function foo():int {
return 1;
}
}
唯一的问题是标准 类 没有在内部指定 return 类型。由于 returning 资源、混合、联合类型等原因,许多方法无法指定类型。这意味着实际上它们没有 return 类型。 PHP 规则说如果重写的方法没有 return 类型,子方法可以指定(缩小)类型:
class A {
public function foo() { // this could also be :mixed but that was only introduced in PHP 8
return '';
}
}
class B extends A {
public function foo():int {
return 1;
}
}
所以,你问错了问题。问题不是为什么自 PHP 8.1 以来不能覆盖 return 类型,因为情况总是如此,而是为什么 PHP internal 类 没有指定return类型。
从 PHP 8.1 开始,declare most return types 成为可能。但是,由于这会导致重大变化,与通常会产生的致命错误相比,内部方法暂时只会抛出 Deprecation 消息。在 PHP 9.0 中,所有这些都将得到修复。
对于您的特定情况,您应该使用组合而不是继承。大多数时候应该避免继承,尤其是内部 类。组合提供了更大的灵活性并且更容易测试。
理解这一点的方法是将函数签名视为 合同。在 built-in mysqli
class 中,我们有以下签名:
public function query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool
我们可以将其翻译成英文:
- 如果我有一个
mysqli
的实例 ...
- ...我可以调用方法
query
(public
) ...
- ...第一个参数是
string
...
- ... 和可选的
int
作为第二个参数 ...
- ... 我将返回
mysqli_result
对象或布尔值
所以,下面的代码被合约保证运行成功:
assert($foo instanceof \mysqli);
$result = $foo->query('Select 1', MYSQLI_USE_RESULT);
assert($result instanceof mysqli_result || is_bool($result));
现在让我们 运行 使用您建议的 class:
实例的代码
assert($foo instanceof \mysqli);
// Success: `MySql` is a sub-type of `\mysqli`
$result = $foo->query('Select 1', MYSQLI_USE_RESULT);
// Success, but second argument ignored
assert($result instanceof mysqli_result || is_bool($result));
// Failure! Function may return null, which doesn't meet this assertion
// If the custom MysqlResult doesn't extend mysqli_result, that will also fail
因此,如您所见,您的 class 未能满足 built-in class 的 合同。
从逻辑上讲,这一直是错误,因为您“在精神上”违反了合同,但最近才成为可能PHP到强制执行这个。这就是为什么它目前不是硬错误,这样您就有机会修复“侥幸逃脱”的旧代码。
我像这样重载了 mysqli
class 的 query
方法:
class MySql extends \mysqli
{
function query(string $sql): ?MySqlResult // line #30
{
$result = parent::query($sql);
return new MySqlResult($result);
}
}
in PHP8.0 这不是问题。 但是,从 PHP8.1 开始,我现在收到此错误:
Deprecated: Return type of
Repository\MySql\MySql::query($sql, $resultmode = null)
should either be compatible withmysqli::query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool
, or the#[\ReturnTypeWillChange]
attribute should be used to temporarily suppress the notice inrepository\src\MySql\MySql.php
on line 30
我知道如何修复错误 - 我可能最终会更改方法的名称,因为我想 return 我自己的自定义对象。
问题
我正在寻找一个答案,从理论和面向对象的角度捕捉这种变化的需要,可能使用语言理论,或者将其与其他语言进行比较。
为什么需要进行此更改? 进行此更改的必要性或原因是什么? 有什么方法可以在扩展 class 时在 PHP 中允许重载 return 类型?
为了使语言保持一致,必须进行更改。如果重写的方法可以 return 不同的类型,它会导致代码非常混乱。它基本上意味着被覆盖的函数做了完全不同的事情。 This behaviour was never allowed in PHP. 这样的代码总是会抛出:
class A {
public function foo():string {
return '';
}
}
class B extends A {
public function foo():int {
return 1;
}
}
唯一的问题是标准 类 没有在内部指定 return 类型。由于 returning 资源、混合、联合类型等原因,许多方法无法指定类型。这意味着实际上它们没有 return 类型。 PHP 规则说如果重写的方法没有 return 类型,子方法可以指定(缩小)类型:
class A {
public function foo() { // this could also be :mixed but that was only introduced in PHP 8
return '';
}
}
class B extends A {
public function foo():int {
return 1;
}
}
所以,你问错了问题。问题不是为什么自 PHP 8.1 以来不能覆盖 return 类型,因为情况总是如此,而是为什么 PHP internal 类 没有指定return类型。
从 PHP 8.1 开始,declare most return types 成为可能。但是,由于这会导致重大变化,与通常会产生的致命错误相比,内部方法暂时只会抛出 Deprecation 消息。在 PHP 9.0 中,所有这些都将得到修复。
对于您的特定情况,您应该使用组合而不是继承。大多数时候应该避免继承,尤其是内部 类。组合提供了更大的灵活性并且更容易测试。
理解这一点的方法是将函数签名视为 合同。在 built-in mysqli
class 中,我们有以下签名:
public function query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool
我们可以将其翻译成英文:
- 如果我有一个
mysqli
的实例 ... - ...我可以调用方法
query
(public
) ... - ...第一个参数是
string
... - ... 和可选的
int
作为第二个参数 ... - ... 我将返回
mysqli_result
对象或布尔值
所以,下面的代码被合约保证运行成功:
assert($foo instanceof \mysqli);
$result = $foo->query('Select 1', MYSQLI_USE_RESULT);
assert($result instanceof mysqli_result || is_bool($result));
现在让我们 运行 使用您建议的 class:
实例的代码assert($foo instanceof \mysqli);
// Success: `MySql` is a sub-type of `\mysqli`
$result = $foo->query('Select 1', MYSQLI_USE_RESULT);
// Success, but second argument ignored
assert($result instanceof mysqli_result || is_bool($result));
// Failure! Function may return null, which doesn't meet this assertion
// If the custom MysqlResult doesn't extend mysqli_result, that will also fail
因此,如您所见,您的 class 未能满足 built-in class 的 合同。
从逻辑上讲,这一直是错误,因为您“在精神上”违反了合同,但最近才成为可能PHP到强制执行这个。这就是为什么它目前不是硬错误,这样您就有机会修复“侥幸逃脱”的旧代码。