13 января 2012 в 12:54

Схема обработки ошибок в Yii из песочницы

Yii*
Всем привет!
Процесс обработки ошибок в 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 по похожему сценарию. Но есть определенные различия, о которых я упомяну.
image

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
+43
12923
294
vitalets 20,0

Комментарии (22)

–3
Mobyman, #
Ой спасибо, я как раз диплом на Yii планирую писать, а пока его совсем не знаю. Этот материал будет мне полезен.
0
Ekstazi, #
Тоже ковырялся в нем недавно, рендеринг и впрямь там хитрый.
+2
Fesor, #
Отчего же хитрый то?
0
Ekstazi, #
Была задача отследить как работает обработчик ошибок в Yii и немного видоизменить его(перехватить рендеринг вьюхи выводимой им). Сперва я полез в код, но это дало мало результатов. Пришлось отладчик юзать. В итоге получил то что требовалось, но в недрах обработчика ошибок я наковырялся.
0
vitalets, #
хитрый оттого, что при вызове render(«error») я ожидаю рендеринг вьюхи error, но никак не запуск другого контроллера/действия.
Раз уж дошло до рендеринга, то никаких существенных изменений в логике быть не должно.
+4
JiLiZART, #
ух ты, уже дипломы на Yii пишут ;)
–4
vitalets, #
может курс Yii включат в обязательную программу?)
–1
Mobyman, #
А почему бы и нет, если webdev нравится? :)
–1
gouranga, #
я помогал приятелю писать «Электронную библиотеку» на Yii еще в 2008—2009… вот были времена… :)
0
Ekstazi, #
Я дипломную по yii 2 года назад защищал
0
ataraev, #
Отличная статья, все собрано в одном месте, а то мне пришлось целое расследование проводить что бы это все найти.
0
zim32, #
Спасибо. Диаграмки в Dia рисовали?
0
vitalets, #
Нет, visio 2010.
Про Dia не слышал, но теперь знаю :)
0
Cage, #
Пожалуй распечатаю схему, повешу рядом
+1
vitalets, #
может ее в pdf выложить?
а то тут качество не очень.
0
Cage, #
было бы здорово
0
vitalets, #
Добавил PDF-версию схемы
0
Cage, #
Спасибо
–9
SowingSadness, #
Как качество рисунка изменится от того, что его выложат в pdf?
Бред написали.
0
vitalets, #
Имеется ввиду экспорт из visio в pdf вместо png. Субъективно выглядит лучше.
Выложу вечером, на работе все закрыто(
0
SamDark, #
Можно схемку перезалить? iFolder, кажется, накернился.
0
vitalets, #
Вообще у меня работает.
Перезалил на народ:
narod.ru/disk/38515463001/yii%20errors.pdf.html

Только зарегистрированные пользователи могут оставлять комментарии.
Войдите, пожалуйста.