TL; DR: поддержка выводов типа для методов magi c нарушает обратную совместимость.
Пример: что выводит этот код?
$foo = new Foo;
$bar = $foo->__construct();
echo get_class($bar);
Если вы сказали Foo
, вы не правы: это Bar
.
PHP имеет долгую и сложную эволюцию обработки возвращаемого типа.
- До PHP 7.0 подсказки типа возврата были ошибкой разбора.
- В PHP 7.0 мы получили объявления типа возврата с очень простыми правилами ( RF C ), и после, пожалуй, самых спорных внутренних дебатов в истории, мы получили строгие типы ( RF C).
- PHP хромали вместе с некоторыми странностями в ко- и противоречивости до PHP 7.4, где мы получили многие из этих отсортированных ( RF C).
Поведение сегодняшнего дня отражает рост этого организма, бородавки и все остальное. ,
Вы указываете, что поведение __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()
дал вам ошибку не потому, что типы были разные , а потому что вы вообще указали тип ! Это также ошибка компиляции :
class Foo {
public function __clone(): Foo {
return new Foo;
}
}
"Зачем ?!", вы кричите.
Я считаю, что есть две причины:
- Internals обязана поддерживать высокую планку обратной совместимости.
- Постепенное улучшение происходит медленно, каждое улучшение опирается на более ранние.
Давайте поговорим о # 1. Рассмотрим этот код , который действителен вплоть до 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)
включен? Ну, потому что этот код отлично подходит и для режима строгих типов! Это даже имеет смысл :
<?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
, никто бы не бросил глаз. Единственная странная часть: имя метода «зарезервировано».
Таким образом, движок не может (а) принудительно __toString
вернуть тип строки или (б) помешать вам установить собственный тип возвращаемого значения. Потому что выполнение любого из них нарушило бы обратную совместимость.
Однако мы могли бы реализовать новый положительный выбор, который говорит, что эти методы не могут быть вызваны напрямую. Как только мы это сделаем, мы можем добавить к ним вывод типа. Гипотетически:
<?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: когда у нас есть способ защиты обратной совместимости, мы можем добавить способ принудительного применения типов к методам magi c.
В резюме, __construct
, __destruct
, __clone
, __toString
, et c. обе (а) функции, которые вызывает движок в определенных обстоятельствах, для которых он может разумно вывести типы, и (б) функции, которые - исторически - могут вызываться напрямую способами, которые нарушают разумный вывод типа из (1).
Это причина PR 4117 , чтобы исправить Ошибка # 69718 заблокирована.
Единственный способ сломать этого пат: разработчик соглашается с обещанием, что эти методы не могут быть вызваны напрямую. Это освобождает движок для применения строгих правил вывода типов.