Всем привет!
Процесс обработки ошибок в Yii был для меня не совсем прозрачным с первых дней использования этого фреймворка. Даже несмотря на наличие в документации специального раздела Error Handling. В каких случаях какие view используются, как влияет ajax или debug-режим, зачем нужен errorAction, в чем отличия при обработке исключений?
В итоге после копания в документации и исходном коде фреймворка я нарисовал наглядную схему обработки ошибок, которая лично для меня оказалась весьма полезной и наверняка пригодится кому-то еще.
Под катом собственно схема и некоторые комментарии к ней.
Итак, стандартно Yii обрабатывает только ошибки уровня warning и notice, а также uncaught exceptions. Для обработки фатальных ошибок нужно использовать register_shutdown_function (об этом в конце статьи).
При возникновении варнинга или непойманного исключения Yii будет их обрабатывать если установлены соответственно две константы:
И ошибки, и исключения обрабатываются в Yii по похожему сценарию. Но есть определенные различия, о которых я упомяну.

Прежде всего происходит запись в лог информации об ошибке или исключении — функция Yii::log().
Далее осуществляется вызов события onError (onException). Если есть пользовательскй обработчик на эти события, то он вызывается, выполняет необходимые действия и может остановить дальнейшую обработку события (установив event->handled = true). Повесить обработчик можно так:
или через метод attachEventHandler().
Если обработка события продолжается (event->handled = false), то в игру вступает стандартный компонент yii-приложения ErrorHandler (по умолчанию он всегда не null). Он вызывает соответствующий метод handleError() или handleException(). Внутри этих похожих методов есть важное различие между ошибкой и исключением (доступное в текущей версии Yii 1.1.9):
В зависимости от этого вызывается либо простейший html-вывод ошибки через app->displayError(), либо хитрая функция render() компонента ErrorHandler.
Раньше проверка на ajax была и в handleException(), но в 1.1.9 ее убрали, что позволяет более удобно обрабатывать исключения при ajax-запросах — например возвращать json.
Функцию ErrorHandler->render() я называю «хитрой», потому что она работает немного по другому чем обычный рендеринг у контроллера:
Поиск view происходит последовательно в:
1. themes/ThemeName/views/system
2. protected/views/system
3. framework/views
В случае установленного errorAction (например «site/error» в конфиге), этот метод, повторюсь, используется для пользовательского отображения ошибок и исключений в production режиме. В нем удобно сделать различный вывод для ajax и не-ajax запросов. Также отмечу, что здесь вывод может осуществляться в общий layout приложения через views/site/error.php (в отличие от views/system/errorXXX.php, которые содержат полный html-код страницы).
А что же с фатальными ошибками? Их перехват через register_shutdown_function на данный момент в Yii не реализован. Решение, которое я использую сейчас в проектах, такое:
1. При инициализации приложения зарегистрировать shutdown-обработчик
2. В обработчике проверить наличие ошибки и ее уровень через error_get_last(). Если ошибка уже обработана, то вернется NULL
3. Запустить весь механизм обработки через app->handleError(), т.е. замкнуть на начало схемы
При использовании error_get_last() важно учесть, что эта функция игнорирует директиву error_reporting и символ @ перед оператором, т.е. может содержать E_NOTICE, которые не видны и не обрабатываются (например вызов @session_start() при запущенных сессиях).
Поэтому в обработчике я проверяю только фатальные ошибки.
Также устанавливаю errorAction = null — тогда, как хорошо видно из схемы, будет показана системная вьюха error.php, что надежнее, чем запускать еще одно действие контроллера, где вероятность повторной ошибки выше:
На мой взгляд процесс обработки ошибок в Yii не очень прост, но зато обладает большой гибкостью. С удовольствием прислушаюсь к вашим комментариям и советам.
Благодарю за внимание!
UPD:
Выложил схему в формате PDF
Процесс обработки ошибок в Yii был для меня не совсем прозрачным с первых дней использования этого фреймворка. Даже несмотря на наличие в документации специального раздела Error Handling. В каких случаях какие view используются, как влияет ajax или debug-режим, зачем нужен errorAction, в чем отличия при обработке исключений?
В итоге после копания в документации и исходном коде фреймворка я нарисовал наглядную схему обработки ошибок, которая лично для меня оказалась весьма полезной и наверняка пригодится кому-то еще.
Под катом собственно схема и некоторые комментарии к ней.
0. Уровни ошибок
Итак, стандартно Yii обрабатывает только ошибки уровня warning и notice, а также uncaught exceptions. Для обработки фатальных ошибок нужно использовать register_shutdown_function (об этом в конце статьи).
1. Поехали!
При возникновении варнинга или непойманного исключения Yii будет их обрабатывать если установлены соответственно две константы:
YII_ENABLE_ERROR_HANDLER = true
YII_ENABLE_EXCEPTION_HANDLER = true
И ошибки, и исключения обрабатываются в Yii по похожему сценарию. Но есть определенные различия, о которых я упомяну.

