1

我像这样重载query了类的方法mysqli

class MySql extends \mysqli
{
    function query(string $sql): ?MySqlResult  // line #30
    {
        $result = parent::query($sql);
        return new MySqlResult($result);
    }
}

在 PHP8.0 中这不是问题。但是,从 PHP8.1 开始,我现在收到此错误:

已弃用:返回类型 ofRepository\MySql\MySql::query($sql, $resultmode = null)应与 兼容mysqli::query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool,或者#[\ReturnTypeWillChange]应使用该属性暂时抑制第repository\src\MySql\MySql.php30 行中的通知

我知道如何修复错误——我可能最终会更改方法的名称,因为我想返回一个我自己的自定义对象。

问题

我正在寻找一个从理论和面向对象的角度捕捉这种变化需求的答案,可能使用语言理论,或者将其与其他语言进行比较。

为什么这个改变是必要的?进行此更改的需要或原因是什么?扩展类时,有什么方法可以在 PHP 中允许重载返回类型?

4

2 回答 2

3

为了使语言保持一致,这种改变是必要的。如果被覆盖的方法可以返回不同的类型,则会产生非常混乱的代码。这基本上意味着被覆盖的函数做了一些完全不同的事情。这种行为在 PHP 中是不允许的。像这样的代码总是会抛出:

class A {
    public function foo():string {
        return '';
    }
}

class B extends A {
    public function foo():int {
        return 1;
    }
}

唯一的问题是标准类没有在内部指定返回类型。由于返回资源、混合、联合类型等,许多方法无法指定类型。这意味着它们实际上没有返回类型。PHP 规则说,如果被覆盖的方法没有返回类型,则子方法可以指定(缩小)类型:

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 以来不能覆盖返回类型,因为总是如此,而是为什么 PHP 内部类没有指定返回类型。

自 PHP 8.1 起,声明大多数返回类型成为可能。但是,由于这会导致重大更改,与通常会产生的致命错误相比,内部方法暂时仅抛出弃用消息。在 PHP 9.0 中,所有这些都将得到修复。


对于您的特定情况,您应该使用组合而不是继承。大多数时候应该避免继承,尤其是内部类。组合提供了更大的灵活性并且更容易测试。

于 2022-02-14T16:53:08.993 回答
3

理解这一点的方法是将函数签名视为合约。在内置mysqli类中,我们有以下签名:

public function query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool

我们可以将其翻译成英文,如下所示:

  • 如果我有一个实例mysqli...
  • ...我可以调用query它的方法(public)...
  • ... 以 astring作为第一个参数 ...
  • ...以及可选的int作为第二个参数...
  • ...我将返回一个mysqli_result对象或布尔值

因此,以下代码由合约保证成功运行

assert($foo instanceof \mysqli);
$result = $foo->query('Select 1', MYSQLI_USE_RESULT);
assert($result instanceof mysqli_result || is_bool($result));

现在让我们使用您提议的类的实例运行该代码:

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

因此,如您所见,您的类不符合内置类的约定。

从逻辑上讲,这始终是一个错误,因为您“在精神上”违反了合同,但是直到最近 PHP 才有可能强制执行此操作。这就是为什么它目前不是一个硬错误,因此您有机会修复“侥幸逃脱”的旧代码。

于 2022-02-14T16:56:29.610 回答