У меня есть ситуация, когда функция PHP пытается перенаправить браузер через HTTP 302, но исключение бросается в деструктор, вызываемый «exit». Фактический код, о котором идет речь, это метод _doRedirect () SimpleSAML, но вот упрощенная ситуация:
header('Location: http://somewhere.com', TRUE, 302); exit; // end script execution
«Выход» запускает деструктор для несвязанного класса, и данные об ошибках записываются в ответ HTTP … но никто не замечает эту ошибку, поскольку браузер выполняет перенаправление HTTP 302.
В идеале я бы хотел изменить код состояния на HTTP 500, чтобы браузер просто отображал страницу с ошибкой на нем. Но возможно ли это? Если нет, какие у меня варианты?
К сожалению, вы сталкиваетесь с проблемой дизайна .
Из документации:
Следующие типы ошибок не могут быть обработаны с помощью определенной пользователем функции: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING и большая часть E_STRICT, поднятых в файле, где вызывается set_error_handler ().
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 и снова сломать его или другие непредвиденные проблемы.