Pull to refresh

Схема обработки ошибок в Yii

Reading time 4 min
Views 34K
Всем привет!
Процесс обработки ошибок в 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
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+43
Comments 22
Comments Comments 22

Articles