Pull to refresh

Обработка критических ошибок в PHP

Reading time 6 min
Views 32K
В статье описан функционал, который доступен в PHP (актуально для 5.3.х) для обработки ошибок всех типов, включая ошибки интерпретации кода (E_ERROR, E_PARSE, E_WARNING, etc). Эта обработка поможет вам для управляемого отображения страницы в случае возникновения таких проблем. В статье присутствует множество описаний и рабочих примеров(архитектуры) для того, что бы сразу воспользоваться в своем программном продукте. В конце концов, ну немного сломали сайт, ну надо же, об этом сообщить поисковику с заголовком 4хх или 5хх и повеселить пользователя, вместо возврата белого экрана (или что хуже экрана со священной информацией, для хакеров) с ответом 200 Ok.



Идея написать этот топик возникла, когда я на храбре задал 2 вопроса:

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

Если заинтересовались, то подробности под катом…


Причины использования


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

Описания функций


Данный функционал доступен в PHP для того, что бы обрабатывать ошибки и контролировать вывод. Вот описание их вкусностей и недостатков. Документацию я приводить не буду, я только сошлюсь её страницы и опишу свое мнение. Все что будет приведено это только малая доля, ссылки на соответствующие разделы документации я приведу в конце статьи. Итак встречаем:

— Контроль некритических ошибок: замечания, предупреждения, пользовательские ошибки. В общем все то, что не завершает интерпретацию аварийно.
set_error_handler — Задает определенный пользователем обработчик ошибок.
Нужен для того, что бы писать в лог все такие ошибки. Если её не задать, то в лог это не пишется, а мне вот всегда хочется узнать при каких боевых ситуациях могут вызываться замечания и предупреждения. То есть позволяет автоматически тестировать продукт пользователем и он даже не будет замечать этого.
Если функция не задана, то PHP лишь пытается вывести данные на экран, а если ему и это не дают, то вообще никаких признаков жизни от этих типов ошибок не возникает.

— Контроль, исключений: является ошибкой типа E_ERROR.
set_exception_handler — Задает пользовательский обработчик исключений
Ну не знаю, зачем это вообще было придумано, когда есть то, что описано ниже и просто обработка ошибки типа Exception. Так что сообщаю что оно просто существует. Она перехватывает критическую ошибку «исключение» и позволяет что-то с ней делать. В любом случае скрипт завершается. Её работы по умолчанию лично для меня достаточно (пишет в логи, пытается вывести на экран). Я бы её вообще не переопределял, а то придется в логи о случившимся исключении самому писать.

— Функции контроля вывода: Тут я опишу 3 функции, которые следует знать по разным причинам. Например, для проблем производительности или для проблем вывода заголовков. В нашем случае требуется выводить заголовки ошибок.

ob_start — Включение буферизации вывода
ob_flush — Сброс (отправка) буфера вывода
ob_end_clean — Очищает (стирает) буфер вывода и отключает буферизацию вывода

Если вкратце, то данные функции позволяют записывать все данные выводимые через echo в некий буфер. Это функционал позволяют отправлять заголовки в любой части кода, а так же многие другие вещи, темы которых не относится к данной статье. А если вдруг случилась беда. То можно сбрасывать весь буфер и весь стек (об этом не в этой статье), писать заголовок ошибки и выводить уведомление пользователю.

— Получение последней произошедшей ошибки: её надо использовать вместе с другими перехватывающими функциями, которые тут описаны.
error_get_last — Получение информации о последней произошедшей ошибке
С помощью нее, возможно вернуть последнюю ошибку. Ей очень удобно ловить критические ошибки и код становится оптимальным (появилась с 5.2.х если что).

— Функция которая запускается после того как все отработало: Барабанная дробь.
register_shutdown_function — Регистрирует функцию, которая выполнится по завершении работы скрипта. Завершение может быть штатным или аварийным, без разницы.
Много плюсов и никаких минусов:
  • Данная функция не переопределяется, а определяется дополнительно, и определить вы её можете много раз
  • Так как она не переопределяется, то все ошибки уже пишутся в лог так или иначе, их не требуется переопределять
  • Данная функция, не еще отправляет контент, о чем написано в документации, и соотвественно можно пользоваться функциями буферизации

