Обработка ошибок в PHP и исключения
Итак. Работа над RedBox ведется полным ходом. Сегодня расскажу про обработку ошибок.
Этот момент важнее, чем может показаться на первый взгляд. Во-первых, чем больше информации об ошибке будет отображаться, тем меньше времени уйдет на ее поиск и устранение. Во-вторых, если уж и выводить максимум информации, то доступ к ней должен иметь только разработчик.
Почему? С одной стороны, пользователя, зашедшего на сайт не интересует по какой причине он не работает. Ошибка MySQL, или PHP, или другая? Ему по барабану. Он пришел за информацией, а ее нет. Важнее подсказать ему, что делать (“Загляните через пару минут”, “Воспользуйтесь зеркалом нашего сайта по адресу…”). С другой – ни к чему светить информацию, которую он видеть не должен (тексты запросов, document root и пр.).
Разделение вывода
Начнем со второй проблемы. Ее я решил очень просто: в настройках сайта, имеется специальная константа debug_mode. Если она == 1 – выводим всю инфу в браузер, в остальных случаях – показываем пользователю текст (“Упс, у нас ошибочка” или подобное). Еще у меня реализована отсылка сообщений на почту в случае возникновения некоторых ошибок.
Помимо этого, если сайт в режиме отладки, в публичной части выводится заметное сообщение об этом. Поэтому залить на хостинг и забыть выключить “отладку” не получится.
Ошибки или исключения?
До появления 5-ой версии PHP все ошибки приходилось обрабатывать разработчику. Каждый выходил из этой ситуации по-разному. Кто-то просто писал:
die('Ашыпка!!!');
Более продвинутые товарищи писали что-то наподобие этого:
echo 'Ашыпка!!!';
return false;
Самые умные –
trigger_error('Ашыпка!!!', E_USER_ERROR);
Но, согласитесь, это все костыли. В первом случае ошибку никак не обработать, во втором – нужно на каждом уровне вложенности стека вызова проверять что вернула функция. Третий вариант – наиболее близок к идеалу.
С появлением механизма исключений все сильно упростилось:
try {
// Код
if (/* какая-то проверка */) {
throw new Exception('Ашыпка!!!', 0)
}
}
catch (Exception $e) {
// обрабатываем
// например, просто выводим
echo $e // тут работает "магический" метод __toString класса Exception
}
Причем можно определять классы-потомки от Exception и строить обработку ошибок на основе этих классов:
class SeriosErrorException extends Exception;
class SomeTinyErrorException extends Exception;
try {
// Код
if (/* какая-то проверка */) {
throw new SeriosErrorException('Сурьезная ашыпка!!!', 0)
}
if (/* еще какая-то проверка */) {
throw new SomeTinyErrorException('Так себе ошибочка!!!', 0)
}
}
catch (SeriosErrorException $e) {
// обрабатываем
}
catch (SomeTinyErrorException $e) {
// обрабатываем
}
Т.е. первый блок catch ловит только ошибки класса SeriosErrorException, все остальные “спускаются ниже”.
Вывод пользовательских ошибок
Пару слов о том, как выводятся ошибки в RedBox CMS.
Для таких ошибок создан класс UserException примерно следующего содержания:
class UserException extends Exception {
private $message_array = array(
// коды ошибок
);
private $info;
public function __construct($code, $info = NULL) {
$this->info = $info;
parent::__construct($this->message_array[$code], $code);
}
public function getText() {
// Вывод ошибки в виде текста
}
public function getHtml() {
// Вывод ошибки в виде HTML
}
}
Например ошибка запроса к БД кидается следующим образом:
throw new UserException(1202, array('mysql' => $this->_query, 'text' => mysql_error()));
В режиме отладки это отображается так:
(для подсветки я использую GeSHi)
Вывод стандартных ошибок
По-умолчанию, PHP не очень разговорчив и выдает такие сообщения:
Warning: Wrong parameter count for strpos() in /usr/home/knave11t/ab.ru/WWW/inc/classes/News.class.php on line 52
Я предлагаю “переделывать” все ошибки в исключения с помощью функции set_error_handler. А дальше ловить эти исключения и обрабатывать их.
Для таких целей я использую класс, разработанный Дмитрием Котеровым.



ивзращенец :))
Ну-ка, ну-ка, покажи как ты их обрабатываешь. :)
А откуда стек вызова брать?
Нашел откуда брать, но как построить строку кода, как в твоем примере? Можно из массива — до некоторых пор я так и делал, но тогда неверно отображаются параметры:
test(string so long…);
Неплохо было бы чтобы строки заключались в кавычки…
test(’string so long…’);
Но это возможно только в том случае, если брать стек из getTraceAsStrng – но как его разбирать на части не пойму…
Вот как у меня выглядит функция вывода исключения:
public function getHtml() {
$html = '
';
$html .= '
' . $this->message . '
';
$html .= '
Строка ' . $this->line . ' файла ' . $this->getFile() . '
';
if (!empty($this->info) && is_array($this->info)) {
$html .= '
Дополнительная информация
';
$html .= '
';
foreach ($this->info as $lang => $text) {
$geshi = new GeSHi($text, $lang);
$geshi->set_header_type(GESHI_HEADER_DIV);
$geshi->enable_classes(true);
$geshi->set_overall_class('code-block');
$html .= '
';
}
$html .= '
';
}
$stack = $this->getTrace();
if (is_array($stack) && count($stack) > 0) {
$html .= '
Стек вызова
';
$html .= '
';- ';
foreach ($stack as $stackLine) {
$html .= '
$html .= 'Строка ' . $stackLine['line'] . ' файла ' . $stackLine['file'];
$code = '';
if(!empty($stackLine['class'])) {
$code .= $stackLine['class'];
}
if(!empty($stackLine['type'])) {
$code .= $stackLine['type'];
}
if(!empty($stackLine['function'])) {
$code .= $stackLine['function'];
}
$args = array();
foreach ($stackLine['args'] as $arg) {
if (is_string($arg))
$args[] = '"' . $arg . '"';
elseif (is_array($arg))
$args[] = print_r($arg, true);
elseif (is_object($arg))
$args[] = print_r($arg, true);
elseif (is_bool($arg))
$args[] = ($arg === true ? 'true' : 'false');
elseif (is_null($arg))
$args[] = 'null';
else
$args[] = $arg;
}
$code .= '(' . implode(', ', $args) . ');';
$geshi = new GeSHi($code, 'php');
$geshi->set_header_type(GESHI_HEADER_DIV);
$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
$geshi->set_tab_width(4);
$geshi->enable_classes(true);
$geshi->set_overall_class('code-block');
$geshi->start_line_numbers_at($stackLine['line']);
$html .= $geshi->parse_code();
$html .= '
';
}
$html .= '
';
}
return $html;
}