PHP — Как вернуть управляемый контент при критической ошибке PHP (E_ERROR, E_PARSE)?

Здравствуйте пытливые умы.
В данном вопросе я хотел бы продолжить популярную тему вопросов с обработками ошибок в PHP.

Условия.

1) Есть, код.
2) В одном из подключаемых файлах, допустим в одном из модулей закралась ошибка E_ERROR (допустим исключение или несовместимость кода под разные ОС, настройки серверов) или E_PARSE (случайно напортачил программист, и залил на сервер без теста, опаздывал к девушке например).
3) В случае рабочего сервера, где вывод ошибок отключен на соответствующей странице где ситуация случилось — выдает пустоту. Ниже приведу свои разработки на этот счет

Если вы заинтересовались условиями, то прошу под храбракат, тут еще интереснее.


Почему я привел 2 примера зная, что сущесвтуют set_exception_handler и set_error_handler?
Потому что в первый случай хотелось бы свести к унивесальному способу, а второй вариант не обрабатывает ошибки например E_PARSE, E_ERROR, он вообще тупо не запускает функцию перехвата. Хотелось убить 2 зайца одним разом.

Задача.

Отдать заголовки 404, в случае критических ошибок кода + некоторые данные, пускай даже статические вроде «ой, ошибка, сейчас мы это активно исправляем, заходите позже» (пояснения для пользователей).

Зачем?
Потому что поиковик должен на них напираться на 404, а не включать в индекс пустые страницы c 200 Ok + пользователю надо сообщить, что сейчас на сайте есть ошибка. В логи так или иначе это писаться будет, но не всегда в логи заглядываешь, если с виду все хорошо, ну и в меил тоже — мы же ушли к девушкам.

Решение с заголовками

<?php
header($_SERVER[«SERVER_PROTOCOL»].' '.'404'); // выставление заголовков перед генерацией контента

// Генерация контента, возможны ошибки работы кода, исключения, либо ошибки парса.
$data = generate();

header($_SERVER[«SERVER_PROTOCOL»].' '.'200'); // выставление правильных заголовков

// вывод данных к примеру, но лучше через буферизированный вывод
echo $data;
?>

Предположительное решение проблемы

  1. Мало, вот пытаюсь найти что-то вроде перехвата кодов ошибок на уровне Apache, но это жутко не универсально и привязывает к одному типу сервера. Более того я пока так и не нашел ни одного примера, как из PHP можно отправить Apache сообщение «Хьюстон у нас проблемы!», что бы тот загрузил дефолтную страницу ошибки.
  2. Через программный способ, то сейчас изучаю фунции буферизации вывода, может быть что дать сможет.
  3. Немного не представляю других способов, если конечно в PHP нет чего-нибуть этогкого, за это я возьмусь после первых 2-х пунктов.
  4. Есть вариант записать в вывод данные (как описано выше в Решение с заголовками), далее сбросить их и заново переписать с нуля с новыми заголовками — но это очень костыльно.

На все приведенное выше я не знаю решений, это только предположения.

Ссылки на материал на текущий момент

httpd.apache.org/docs/2.0/mod/core.html#errordocument
www.php.net/manual/ru/ref.outcontrol.php
php.net/manual/ru/function.set-error-handler.php
php.net/manual/ru/function.set-exception-handler.php
www.php.net/manual/ru/index.php

Господа, ваши предложения и главное решения с пруфлинками в студию

UPD:
Удивляюсь хомячкам, которые за редчайшие и тонкие вопросы минусуют карму, научитесь сами кодить для начала без костылей.

UPD2:
— В данном вопросе есть 2 ответа и все интересные, решение является наиболее элегантным, но второе решение тоже интересно.
— Тем кому опять-таки надо зарегистрировать метод класса прошу сюда, там я описал, как это делается.

UPD3:
В процессе разборов полетов, появилась дискуссия, которую советую прочитать для общего развития.
  • Вопрос задан
  • 7551 просмотр
Решения вопроса 1
Aco
@Aco
Заклинатель кода
register_shutdown_function(function () {
   $error = error_get_last();
   if ($error && ($error['type'] == E_ERROR || $error['type'] == E_PARSE || $error['type'] == E_COMPILE_ERROR)) {
       if (strpos($error['message'], 'Allowed memory size') === 0) { // если кончилась память
           ini_set('memory_limit', (intval(ini_get('memory_limit'))+64)."M"); // выделяем немножко что бы доработать корректно
           Log::error("PHP Fatal: not enough memory in ".$error['file'].":".$error['line']);
	} else {
           Log::error("PHP Fatal: ".$error['message']." in ".$error['file'].":".$error['line']);
        }
        // ... завершаемая корректно ....
    }
})



