Подтвердить что ты не робот

Почему невозможно исключить исключение из __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

4b9b3361

Ответ 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

Ответ 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