У меня есть ситуация, когда функция PHP пытается перенаправить браузер через HTTP 302, но в деструкторе, вызываемом при выходе, генерируется исключение. Фактический код, о котором идет речь _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 */
Даже хорошо известные компоненты отладки не поддерживают это.
Например, используя 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 и о том, что он может снова сломаться, или о других непреднамеренных проблемах.