Почему невозможно исключить исключение из __toString()?
Почему невозможно исключить исключение из __toString()?
class a
{
public function __toString()
{
throw new Exception();
}
}
$a = new a();
echo $a;
приведенный выше код создает следующее:
Fatal error: Method a::__toString() must not throw an exception in /var/www/localhost/htdocs/index.php on line 12
Я указал на http://php.net/manual/en/migration52.incompatible.php, где описано это поведение, но почему? Любые причины для этого?
Может кто-нибудь здесь это знает?
В отладчике ошибок php-dev-team, как обычно, ничего не говорит, кроме руководства: http://bugs.php.net/50699
Ответы
Ответ 1
После пары поисков я нашел это, в котором говорится:
Johannes объяснил, что не существует способа гарантировать, что исключение, созданное во время отливки в строку, будет корректно обработано Zend Engine, и что это не изменится, если большие части Engine переписываются. Он добавил, что в прошлом обсуждались такие проблемы, и предложил Гильерме проверить архивы.
Johannes, на который ссылается выше, - это диспетчер выпуска PHP 5.3, поэтому он, вероятно, является "официальным" объяснением, так как вы можете узнать, почему PHP ведет себя таким образом.
В разделе далее упоминается:
__toString()
будет, как ни странно, принять trigger_error().
Таким образом, не все потеряно в отчетах об ошибках в __toString()
.
Ответ 2
Я предполагаю, что __toString
хакерский и, следовательно, существует вне обычного стека. Тогда брошенное исключение не знает, куда идти.
Ответ 3
в ответ на принятый ответ, я придумал (возможно) лучший способ обработки исключений внутри __toString()
:
public function __toString()
{
try {
// ... do some stuff
// and try to return a string
$string = $this->doSomeStuff();
if (!is_string($string)) {
// we must throw an exception manually here because if $value
// is not a string, PHP will trigger an error right after the
// return statement, thus escaping our try/catch.
throw new \LogicException(__CLASS__ . "__toString() must return a string");
}
return $string;
} catch (\Exception $exception) {
$previousHandler = set_exception_handler(function (){
});
restore_error_handler();
call_user_func($previousHandler, $exception);
die;
}
}
Это предполагает наличие обработчика исключений, что имеет место для большинства фреймворков. Как и в случае с trigger_error
, это будет игнорировать цель try..catch, но все же это намного лучше, чем сброс вывода с помощью echo
. Кроме того, многие ошибки преобразования структуры в исключения, поэтому trigger_error
не будет работать в любом случае.
В качестве дополнительного бонуса вы получите полную трассировку стека, как с обычными исключениями, так и с нормальным производственным поведением вашей выборки.
Хорошо работает в Laravel, и я уверен, что он будет работать практически во всех современных фреймворках PHP.
Снимок экрана:
note: в этом примере output()
вызывается методом __toString()
.
![__toString() exception caught by Laravel exception handler]()
Ответ 4
Похоже, что с php 7.4 выдается исключение из __toString(). У меня была проверка совместимости php7.2, и он сказал так и указал Doctrine StaticReflectionClass и StaticReflectionProperty.
Пожалуйста, найдите больше информации о предложении https://wiki.php.net/rfc/tostring_exceptions
Ответ 5
Я не думаю, что обоснование этого решения когда-либо публиковалось. Похоже, какое-то внутреннее архитектурное ограничение.
На более абстрактном уровне это имеет смысл. Объект должен иметь возможность возвращать строковое представление самого себя, нет причин для отказа такого рода действий.
Ответ 6
Я нашел простое решение:
Просто верните что-то вроде нестрокового типа в __toString, когда происходит преобразование ошибок в строку: NULL, FALSE или даже Exception.
Это приведет к выходу, подобному этому (в php -a интерактивном SHELL):
Catchable fatal error: Method MyClass::__toString() must return a string value in php shell code on line 1