Многие Объектно Ориентированные люди обрадуются


Что все вышеизложенные функции можно зарегистрировать даже на методы классов, а также, наверняка, на статические методы классов по той же схеме. Правда способ очень не очевиден для глаза обычного не PHP программиста.
Параметр handler требуется задать через массив, с элементами «название класса|объекта», «метод объекта». Устанавливаемый метод обязательно должен быть public. Пример для функции set_error_handler:
<?php
class BaseErrorCatcher
{
	public function __construct()
	{
		set_error_handler(array($this, 'ErrorCatcher'));
		
		echo "друг, я создался\n";
		
		$errorVarArray['real index'] = true;
		echo $errorVarArray['error index'];
	}
	
	public function ErrorCatcher($errno, $errstr)
	{
		echo "друг, да ты вероянто напортачил где-то в этом: $errstr\n";
	}
}

error_reporting(E_ALL | E_STRICT);	// включаем замечания у кого они выключены
ini_set('display_errors','On');		// ну уж что бы точно вы увидели результат
new BaseErrorCatcher();			// начало теста
?>

Результат:
друг, я создался
друг, да ты вероянто напортачил где-то в этом: Undefined index: error index


Рабочий пример


Пример работы, архитектура класса для универсального и полного контроля ошибок. Я исследовал этот вопрос очень досконально и составил гибридный метод, из ответов на заданные мною вопросы.

Условия

Есть файл с кодом, который запускается первым или перед кодом в котоом может появиться ошибка и этот файл и все файлы до него 100% отлаженные с невозможностью появления ошибки. Вот такое вот условие, что бы было проще — без ошибок до того пока не пройдут все регистрации вышеизложенных функций. В данном файле описаны данные методики контроля ошибок в комплексе. Контролируется буфер, если ошибка, то сбросить буфер и вывести ошибку.

Код с комментариями

От себя добавлю, что код не тестировал, так как это упрощенная схема того, что у меня в коде, замечания принимаются
<?php

class ErrorSupervisor
{
	public function __construct()
	{
		// регистрация ошибок
		set_error_handler(array($this, 'OtherErrorCatcher'));
		
		// перехват критических ошибок
		register_shutdown_function(array($this, 'FatalErrorCatcher'));
		
		// создание буфера вывода
		ob_start();
	}
	
	public function OtherErrorCatcher($errno, $errstr)
	{
		// контроль ошибок:
		// - записать в лог
		return false;
	}
	
	public function FatalErrorCatcher()
	{
		$error = error_get_last();
		if (isset($error))
			if($error['type'] == E_ERROR
				|| $error['type'] == E_PARSE
				|| $error['type'] == E_COMPILE_ERROR
				|| $error['type'] == E_CORE_ERROR)
			{
				ob_end_clean();	// сбросить буфер, завершить работу буфера
			
				// контроль критических ошибок:
				// - записать в лог
				// - вернуть заголовок 500
				// - вернуть после заголовка данные для пользователя
			}
			else
			{
				ob_end_flush();	// вывод буфера, завершить работу буфера
			}
		else
		{
			ob_end_flush();	// вывод буфера, завершить работу буфера
		}
	}
}

// запуск контроллера
$errorController = new ErrorSupervisor();

// генерируем контент
// изменяем заголовки, в обещм делаем много чего
echo "генерация простейшего контента";

// тестируем систему (не тестировал, но у меня в более сложном варианте работает)
include 'null'; // запускается OtherErrorCatcher
// require 'null'; // запустится FatalErrorCatcher
// require 'foobar.php'; // а внутри этого файла вообще ошибка компиляции
?>


Ссылки


Разделы документации



Другая полезная информация



Спасибо за внимание.

UPD: По советам из комментариев, дополнил класс ErrorSupervisor новой функциональностью, исправил пару заблуждений, добавил дополнительную интересную информацию по теме, немного отладил код

UPD2 Внимание: Товарищ по PHP-разуму написал хорошую статью про битовые операции в PHP как раз к теме данной статьи, советую почитать. Эти знания позволяют более элегантно писать код. Менять текст данной статьи уже не стал для того, что бы сохранился смысл.
Tags:
Hubs:
+94
Comments 57
Comments Comments 57

Articles