php - 为什么 php 允许它知道它不允许的无效返回类型声明?

标签 php php-internals

据我所知,php 有能力阻止在它知道有问题的地方声明返回类型。

class Foo {
    public function __clone(): Baz {
        return new Baz;
    }
}

class Baz {

}

$foo = new Foo;
$newFoo = clone $foo;

这会导致 Fatal error: Clone method Foo::__clone() cannot declare a return type ,这是完全合理的。

但是为什么 php 会允许这样的事情:
class Foo {
    public function __toString(): float {
        return "WAT!?!";
    }
}

echo new Foo;

这导致

Fatal error: Uncaught TypeError: Return value of Foo::__toString() must be of the type float, string returned



这没有意义,因为您是否尝试返回浮点数:

Fatal error: Uncaught Error: Method Foo::__toString() must return a string value



php 阻止这些类型方法的声明返回类型而不是给出那些可疑的错误不是更有意义吗?如果不是,这背后的内部原因是什么?在 之类的情况下,是否有一些机械障碍可以阻止 php 执行此操作?克隆 ?

最佳答案

TL;DR:支持魔术方法的类型推断打破了向后兼容性。

示例:这段代码输出什么?

$foo = new Foo;
$bar = $foo->__construct();
echo get_class($bar);

如果你说 Foo ,你错了:它是 Bar .

PHP 在其返回类型处理方面经历了漫长而复杂的演变。
  • 在 PHP 7.0 之前,返回类型提示是一个解析错误。
  • 在 PHP 7.0 中,我们得到了具有非常简单规则的返回类型声明 ( RFC ),在可能是有史以来最有争议的内部辩论之后,我们得到了严格类型 ( RFC )。
  • PHP 一瘸一拐地伴随着协方差和逆方差的一些奇怪之处,直到 PHP 7.4,我们对其中的许多进行了排序 ( RFC )。

  • 今天的行为反射(reflect)了这种有机增长,疣等等。

    您表示 __clone()行为是明智的,然后将其与明显不合理的行为进行比较 __toString()行为。我挑战,在对类型推断的任何理性预期下,它们都不是明智的。

    这是 __clone 引擎代码:
    6642     if (ce->clone) {                                                          
    6643         if (ce->clone->common.fn_flags & ZEND_ACC_STATIC) {                   
    6644             zend_error_noreturn(E_COMPILE_ERROR, "Clone method %s::%s() cannot be static",
    6645                 ZSTR_VAL(ce->name), ZSTR_VAL(ce->clone->common.function_name));
    6646         } else if (ce->clone->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {   
    6647             zend_error_noreturn(E_COMPILE_ERROR,                              
    6648                 "Clone method %s::%s() cannot declare a return type",         
    6649                 ZSTR_VAL(ce->name), ZSTR_VAL(ce->clone->common.function_name));
    6650         }                                                                     
    6651     }                                                                         
    

    请仔细注意该措辞(强调我的):

    Clone method ... cannot declare a return type


    __clone()给你一个错误不是因为类型不同,而是因为你给了一个类型!这也是compile error :
    class Foo {
        public function __clone(): Foo {
            return new Foo;
        }
    }
    

    “为什么?!”,你尖叫。

    我认为有两个原因:
  • Internals 受制于向后兼容性维护的高标准。
  • 增量改进是缓慢的,每一次改进都建立在早期改进的基础上。

  • 让我们谈谈#1。考虑 this code ,从 PHP 4.0 开始一直有效:
    <?php
    class Amount {
        var $amount;
    }
    class TaxedAmount extends Amount {
        var $rate;
        function __toString() {
            return $this->amount * $this->rate;
        }
    }
    $item = new TaxedAmount;
    $item->amount = 242.0;
    $item->rate = 1.13;
    echo "You owe me $" . $item->__toString() . " for this answer.";
    

    一些可怜的灵魂使用了__toString作为他们自己的方法,以一种完全合理的方式。现在保留其行为是重中之重,因此我们无法对破坏此代码的引擎进行更改。这就是 strict_types 的动机声明:允许选择更改解析器行为,以便在添加新行为的同时保持旧行为。

    您可能会问:我们为什么不在 declare(strict_types=1) 时解决这个问题?是吗?嗯,因为 this code在严格类型模式下也完全有效!它甚至使 sense :
    <?php declare(strict_types=1);
    
    class Amount {
        var $amount;
    }
    class TaxedAmount extends Amount {
        var $rate;
        function __toString(): float {
            return $this->amount * $this->rate;
        }
    }
    $item = new TaxedAmount;
    $item->amount = 242.0;
    $item->rate = 1.13;
    echo "You owe me $" . $item->__toString() . " for this answer.";
    

    这段代码没有任何味道。这是有效的 PHP 代码。如果方法被调用 getTotalAmount而不是 __toString ,没有人会眨眼。唯一奇怪的部分:方法名称是“保留的”。

    所以引擎既不能 (a) 强制执行 __toString返回字符串类型或 (b) 阻止您设置自己的返回类型。因为无论做什么都会违反向后兼容性。

    然而,我们可以做的是实现一个新的肯定选择加入,说明这些方法不能直接调用。一旦我们这样做了,那么我们就可以为它们添加类型推断。假设:
    <?php declare(strict_magic=1);
    
    class Person {
        function __construct(): Person {
        }
        function __toString(): string {
        }
        // ... other magic
    }
    
    (new Person)->__construct(); // Fatal Error: cannot call magic method on strict_magic object
    

    这就是第 2 点:一旦我们有了保护向后兼容性的方法,我们就可以添加一种方法来强制使用魔法方法的类型。

    总之,__construct , __destruct , __clone , __toString等都是 (a) 引擎在某些情况下调用的函数,它可以合理地推断类型,以及 (b) 函数 - 从历史上看 - 可以以违反 (1) 中的合理类型推断的方式直接调用。

    这就是原因PR 4117修复 Bug #69718被阻止。

    打破这种僵局的唯一方法:开发人员选择 promise 不能直接调用这些方法。这样做可以释放引擎来强制执行严格的类型推断规则。

    关于php - 为什么 php 允许它知道它不允许的无效返回类型声明?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60159633/

    相关文章:

    php - 构建 PHP 扩展并使用 call_user_function

    php - 自动后端生成器

    javascript - 检查 jQuery.validate() 上的元素上是否已有 "active"类

    php - CSV 到 Mysql 导入脚本以匹配字段

    php - 如何使用 Microsoft Visual C++ 2008 编译 PHP 扩展?

    php - 如何使用eclipse查看php函数源代码

    php - 在 PHP 上用双引号解析 JSON

    javascript - 如何使用“并存储其位置”从左或右、上或下移动产品 div

    php - 在哪里可以找到触发 unset() 垃圾回收的 "low memory"和 "free CPU cycles"调用?

    php - 从扩展返回 "Native"PHP 对象