Как изменить HTTP-ответ и показать сообщение об ошибке, когда исключение происходит в деструкторе?

У меня есть ситуация, когда функция PHP пытается перенаправить браузер через HTTP 302, но исключение бросается в деструктор, вызываемый «exit». Фактический код, о котором идет речь, это метод _doRedirect () SimpleSAML, но вот упрощенная ситуация:

header('Location: http://somewhere.com', TRUE, 302); exit; // end script execution 

«Выход» запускает деструктор для несвязанного класса, и данные об ошибках записываются в ответ HTTP … но никто не замечает эту ошибку, поскольку браузер выполняет перенаправление HTTP 302.

В идеале я бы хотел изменить код состояния на HTTP 500, чтобы браузер просто отображал страницу с ошибкой на нем. Но возможно ли это? Если нет, какие у меня варианты?

К сожалению, вы сталкиваетесь с проблемой дизайна .

  • Функция set_error_handler не обрабатывает неперехваченные исключения (это ошибка уровня E_ERROR):

Из документации:

Следующие типы ошибок не могут быть обработаны с помощью определенной пользователем функции: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING и большая часть E_STRICT, поднятых в файле, где вызывается set_error_handler ().

  • Функция reguster_shutdown_function вызывается после вашего вызова exit (или когда заканчивается конец скрипта), но до уничтожения ваших объектов.

Поэтому, если вы пытаетесь использовать такой код:

 <?php register_shutdown_function(function() { $error = error_get_last(); if (!is_null($error)) { header_remove(); header("HTTP/1.1 500 Internal Server Error"); // do something with $error } else { echo "no error happened"; } }); class A { public function __destruct() { throw new \Exception("Hey dude!"); } } header("HTTP/1.1 301 Moved Permanently"); $a = new A; 

Вы получите сообщение об ошибке «Ошибка» и появится фатальная ошибка, а 301 останется без изменений.

введите описание изображения здесь

Если вы считаете, что можете уничтожить свой объект из-за обратного вызова register_shutdown_function , вы столкнетесь с проблемой определения области, в которой ваш объект ссылается в своей исходной области, и в области register_shutdown_function : поскольку одна ссылка объекта все еще существует, она будет не быть уничтоженным.

 <?php class A { public function __destruct() { throw new \Exception("Hey dude!"); } } $a = new A; /* global scope */ register_shutdown_function(function() use ($a /* local scope */) { try { unset($a); /* removed from local scope but another reference still exists in global scope */ } catch (\Exception $e) { echo "Catched!\n"; } }); /* destructor called here */ с <?php class A { public function __destruct() { throw new \Exception("Hey dude!"); } } $a = new A; /* global scope */ register_shutdown_function(function() use ($a /* local scope */) { try { unset($a); /* removed from local scope but another reference still exists in global scope */ } catch (\Exception $e) { echo "Catched!\n"; } }); /* destructor called here */ 

Даже хорошо известные компоненты отладки не поддерживают это.

Например, используя компонент Symfony2 Debug :

composer.json

 { "require": { "symfony/debug": "~2.5" } } 

test.php

 <?php require('vendor/autoload.php'); use Symfony\Component\Debug\Debug; Debug::enable(); class A { public function __destruct() { throw new \Exception("Hey dude!"); } } $a = new A; 

Компонент включен, но не отображает приятное ожидаемое исключение.

Я думаю, что последовательность событий такова:

 /* 1 */ header("Location: /some/path"); /* 2 */ echo "Some content"; /* 3 */ exit; /* 3.1 */ throw new Exception("Some exception"); 

Когда контент отправляется на шаге 2, PHP также должен отправить заголовок, который установлен в значение 302. После отправки заголовка исключение не может изменить код состояния. Сгенерированное сообщение об ошибке игнорируется браузером при его переходе в указанное место.

Решение:

Вы можете попробовать добавить ob_start() чтобы начать буферизацию в подходящем месте . Под подходящим местоположением я имею в виду где угодно до этапа 1 на приведенной выше иллюстрации. Буферизация отменяет отправку заголовка и / или содержимого до тех пор, пока страница не будет обработана (или буфер не будет очищен явно). Это может дать PHP возможность изменить код ответа и заголовок.

Похоже, что вы используете библиотеку, которая заставляет перенаправить. Я не уверен, почему, но я бы, вероятно, советовал не взламывать это. Один из вариантов заключается в том, чтобы сохранить ошибку в данных флэш-памяти из класса, который его бросает, а затем на страницу, на которую она перенаправляется, если данные флэш-памяти существуют, тогда отобразите ошибку. Таким образом, вам не нужно беспокоиться о том, чтобы случайно обновить simpleSAML и снова сломать его или другие непредвиденные проблемы.

http://laravel.com/docs/4.2/session#flash-data