PHP предоставляет множество
инструментов для обработки ошибок и исключений , в частности
расширяемые
обработчики для управления ошибками и исключениями по мере их возникновения. Тем не менее, существует ряд фатальных
ошибок, которые PHP не предоставляет непосредственно инструментам для их обработки. В лучшем случае эти фатальные ошибки приведут к частичным выводам страниц или «белому экрану смерти».
Эти ошибки включают в себя:
E_ERROR
Фатальные ошибки времени выполнения. Они указывают на ошибки, которые не могут быть восстановлены, такие как проблема выделения памяти. Выполнение скрипта остановлено
E_PARSE
Ошибки синтаксического анализа во время компиляции. Ошибки разбора должны генерироваться только парсером
E_CORE_ERROR
Неустранимые ошибки, возникающие при первоначальном запуске PHP. Это похоже на E_ERROR, за исключением того, что оно генерируется ядром PHP
E_COMPILE_ERROR
Фатальные ошибки времени компиляции. Это похоже на E_ERROR, за исключением того, что оно генерируется Zend Scripting Engine.
Используя комбинацию функции выключения , error_get_last и буферизации вывода , можно создать структуру, которая позволит вам перехватывать фатальные ошибки. Теория работы довольно проста:
- Зарегистрируйте функцию отключения.
- Когда вызывается функция завершения работы, проверьте, завершил ли сценарий выполнение должным образом, запросив error_get_last на наличие фатальных ошибок.
- Обработайте фатальную ошибку, как считаете нужным.
Давайте посмотрим на некоторый код:
<?php
ob_start();
$res = register_shutdown_function('shutdown');
function shutdown()
{
if ($error = error_get_last()) {
if (isset($error['type']) && ($error['type'] == E_ERROR || $error['type'] == E_PARSE || $error['type'] == E_COMPILE_ERROR)) {
ob_end_clean();
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
echo '<h1>Bad Stuff Happend</h1>';
echo '<p>But that is okay</p>';
echo '<code>' . print_r($error, true) . '</code>';
}
}
}
ob_end_flush();
?>
Вся сгенерированная страница перед отправкой клиенту помещается в выходной буфер. Использование здесь буферизации вывода позволяет нам избавиться от частично сгенерированного контента при возникновении фатальной ошибки и заменить его пользовательским сообщением об ошибке. Например:
<?php
ob_start();
$res = register_shutdown_function('shutdown');
function shutdown()
{
if ($error = error_get_last()) {
if (isset($error['type']) && ($error['type'] == E_ERROR || $error['type'] == E_PARSE || $error['type'] == E_COMPILE_ERROR)) {
ob_end_clean();
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
echo '<h1>Bad Stuff Happend</h1>';
echo '<p>But that is okay</p>';
echo '<code>' . print_r($error, true) . '</code>';
}
}
}
?>
<h1>My Bad Day</h1>
<?php require 'FileNotFound.php'; ?>
<p>What a bad day...</p>
<?php
ob_end_flush();
?>
Неудачный вызов require генерирует фатальный E_COMPILE_ERROR. Мы улавливаем ошибку, и вместо того, чтобы рассказывать клиенту о «моем плохом дне», мы убираем это и даем пользователю очень утешительное сообщение, что все в порядке, даже если наш сайт взорвался. Вместо того, чтобы делать что-то умное, например регистрировать ошибку, мы отображаем ее
<h1>Bad Stuff Happend</h1><p>But that is okay</p><code>Array
(
[type] => 64
[message] => require() [<a href="function.require">function.require</a>]: Failed opening required 'FileNotFound.php' (include_path='.:/usr/local/lib/php')
[file] => /home/michael/www/eggplant/public_html/samples/fatal/index.php
[line] => 28
)
</code>
Если мы исправим отсутствующий необходимый файл, добавив следующий скрипт:
<?php
i'm an idiot
?>
… Мы можем отловить фатальную ошибку E_PARSE в нашем обработчике завершения работы:
<h1>Bad Stuff Happend</h1><p>But that is okay</p><code>Array
(
[type] => 4
[message] => syntax error, unexpected T_STRING
[file] => /home/michael/www/eggplant/public_html/samples/fatal/FileWithParseError.php
[line] => 2
)
</code>
Давайте исправим этот необходимый файл с помощью реального кода:
<?php
myBadTypo();
function myBadDay()
{
ini_set('memory_limit', '5000');
echo str_repeat('bad bad bad', 50000);
}
?>
… Другой поймал E_ERROR:
<h1>Bad Stuff Happend</h1><p>But that is okay</p><code>Array
(
[type] => 1
[message] => Call to undefined function myBadTypo()
[file] => /home/michael/www/eggplant/public_html/samples/fatal/FileThatBlowsMemLimits.php
[line] => 3
)
</code>
Наконец, допустим, мы отсортировали необходимый файл, но взорвали банк с доступной памятью:
<?php
myBadDay();
function myBadDay()
{
ini_set('memory_limit', '5000');
echo str_repeat('bad bad bad', 50000);
}
?>
… мы можем перехватить фатальную ошибку E_ERROR и в нашем обработчике завершения работы:
<h1>Bad Stuff Happend</h1><p>But that is okay</p><code>Array
(
[type] => 1
[message] => Allowed memory size of 262144 bytes exhausted (tried to allocate 550001 bytes)
[file] => /home/michael/www/eggplant/public_html/samples/fatal/FileThatBlowsMemLimits.php
[line] => 9
)
</code>
That’s all pretty good, however there are some limits that need to be taken into consideration:
- E_PARSE errors on the include that builds out the shutdown structure will not be caught, as the interpreter can’t get beyond the parse error to put it into place.
- E_CORE_ERROR errors? These are environmental errors caught when PHP itself is bootstrapping — pre-script interpretation. No dice.
- When using output buffering in combination with the ob_gzhandler, take care when cleaning the buffer. Chances are the
Content-Encoding: gzip
header was already set and you will need to follow suit. - If your output buffer has already been flushed before hitting a fatal error and the shutdown function, your content to that point is already gone. Check ob_get_status to see if that is the case.
For an similar implementation using a shutdown function, check out eZ Components’ Execution class.