2. Запись в лог
Прежде всего происходит запись в лог информации об ошибке или исключении — функция Yii::log().
3. Вызов события
Далее осуществляется вызов события onError (onException). Если есть пользовательскй обработчик на эти события, то он вызывается, выполняет необходимые действия и может остановить дальнейшую обработку события (установив event->handled = true). Повесить обработчик можно так:
$app->onError=function($event) { ... }
или через метод attachEventHandler().
4. Подключение ErrorHandler
Если обработка события продолжается (event->handled = false), то в игру вступает стандартный компонент yii-приложения ErrorHandler (по умолчанию он всегда не null). Он вызывает соответствующий метод handleError() или handleException(). Внутри этих похожих методов есть важное различие между ошибкой и исключением (доступное в текущей версии Yii 1.1.9):
- В handleError() проверяется тип запроса (ajax или нет) и режим приложения (константа YII_DEBUG)
- В handleException() проверяется тип исключения (CHttpException или CException) и режим приложения (константа YII_DEBUG)
В зависимости от этого вызывается либо простейший html-вывод ошибки через app->displayError(), либо хитрая функция render() компонента ErrorHandler.
Раньше проверка на ajax была и в handleException(), но в 1.1.9 ее убрали, что позволяет более удобно обрабатывать исключения при ajax-запросах — например возвращать json.
5. Хитрый рендеринг в ErrorHandler
Функцию ErrorHandler->render() я называю «хитрой», потому что она работает немного по другому чем обычный рендеринг у контроллера:
- При YII_DEBUG = true запускается с параметром «exception»:
Рендерит «exception.php» — отображает детальную отладочную информацию, причем и для ошибок, и для исключений - При YII_DEBUG = false а также для CHttpException запускается с параметром «error»:
а. Если установлен errorAction, то он выполняется и выводит пользовательское отображение ошибки
б. Если errorAction не установлен, то пытается вызвать view errorXXX.php, где ХХХ — http код исключения (для ошибок всегда 500)
в. Если view errorXXX.php не найден, то пытается вызвать универсальное error.php
Поиск view происходит последовательно в:
1. themes/ThemeName/views/system
2. protected/views/system
3. framework/views
6. Пользовательское отображение ошибки
В случае установленного errorAction (например «site/error» в конфиге), этот метод, повторюсь, используется для пользовательского отображения ошибок и исключений в production режиме. В нем удобно сделать различный вывод для ajax и не-ajax запросов. Также отмечу, что здесь вывод может осуществляться в общий layout приложения через views/site/error.php (в отличие от views/system/errorXXX.php, которые содержат полный html-код страницы).
Послесловие: обработка фатальных ошибок
А что же с фатальными ошибками? Их перехват через register_shutdown_function на данный момент в Yii не реализован. Решение, которое я использую сейчас в проектах, такое:
1. При инициализации приложения зарегистрировать shutdown-обработчик
2. В обработчике проверить наличие ошибки и ее уровень через error_get_last(). Если ошибка уже обработана, то вернется NULL
3. Запустить весь механизм обработки через app->handleError(), т.е. замкнуть на начало схемы
При использовании error_get_last() важно учесть, что эта функция игнорирует директиву error_reporting и символ @ перед оператором, т.е. может содержать E_NOTICE, которые не видны и не обрабатываются (например вызов @session_start() при запущенных сессиях).
Поэтому в обработчике я проверяю только фатальные ошибки.
Также устанавливаю errorAction = null — тогда, как хорошо видно из схемы, будет показана системная вьюха error.php, что надежнее, чем запускать еще одно действие контроллера, где вероятность повторной ошибки выше:
class WebApplication extends CWebApplication {
protected function init()
{
register_shutdown_function(array($this, 'onShutdownHandler'));
parent::init();
}
public function onShutdownHandler()
{
// 1. error_get_last() returns NULL if error handled via set_error_handler
// 2. error_get_last() returns error even if error_reporting level less then error
$e = error_get_last();
$errorsToHandle = E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING;
if(!is_null($e) && ($e['type'] & $errorsToHandle)) {
$msg = 'Fatal error: '.$e['message'];
// it's better to set errorAction = null to use system view "error.php" instead of run another controller/action (less possibility of additional errors)
yii::app()->errorHandler->errorAction = null;
// handling error
yii::app()->handleError($e['type'], $msg, $e['file'], $e['line']);
}
}
}
Заключение
На мой взгляд процесс обработки ошибок в Yii не очень прост, но зато обладает большой гибкостью. С удовольствием прислушаюсь к вашим комментариям и советам.
Благодарю за внимание!
UPD:
Выложил схему в формате PDF