ФорумПрограммированиеПыхнуть хотите?F.A.Q. → Обработка ошибок в PHP

Обработка ошибок в PHP

  • Timur

    Сообщения: 1068 Репутация: N Группа: Джедаи

    Spritz 19 июля 2009 г. 18:19, спустя 7 минут 45 секунд



    [size=14pt]Обработка ошибок в PHP[/size]

    PHP5 предлагает нам мощный и гибкий механизм исключительных ситуаций. Однако большая часть библиотек языка выполнена в процедурном стиле и обработка ошибок в них сделана соответственно.



    [size=14pt]Основное[/size]

    Ошибки в PHP делятся на несколько типов:

    • [tt]E_PARSE[/tt] — синтаксическая ошибка (незакрытая скобка, лишняя запятая, забытые точка с запятой и т.п.). Такие ошибки обычно (но всегда) обнаруживаются сразу, т.к. с ними PHP просто не сможет распарсить программу.

    • [tt]E_ERROR[/tt] — «фатальная» ошибка. Дальнейшее выполнение программы невозможно. Примеры: вызов несуществующей функции, неудачная попытка подключения файла через require, повторное объявление класса или функции, неперехваченное исключение.

    • [tt]E_WARNING[/tt] — предупреждение. Примеры – обращение к несуществующему файлу, передача в цикл [tt]foreach[/tt] переменной, не являющейся массивом.

    • [tt]E_NOTICE[/tt] — уведомление. Чаще всего вызвано использованием необъявленных переменных, обращению к несуществующим элементам массивов.

    • [tt]E_CORE_ERROR[/tt] — фатальная ошибка при запуске PHP. Аналогичен [tt]E_ERROR[/tt], но инициируется ядром.

    • [tt]E_CORE_WARNING[/tt] — предупреждение при запуске PHP.

    • [tt]E_COMPILE_ERROR[/tt] — ошибка при компиляции.

    • [tt]E_COMPILE_WARNING[/tt] — предупреждение при компиляции.

    • [tt]E_STRICT[/tt] (появился в PHP 5) — «советы» помогающие сделать ваш код более совместимым с будущими версиями PHP.

    • [tt]E_RECOVERABLE_ERROR[/tt] (появился в PHP 5.2) — «поправимая, но потенциально опасная ошибка». Возникает, например, при попытке преобразовать в строку объект, не имеющий метода [tt]__toString()[/tt], при передаче в функцию аргумента неверного типа (если применяется контроль типов).

    • [tt]E_DEPRECATED[/tt] (появился в PHP 5.3) — уведомления об использовании устаревших конструкций, несовместимых с будущими версиями PHP.


    Описанные выше типы сообщений инициируются самим PHP. Для установки своих сообщений используется функция [tt]trigger_error()[/tt].

    bool trigger_error ( string $error_msg [, int $error_type= E_USER_NOTICE ] )


    $error_msg — текст сообщения
    $error_type — тип ошибки

    Типом такого сообщения может быть

    • [tt]E_USER_ERROR[/tt],

    • [tt]E_USER_WARNING[/tt],

    • [tt]E_USER_NOTICE[/tt],

    • [tt]E_USER_DEPRECATED[/tt] (начиная с PHP 5.3).

    Поведение при этом аналогично E_ERROR, E_WARNING, E_NOTICE и E_USER_DEPRECATED соответственно.



    [size=14pt]Настройки вывода ошибок.[/size]

    Вывод ошибок определяется директивой [tt]display_errors[/tt]. Её значение можно изменять во время выполнения скрипта с помощью функции [tt]ini_set()[/tt]. Однако, на неустранимые ошибки (такие как [tt]E_PARSE[/tt]) эта директива не влияет.

    С помощью функции [tt]error_reporting()[/tt] можно установить, какие ошибки будут обрабатываться PHP.

    int error_reporting ([ int $level ] )


    Параметр $level определяет тип обрабатываемых ошибок. С помощью побитовых операторов можно комбинировать несколько типов:

    error_reporting(E_ERROR | E_WARNING | E_PARSE);


    — будут показаны [tt]E_ERROR[/tt], [tt]E_WARNING[/tt] и [tt]E_PARSE[/tt] ошибки.

    error_reporting(E_ALL ^ E_NOTICE);


    — будут показаны все ошибки, за исключением [tt]E_NOTICE[/tt].

    Константа [tt]Е_ALL[/tt] определяет сразу несколько типов ошибок. Её значение может быть различным в зависимости от версии PHP. Например, до PHP 6 в [tt]E_ALL[/tt] не входит [tt]E_STRICT[/tt]. Что бы быть уверенным, что будут обработаны все возможные ошибки, используйте:

    error_reporting(-1);




    [size=14pt]Установка собственных обработчиков ошибок.[/size]

    Стандартное поведение при возникновении ошибки можно изменить.

    mixed set_error_handler ( callback $error_handler [, int $error_types = E_ALL | E_STRICT ] )


    $error_handler определяет функцию, которая будет вызываться при возникновении ошибки.
    $error_types устанавливает типы обрабатываемых ошибок.

    Обработчик может принимать от двух до пяти аргументов:
       1. тип ошибки,
       2. текст сообщения,
       3. файл в котором произошла ошибка,
       4. строка в этом файле
       5. контекст — содержит символьную таблицу участка программы, где произошла ошибка.
    Если функция возвращает [tt]false[/tt], то запускается встроенный обработчик.

    Напишем свой простой обработчик:

    // Обрабатываются все возможные ошибки
    error_reporting(-1);

    // Функция-обработчик
    function myErrorHandler($type, $message, $file, $line)
    {
    static $titles = array(
    E_WARNING => 'Предупреждение',
    E_NOTICE => 'Уведомление',
    E_USER_ERROR => 'Ошибка, определенная пользователем',
    E_USER_WARNING => 'Предупреждение, определенное пользователем',
    E_USER_NOTICE => 'Уведомление, определенное пользователем',
    E_STRICT => 'Проблема совместимости в коде',
    E_RECOVERABLE_ERROR => 'Поправимая ошибка'
    );

    print '<h3>' . $titles[$type] . '</h3>'
    . '<p>' . $message . '<br />'
    . 'Источник: ' . basename($file) . ', line ' . $line . '</p>';
    return true;
    }

    // Назначаем обработчик
    set_error_handler('myErrorHandler');


    Теперь вызовем какую-нибудь ошибку:

    echo $X3;


    Получим сообщение:

    Уведомление
    Undefined variable: X3
    Источник: err.php, line 108


    При этом значение [tt]error_reporting[/tt] не учитывается — обрабатываются все возможные ошибки. Но в самом обработчике можно предусмотреть различное поведение в зависимости от этого значения. Так же прерывание работы скрипта следует предусмотреть самостоятельно (если нужно, конечно).

    Часто, даже зная файл и строку где произошла ошибка, не всегда ясно что могло её вызвать. В таких случаях полезно увидеть стек вызовов. Для его получения воспользуемся функцией [tt]debug_backtrace[/tt], которая возвращает массив с информацией о вызванных функциях. Эта информация представляет собой массив, который может следующие ключи:

    • function — название функции (или метода),

    • line — номер строки,

    • file — путь к файлу,

    • class — имя класса,

    • object — объект (PHP 5.1.1 и выше),

    • type — если был вызван метод объекта содержит «[tt]->[/tt]», если статический метод класса — «[tt]::[/tt]», если обычная функция — ничего.

    • args — массив аргументов функции.



    Добавим в обработчик вывод стека:


    $backtrace = debug_backtrace();
    array_shift($backtrace); // удалим вызов самого обработчика
    print 'Стек вызовов: <ol>';

    foreach ($backtrace as $call) {
    print '<li>';
    if (array_key_exists('file', $call)) {
    print basename($call['file']) . ', line '
    . $call['line'] . ': ';
    }

    if (array_key_exists('object', $call) &&
    method_exists($call['object'], '__toString')) {

    print $call['object'];
    }

    if (array_key_exists('type', $call)) {
    if ($call['type'] == '->') {
    print $call['class'] . '->';
    } elseif ($call['type'] == '::') {
    print $call['class'] . '::';
    }
    }

    print $call['function'] . '(';

    $strArgs = '';
    foreach ($call['args'] as $arg) {

    if (is_null($arg)) {
    $strArgs .= 'null';

    } elseif (is_bool($arg)) {
    $strArgs .= ($arg) ? 'true' : 'false';

    } elseif (is_string($arg)) {
    $strArgs .= '"' . $arg . '"';

    } elseif (is_integer($arg) || is_float($arg)) {
    $strArgs .= $arg;

    } elseif (is_array($arg)) {
    $strArgs .= 'array (' . sizeof($arg) . ')';

    } elseif (is_object($arg)) {
    $strArgs .= 'object (' . get_class($arg) . ')';

    } elseif (is_resource($arg)) {
    $strArgs .= 'resource (' . get_resource_type($arg) . ')';
    }

    $strArgs .= ', ';
    }
    $strArgs = substr($strArgs, 0, -2);

    print $strArgs . ')</li>';
    }

    print '</ol>';


    Вызовем ошибку:

    function callUserNotice()
    {
    trigger_error('Это уведомление', E_USER_NOTICE);
    }
    callUserNotice("Hello", 123, array());


    Получим сообщение:

    Уведомление, определенное пользователем
    Это уведомление
    Источник: err.php, line 132
    Стек вызовов:
    err.php, line 132: trigger_error("Это уведомление", 1024)
    err.php, line 150: callUserNotice("Hello", 123, array (0))


    К сожалению, ощибки [tt]E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING[/tt] и большая часть [tt]E_STRICT[/tt] не смогут быть обработаны этой функцией.

    Однако, можно воспользоваться функциями [tt]register_shutdown_function()[/tt] и [tt]error_get_last()[/tt]

    void register_shutdown_function ( callback $function )


    $function определяет имя функции, которая будет вызвана при завершении работы скрипта.

    array error_get_last ()


    Она возвращает информацию о последней ошибке.
    Напишем функцию:

    function myFatalCatcher()
    {
    $error = error_get_last();
    if ($error['type'] == E_ERROR ||
    $error['type'] == E_CORE_ERROR ||
    $error['type'] == E_COMPILE_ERROR ||
    $error['type'] == E_USER_ERROR) {

    myErrorHandler(
    $error['type'],
    $error['message'],
    $error['file'],
    $error['line']);
    }
    }

    register_shutdown_function('myFatalCatcher');


    Т.о. если программа была завершена из-за фатальной ошибки, будет вызван обработчик [tt]myErrorHandler()[/tt]. Что бы одновременно не производился стандартный вывод ошибок, следует отключить [tt]display_errors[/tt].  
    К сожаленью, перехватить ошибки [tt]E_PARSE[/tt] или получить стек вызовов при этом не удастся.

    Вызовем фатальную ошибку:

    function callError()
    {
    doSomething();
    }
    callError();


    В результате получим:

    Фатальная ошибка
    Call to undefined function doSomething()
    Источник: err.php, line 126
    Стек вызовов:
    myFatalCatcher()


    Полный код с примерами можно найти в приложении.


    [size=14pt]Альтернативы[/size]

    Более универсальный способ заключается в использовании функции [tt]ob_start[/tt]. В её параметре можно задать функцию, которая будет вызываться в любом случае, как бы не завершилась программа. В эту функцию передается все содержимое выходного потока, находившееся в буфере, в том числе и сообщение об ошибке. Данный способ подробно описан Д. Котеровым в 45-ой набле.


    [size=14pt]Что ещё покурить почитать?[/size]
    1. err.zip (83)
  • Sinkler

    Сообщения: 7958 Репутация: N Группа: в ухо

    Spritz 19 июля 2009 г. 18:06, спустя 23 часа 47 минут 53 секунды

    +1, спасибо за статью)))
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 19 июля 2009 г. 18:11, спустя 4 минуты 22 секунды

    +2, просплюсь - обязательно прочту!
    https://smappi.org/ - платформа по созданию API на все случаи жизни
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 19 июля 2009 г. 21:43, спустя 3 часа 32 минуты 28 секунд

    К сожалению, ощибки E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING и большая часть E_STRICT не смогут быть обработаны этой функцией.

    Однако, можно воспользоваться функциями register_shutdown_function() и error_get_last()


    Возможно ты не имел это в виду, но из текста кажется, что фатальные ошибки можно перехватить так.
    Однако! В указанной набле написано

    Кстати, назначенные по register_shutdown_function функции-финализаторы скрипта также не вызываются при наступлении фатальной ошибки.


    А в целом полезная для пыхи статья!
    ιιlllιlllι унц-унц
  • rider-sx

    Сообщения: 2706 Репутация: N Группа: Адекваты

    Spritz 19 июля 2009 г. 21:56, спустя 12 минут 38 секунд

    +1 Спасибо =) Даже и не думал что можно свой обработчик сделать )
  • Timur

    Сообщения: 1068 Репутация: N Группа: Джедаи

    Spritz 19 июля 2009 г. 22:40, спустя 43 минуты 56 секунд

    Однако! В указанной набле написано

    при E_PARSE — да, тут метод с ob_start(), видимо, единственный. Но E_ERROR, например, обрабатывается, в приложении есть примеры. Вообще хз, надо ещё поковыряться в этой теме

    сморозил чушь. E_PARSE вообще никак не перехватить (что, вообще, логино).
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 20 июля 2009 г. 11:40, спустя 13 часов 33 секунды

    Прочитал, все хорошо описано, спасибо Тимур!
    Спустя 88 сек.
    Скопируй статью в http://pyha.ru/articles/php/
    https://smappi.org/ - платформа по созданию API на все случаи жизни
  • Timur

    Сообщения: 1068 Репутация: N Группа: Джедаи

    Spritz 20 июля 2009 г. 15:42, спустя 4 часа 2 минуты

    сделал
  • adw0rd

    Сообщения: 22959 Репутация: N Группа: в ухо

    Spritz 20 июля 2009 г. 15:44, спустя 1 минуту 25 секунд

    молорик! )
    https://smappi.org/ - платформа по созданию API на все случаи жизни
  • Абырвалг

    Сообщения: 6480 Репутация: N Группа: Джедаи

    Spritz 18 марта 2010 г. 18:47, спустя 241 день 4 часа 3 минуты

    происходит Fatal Error (code = 1): Using $this when not in object context. Я ловлю это событие через register_shutdown_function и error_get_last, обрабатываю.
    Хочу вывести backtrace и очень сильно удивляюсь: backtrace ж очистился. В нем только мой callback, который в register_shutdown_function был отправлен.

    То есть Fatal Error стерла стек вызовов и начала вести его по новой. Как быть?
  • krasun

    Сообщения: 1370 Репутация: N Группа: Джедаи

    Spritz 19 марта 2010 г. 10:12, спустя 15 часов 25 минут 35 секунд

    код, где?
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 19 марта 2010 г. 10:20, спустя 7 минут 24 секунды

    То есть Fatal Error стерла стек вызовов и начала вести его по новой. Как быть?
    спешим - людей смешиш, уверен на 100% что это бред. дело в другом, да и нахуй юзать this когда ко не внутри класса? :)
    Сапожник без сапог
  • krasun

    Сообщения: 1370 Репутация: N Группа: Джедаи

    Spritz 19 марта 2010 г. 10:35, спустя 15 минут 28 секунд

    phpdude, я думаю та можеть быть, что-то более интересное. К примеру


    class Some1
    {
    public function setSome($var)
    {
    // тра-та-та
    return $this;
    }

    private function cucu()
    {
    }
    }

    $obj = new Some1();
    $obj->setSome(20)->cucu();


    или что-то такое
  • Абырвалг

    Сообщения: 6480 Репутация: N Группа: Джедаи

    Spritz 19 марта 2010 г. 10:59, спустя 23 минуты 22 секунды


    спешим - людей смешиш, уверен на 100% что это бред. дело в другом, да и нахуй юзать this когда ко не внутри класса? :)


    дело было вечером, делать было нечего. Вопрос не в том "нахуй юзать this в статик-методах" а в том "как задетектить это и нормально вывести бектроейс".
    Дуд, ты ж сам знаешь, что пых позволяет вызывать статически не статически объявленные методы. Будет сгенерирован нотайс, но нотайсы мы глушим же, так? =)

    class App
    {
    public function doSmth()
    {
    return $this->foo;
    }
    }
    App::doSmth() // <- вот тебе фатал эррор.

    в примере выше мы получим ошибку, что в классе таком-то на такой-то строке используется this в статик-методе. Но где мы сделали вызов App::doSmth() ? да хуй его знает, бектрейс недоступен, делай get_included_files и разбирай их.

    вот еще код http://www.php.ru/forum/viewtopic.php?p=206421#206421
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 19 марта 2010 г. 11:10, спустя 11 минут 34 секунды

    class App
    {
    public function doSmth()
    {
    return $this->foo;
    }
    }
    App::doSmth() // <- вот тебе фатал эррор.


    нахуй так делать? )
    Спустя 24 сек.
    юзай IDE, она не дает так ошибаться
    Сапожник без сапог

Пожалуйста, авторизуйтесь, чтобы написать комментарий!