Ловит так же падения при отсутствии свободной памяти, ошибки парсинга, и прочего
Ответ написан
Пригласить эксперта
Ответы на вопрос 6
Что сразу попадает в глаз: надо 503 ошибку вместо 404.
Ответ написан
Подобную проблему я решал при помощи стандартного перехвата буфера вывода (ob_start...). Фокус в том, что обработчик буфера вызывается даже в случае фатальных ошибок (указанных вами), таким образом проверив в обработчике буфера, не произошла ли ошибка, мы уже можем слать нужные заголовки и т.д.

ob_start('ob_end');

...

function ob_end($outputData)
{
  $error = error_get_last();
      
  if (is_array($error) && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR)))
  {
    /* Фатальная ошибка, отдаем 500 */
  }

  return $outputData;
}
Ответ написан
@easterism
Ет все хорошо, но как-то забыли всем напомнить, что error_get_last() поддерживается, начиная с 5.2.0
Костыли для более ранних версий, это просто костыли.
Ответ написан
Комментировать
xaker1
@xaker1
Почему в Q&A?
Ответ написан
Комментировать
@galaxy
200 отдается не всегда: stackoverflow.com/a/8295606
Хотя Вашу задачу это не решит, т.к. заставить это работать в связке с ErrorDocument, видимо, нельзя
Ответ написан
png
@png
Коллеги.
Во-первых, согласен с тем, что говорилось выше. Это тоже правильно. Сам делал аналогичные вещи, перекрывал ошибки, писал в лог, слал на почту и т.п.

Во-вторых, если бы меня попросили сделать такую фичу в хорошем добротном проекте, то я бы не стал изобретать велосипед, а воспользовался решением из коробки.
Популярные фреймворки Symfony2, Yii — уже поддерживают это из коробки, причем достаточно давно.
Переполнение по памяти не проверял, остальное ловит без проблем и отдает 500 ошибку, то есть как надо(поправка выше, не обязательно отдавать 503-ю. можно любую 5хх, этот жест скажет поисковику — «Ой, спроси меня позже...», 404 — в таком случае лучше не отдавать, оно может повлиять на позиции сайта в поиске — если для вас это важно.).

Единственное ограничение к стилю кода, пишите без варингов и нотисов, они тоже ловятся. Иначе рискуете сделать проект, которые будет работать только в продакшен-режиме, то есть при выключенных ошибках. Отлаживать такие веши — не самое приятное занятие. К тому же, количество ошибок в таком коде выше на порядок.

В-третьих, на проблему нужно смотреть шире.
То, что код на php вываливается, когда случается ошибка синтаксического рода или нехватка памяти — это мега плохо. Решение — перекрывать функции обработки ошибок — ихмо, костыль, но иногда без него никуда.

Как сделать такой костыль — описано чуть ли не в самом вопросе. Для себя я суть дискуссии понял так — а как нужно строить логику приложения (архитектуру — если хотите), чтобы было правильно с точки зрения обработки ошибок.

Пример с ошибками и статусами HTTP — не единственный. Возможны и другие варианты.

Идея вот в чем. Что и как нужно делать — зависит от вашего проекта. Это может быть не сложный сайт, который отдает информацию, а может быть достаточно сложный комплекс, в котором заложена бизнес-логика, она может отрабатывать как при получении страниц, так и при выполнении фоновых заданий(например, скрипты в CronTab).

Если в двух словах, то я думаю так:
— разбить код логики на блоки (желательно независимые, они могут быть ирерархичными).
— запускать эти блоки как бы «в песочнице» (вопрос как — зависит от самого приложение, у кого-то может и не получиться, или придется ради этого менять структуру БД, какие-то алгоритмы внутри приложения и т.п.)
— по успешному выполнению блока, применяем результат. Что-то сломалось, уведомляем админа о критичной ситуации.

Критичные ситуации лучше всего ловить Unit-тестами, а если это не возможно, то всеми видами тестирования.

Вот, как-то так.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы