Php warning как исправить

I have some PHP code. When I run it, a warning message appears.

How can I remove/suppress/ignore these warning messages?

Banee Ishaque K's user avatar

asked Jan 1, 2010 at 0:32

Alireza's user avatar

1

You really should fix whatever’s causing the warning, but you can control visibility of errors with error_reporting(). To skip warning messages, you could use something like:

error_reporting(E_ERROR | E_PARSE);

Sean Bright's user avatar

Sean Bright

118k17 gold badges138 silver badges145 bronze badges

answered Jan 1, 2010 at 0:37

Tatu Ulmanen's user avatar

Tatu UlmanenTatu Ulmanen

123k34 gold badges186 silver badges184 bronze badges

4

You can put an @ in front of your function call to suppress all error messages.

@yourFunctionHere();

Mark Amery's user avatar

Mark Amery

140k78 gold badges402 silver badges457 bronze badges

answered Jan 1, 2010 at 0:41

PetPaulsen's user avatar

PetPaulsenPetPaulsen

3,3822 gold badges22 silver badges33 bronze badges

11

To suppress warnings while leaving all other error reporting enabled:

error_reporting(E_ALL ^ E_WARNING); 

Mark Amery's user avatar

Mark Amery

140k78 gold badges402 silver badges457 bronze badges

answered Feb 11, 2011 at 8:08

Karthik's user avatar

KarthikKarthik

1,4183 gold badges17 silver badges29 bronze badges

If you don’t want to show warnings as well as errors use

// Turn off all error reporting
error_reporting(0);

Error Reporting – PHP Manual

MD XF's user avatar

MD XF

7,8027 gold badges40 silver badges71 bronze badges

answered Jan 22, 2013 at 3:16

mohan.gade's user avatar

mohan.gademohan.gade

1,0851 gold badge9 silver badges15 bronze badges

0

If you want to suppress the warnings and some other error types (for example, notices) while displaying all other errors, you can do:

error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);

answered Jan 10, 2018 at 17:13

zstate's user avatar

zstatezstate

1,9851 gold badge18 silver badges20 bronze badges

in Core Php to hide warning message set error_reporting(0) at top of common include file or individual file.

In WordPress hide Warnings and Notices add following code in wp-config.php file

ini_set('log_errors','On');
ini_set('display_errors','Off');
ini_set('error_reporting', E_ALL );
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

answered May 12, 2017 at 5:04

Vijay Lathiya's user avatar

1

I do it as follows in my php.ini:

error_reporting = E_ALL & ~E_WARNING  & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED

This logs only fatal errors and no warnings.

honk's user avatar

honk

8,98711 gold badges74 silver badges83 bronze badges

answered Feb 27, 2018 at 8:43

navid's user avatar

navidnavid

9428 silver badges19 bronze badges

0

Not exactly answering the question, but I think this is a better compromise in some situations:

I had a warning message as a result of a printf() statement in a third-party library. I knew exactly what the cause was – a temporary work-around while the third-party fixed their code. I agree that warnings should not be suppressed, but I could not demonstrate my work to a client with the warning message popping up on screen. My solution:

printf('<div style="display:none">');
    ...Third-party stuff here...
printf('</div>');

Warning was still in page source as a reminder to me, but invisible to the client.

FelixSFD's user avatar

FelixSFD

5,97210 gold badges43 silver badges115 bronze badges

answered Dec 30, 2012 at 20:03

DaveWalley's user avatar

DaveWalleyDaveWalley

80710 silver badges22 bronze badges

4

I think that better solution is configuration of .htaccess In that way you dont have to alter code of application. Here are directives for Apache2

php_flag display_startup_errors off
php_flag display_errors off
php_flag html_errors off
php_value docref_root 0
php_value docref_ext 0

answered May 10, 2014 at 16:34

Sebastian Piskorski's user avatar

You could suppress the warning using error_reporting but the much better way is to fix your script in the first place.

Dharman's user avatar

Dharman

30.3k22 gold badges84 silver badges132 bronze badges

answered Jan 1, 2010 at 0:34

Pekka's user avatar

PekkaPekka

440k141 gold badges972 silver badges1085 bronze badges

1

There is already answer with Error Control Operator but it lacks of explanation. You can use @ operator with every expression and it hides errors (except of Fatal Errors).

@$test['test']; //PHP Notice:  Undefined variable: test

@(14/0); // PHP Warning:  Division by zero

//This is not working. You can't hide Fatal Errors this way.
@customFuntion(); // PHP Fatal error:  Uncaught Error: Call to undefined function customFuntion()

For debugging it’s fast and perfect method. But you should never ever use it on production nor permanent include in your local version. It will give you a lot of unnecessary irritation.

You should consider instead:

1. Error reporting settings as mentioned in accepted answer.

error_reporting(E_ERROR | E_PARSE);

or from PHP INI settings

ini_set('display_errors','Off');

2. Catching exceptions

try {
    $var->method();
} catch (Error $e) {
    // Handle error
    echo $e->getMessage();
}

answered May 24, 2020 at 3:28

Jsowa's user avatar

JsowaJsowa

8,6845 gold badges50 silver badges59 bronze badges

Антон Шевчук // Web-разработчик

Не совершает ошибок только тот, кто ничего не делает, и мы тому пример – трудимся не покладая рук над созданием рабочих мест для тестировщиков 🙂

О да, в этой статье я поведу свой рассказа об ошибках в PHP, и том как их обуздать.

Ошибки

Разновидности в семействе ошибок

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

Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

Фатальные ошибки

Самый грозный вид ошибок – фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.

E_PARSE
Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:

<?php
/**
 Parse error: syntax error, unexpected end of file
 */
{

Или написали на непонятном языке:

<?php
/**
 Parse error: syntax error, unexpected '...' (T_STRING)
 */
Тут будет ошибка парсера

Лишние скобочки тоже встречаются, и не важно круглые либо фигурные:

<?php
/**
 Parse error: syntax error, unexpected '}'
 */
}

Отмечу один важный момент – код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:

<?php
// этот код не сработает
error_reporting(E_ALL);
ini_set('display_errors', 1);

// т.к. вот тут
ошибка парсера

E_ERROR
Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин, так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:

Не был найден подключаемый файл:

/**
 Fatal error: require_once(): Failed opening required 'not-exists.php' (include_path='.:/usr/share/php:/usr/share/pear')
 */
require_once 'not-exists.php';

Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:

/**
 Fatal error: Uncaught exception 'Exception'
 */
throw new Exception();

При попытке вызвать несуществующий метод класса:

/**
 Fatal error: Call to undefined method stdClass::notExists()
 */
$stdClass = new stdClass();
$stdClass->notExists();

Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:

/**
 Fatal Error: Allowed Memory Size
 */
$arr = array();

while (true) {
    $arr[] = str_pad(' ', 1024);
}

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

Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug:

/**
 Fatal error: Maximum function nesting level of '256' reached, aborting!
 */
function deep() {
    deep();
}
deep();

Не фатальные

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

E_WARNING
Частенько встречается, когда подключаешь файл с использованием include, а его не оказывается на сервере или ошиблись указывая путь к файлу:

/**
 Warning: include_once(): Failed opening 'not-exists.php' for inclusion
 */
include_once 'not-exists.php';

Бывает, если используешь неправильный тип аргументов при вызове функций:

/**
 Warning: join(): Invalid arguments passed
 */
join('string', 'string');

Их очень много, и перечислять все не имеет смысла…

E_NOTICE
Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.

Когда обращаются к неопределенной переменной:

/**
 Notice: Undefined variable: a
 */
echo $a;

Когда обращаются к несуществующему элементу массива:

<?php
/**
 Notice: Undefined index: a
 */
$b = array();
$b['a'];

Когда обращаются к несуществующей константе:

/**
 Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT'
 */
echo UNKNOWN_CONSTANT;

Когда не конвертируют типы данных:

/**
 Notice: Array to string conversion
 */
echo array();

Для избежания подобных ошибок – будьте внимательней, и если вам IDE подсказывает о чём-то – не игнорируйте её:

PHP E_NOTICE in PHPStorm

E_STRICT
Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывают. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this:

/**
 Strict standards: Non-static method Strict::test() should not be called statically
 */
class Strict { 
    public function test() { 
        echo 'Test'; 
    } 
}

Strict::test();

E_DEPRECATED
Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):

/**
 Deprecated: Function split() is deprecated
 */

// популярная функция, всё никак не удалят из PHP
// deprecated since 5.3
split(',', 'a,b');

В моём редакторе подобные функции будут зачёркнуты:

PHP E_DEPRECATED in PHPStorm

Обрабатываемые

Этот вид, которые разводит сам разработчик кода, я их уже давно не встречал, не рекомендую их вам заводить:

  • E_USER_ERROR – критическая ошибка
  • E_USER_WARNING – не критическая ошибка
  • E_USER_NOTICE – сообщения которые не являются ошибками

Отдельно стоит отметить E_USER_DEPRECATED – этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():

/**
 * @deprecated Deprecated since version 1.2, to be removed in 2.0
 */
function generateToken() {
    trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
    // ...
    // code ...
    // ...
}

Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы display_errors:

  • если display_errors = on, то в случае ошибки браузер получит html c текстом ошибки и кодом 200
  • если же display_errors = off, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок – код будет работать неправильно, но никому об этом не расскажет

Приручение

Для работы с ошибками в PHP существует 3 функции:

  • set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
  • error_get_last() — получает информацию о последней ошибке
  • register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого

Теперь немного подробностей об обработке ошибок с использованием set_error_handler(), в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:

  • $errno – первый аргумент содержит тип ошибки в виде целого числа
  • $errstr – второй аргумент содержит сообщение об ошибке
  • $errfile – необязательный третий аргумент содержит имя файла, в котором произошла ошибка
  • $errline – необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка
  • $errcontext – необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка

В случае если обработчик вернул true, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:

<?php
    // включаем отображение всех ошибок, кроме E_NOTICE
    error_reporting(E_ALL & ~E_NOTICE);
    ini_set('display_errors', 1);
    
    // наш обработчик ошибок
    function myHandler($level, $message, $file, $line, $context) {
        // в зависимости от типа ошибки формируем заголовок сообщения
        switch ($level) {
            case E_WARNING:
                $type = 'Warning';
                break;
            case E_NOTICE:
                $type = 'Notice';
                break;
            default;
                // это не E_WARNING и не E_NOTICE
                // значит мы прекращаем обработку ошибки
                // далее обработка ложится на сам PHP
                return false;
        }
        // выводим текст ошибки
        echo "<h2>$type: $message</h2>";
        echo "<p><strong>File</strong>: $file:$line</p>";
        echo "<p><strong>Context</strong>: $". join(', $', array_keys($context))."</p>";
        // сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется
        return true;
    }
    
    // регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок
    set_error_handler('myHandler', E_ALL);

У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет – пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём

С обработчиком, который написан выше есть одна существенная проблема – он не ловит фатальные ошибки, и вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:

function shutdown() {
    echo 'Этот текст будет всегда отображаться';
}
register_shutdown_function('shutdown');

Данная функция будет срабатывать всегда!

Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль “последних”:

function shutdown() {
    $error = error_get_last();
    if (
        // если в коде была допущена ошибка
        is_array($error) &&
        // и это одна из фатальных ошибок
        in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])
    ) {
        // очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях)
        while (ob_get_level()) {
            ob_end_clean();
        }
        // выводим описание проблемы
        echo 'Сервер находится на техническом обслуживании, зайдите позже';
    }
}
register_shutdown_function('shutdown');

Задание
Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.

О прожорливости

Проведём простой тест, и выясним – сколько драгоценных ресурсов кушает самая тривиальная ошибка:

/**
 * Этот код не вызывает ошибок
 */

// сохраняем параметры памяти и времени выполнения скрипта
$memory = memory_get_usage();
$time= microtime(true);

$a = '';
$arr = [];
for ($i = 0; $i < 10000; $i++) {
    $arr[$a] = $i;
}

printf('%f seconds <br/>', microtime(true) - $time);
echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';

В результате запуска данного скрипта у меня получился вот такой результат:

0.002867 seconds 
984 bytes

Теперь добавим ошибку в цикле:

/**
 * Этот код содержит ошибку
 */

// сохраняем параметры памяти и времени выполнения скрипта
$memory = memory_get_usage();
$time= microtime(true);

$a = '';
$arr = [];
for ($i = 0; $i < 10000; $i++) {
    $arr[$b] = $i; // тут ошиблись с именем переменной
}

printf('%f seconds <br/>', microtime(true) - $time);
echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';

Результат ожидаемо хуже, и на порядок (даже на два порядка!):

0.263645 seconds 
992 bytes

Вывод однозначен – ошибки в коде приводят к лишней прожорливости скриптов – так что во время разработки и тестирования приложения включайте отображение всех ошибок!

Тестирование проводил на PHP версии 5.6, в седьмой версии результат лучше – 0.0004 секунды против 0.0050 – разница только на один порядок, но в любом случае результат стоит прикладываемых усилий по исправлению ошибок

Где собака зарыта

В PHP есть спец символ «@» – оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:

<?php
    echo @UNKNOWN_CONSTANT;

При этом обработчик ошибок указанный в set_error_handler() всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting() внутри обработчика, в этом случае она вернёт 0.

Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло

Исключения

В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Думаю да, ведь это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений

Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.

К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение – сохранить в другое место или сообщить пользователю о проблеме.

Исключение – это объект который наследуется от класса Exception, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, “бросить”) при помощи оператора throw, и можно перехватить (“поймать”) оператором catch. Код генерирующий исключение, должен быть окружен блоком try, для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally:

try {
    // код который может выбросить исключение
    if (rand(0, 1)) {
        throw new Exception('One')
    } else {
        echo 'Zero';
    }
} catch (Exception $e) {
    // код который может обработать исключение
    echo $e->getMessage();
}

В каких случаях стоит применять исключения:

  • если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
  • если используемый вами фреймверк или библиотека декларируют их использование

Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл – помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:

$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';

// директории может не быть
if (!is_dir($directory)) {
    throw new Exception('Directory `logs` is not exists');
}

// может не быть прав на запись в директорию
if (!is_writable($directory)) {
    throw new Exception('Directory `logs` is not writable');
}

// возможно кто-то уже создал файл, и закрыл к нему доступ
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
    throw new Exception('System can't create log file');
}

fputs($file, date('[H:i:s]') . " donen");
fclose($file);

Соответственно ловить данные исключения будем примерно так:

try {
    // код который пишет в файл
    // ...
} catch (Exception $e) {
    // выводим текст ошибки
    echo 'Не получилось: '. $e->getMessage();
}

В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую – различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:

// исключения файловой системы
class FileSystemException extends Exception {}

// исключения связанные с директориями
class DirectoryException extends FileSystemException {
    // коды исключений
    const DIRECTORY_NOT_EXISTS =  1;
    const DIRECTORY_NOT_WRITABLE = 2;
}

// исключения связанные с файлами
class FileException extends FileSystemException {}

Теперь, если использовать эти исключения то можно получить следующий код:

try {
    // код который пишет в файл
    if (!is_dir($directory)) {
        throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS);
    }

    if (!is_writable($directory)) {
        throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE);
    }

    if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
        throw new FileException('System can't open log file');
    }

    fputs($file, date('[H:i:s]'') . " donen");
    fclose($file);
} catch (DirectoryException $e) {
    echo 'С директорией возникла проблема: '. $e->getMessage();
} catch (FileException $e) {
    echo 'С файлом возникла проблема: '. $e->getMessage();
} catch (FileSystemException $e) {
    echo 'Ошибка файловой системы: '. $e->getMessage();
} catch (Exception $e) {
    echo 'Ошибка сервера: '. $e->getMessage();
}

Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.

Так, а что будет если не поймать исключение? Вы получите “Fatal Error: Uncaught exception …”. Неприятно.
Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:

// в качестве обработчика событий 
// будем использовать анонимную функцию
set_exception_handler(function($exception) {
    /** @var Exception $exception */
    echo $exception->getMessage(), "<br/>n";
    echo $exception->getFile(), ':', $exception->getLine(), "<br/>n";
    echo $exception->getTraceAsString(), "<br/>n";
});

Ещё расскажу про конструкцию с использованием блока finally – этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:

try {
    // код который может выбросить исключение
} catch (Exception $e) {
    // код который может обработать исключение
    // если конечно оно появится
} finally {
    // код, который будет выполнен при любом раскладе
}

Для понимания того, что это нам даёт приведу следующий пример использования блока finally:

try {
    // где-то глубоко внутри кода
    // соединение с базой данных
    $handler = mysqli_connect('localhost', 'root', '', 'test');

    try {
        // при работе с БД возникла исключительная ситуация
        // ...
        throw new Exception('DB error');
    } catch (Exception $e) {
        // исключение поймали, обработали на своём уровне
        // и должны его пробросить вверх, для дальнейшей обработки
        throw new Exception('Catch exception', 0, $e);
    } finally {
        // но, соединение с БД необходимо закрыть
        // будем делать это в блоке finally
        mysqli_close($handler);
    }

    // этот код не будет выполнен, если произойдёт исключение в коде выше
    echo "Ok";
} catch (Exception $e) {
    // ловим исключение, и выводим текст
    echo $e->getMessage();
    echo "<br/>";
    // выводим информацию о первоначальном исключении
    echo $e->getPrevious()->getMessage();
}

Т.е. запомните – блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).

Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код 😉

Задание
Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира – посмотрите как это круто выглядит у whoops.

PHP7 – всё не так, как было раньше

Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы столкнётесь через год работы PHP разработчиком. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот – в PHP7 это решили исправить, но как обычно завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:

  1. при возникновении фатальных ошибок типа E_ERROR или фатальных ошибок с возможностью обработки E_RECOVERABLE_ERROR PHP выбрасывает исключение
  2. эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
  3. эти исключения наследуют класс Error
  4. оба класса Exception и Error реализуют интерфейс Throwable
  5. вы не можете реализовать интерфейс Throwable в своём коде

Интерфейс Throwable практически полностью повторяет нам Exception:

interface Throwable
{
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getTraceAsString(): string;
    public function getPrevious(): Throwable;
    public function __toString(): string;
}

Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:

try {
    // файл, который вызывает ошибку парсера
    include 'e_parse_include.php';
} catch (Error $e) {
    var_dump($e);
}

В результате ошибку поймаем и выведем:

object(ParseError)#1 (7) {
  ["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)"
  ["string":"Error":private] => string(0) ""
  ["code":protected] => int(0)
  ["file":protected] => string(49) "/www/education/error/e_parse_include.php"
  ["line":protected] => int(4)
  ["trace":"Error":private] => array(0) { }
  ["previous":"Error":private] => NULL
}

Как видите – поймали исключение ParseError, которое является наследником исключения Error, который реализует интерфейс Throwable, в доме который построил Джек. Ещё есть другие, но не буду мучать – для наглядности приведу иерархию исключений:

interface Throwable
  |- Exception implements Throwable
  |    |- ErrorException extends Exception
  |    |- ... extends Exception
  |    `- ... extends Exception
  `- Error implements Throwable
      |- TypeError extends Error
      |- ParseError extends Error
      |- ArithmeticError extends Error
      |  `- DivisionByZeroError extends ArithmeticError
      `- AssertionError extends Error 

TypeError – для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:

try {
    (function(int $one, int $two) {
        return;
    })('one', 'two');
} catch (TypeError $e) {
    echo $e->getMessage();
}

ArithmeticError – могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:

try {
    1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}

DivisionByZeroError – ошибка деления на ноль:

try {
    1 / 0;
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}

AssertionError – редкий зверь, появляется когда условие заданное в assert() не выполняется:

ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

try {
    assert(1 === 0);
} catch (AssertionError $e) {
    echo $e->getMessage();
}

При настройках production-серверов, директивы zend.assertions и assert.exception отключают, и это правильно

Задание
Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.

При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7

Отладка

Иногда для отладки кода нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:

<?php
function example() {
    echo '<pre>';
    debug_print_backtrace();
    echo '</pre>';
}

class ExampleClass {
    public static function method () {
        example();
    }
}

ExampleClass::method();

В результате выполнения функции debug_print_backtrace() будет выведен список вызовов приведших нас к данной точке:

#0  example() called at [/www/education/error/backtrace.php:10]
#1  ExampleClass::method() called at [/www/education/error/backtrace.php:14]

Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу], но я не встречал использования оных.

Assert

Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP, собственно это кусочек контрактной методологии программирования, и дальше я расскажу вам как я никогда его не использовал 🙂

Первый случай – это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:

// включаем вывод ошибок
error_reporting(E_ALL);
ini_set('display_errors', 1);

// включаем asserts
ini_set('zend.assertions', 1);
ini_set('assert.active', 1);

assert(false, "Remove it!");

В результате выполнения данного кода получим E_WARNING:

Warning: assert(): Remove it! failed

PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError:

// включаем asserts
ini_set('zend.assertions', 1);
ini_set('assert.active', 1);
// переключаем на исключения
ini_set('assert.exception', 1);

assert(false, "Remove it!");

В результате ожидаемо получаем не пойманный AssertionError. При необходимости, можно выбрасывать произвольное исключение:

assert(false, new Exception("Remove it!"));

Но я бы рекомендовал использовать метки @TODO, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними

Второй вариант использования – это создание некоего подобия TDD, но помните – это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:

// callback-функция для вывода информации в браузер
function backlog($script, $line, $code, $message) {
    echo "<h3>$message</h3>";
    highlight_string ($code);
}

// устанавливаем callback-функцию
assert_options(ASSERT_CALLBACK, 'backlog');
// отключаем вывод предупреждений
assert_options(ASSERT_WARNING,  false);

// пишем проверку и её описание
assert("sqr(4) == 16", "When I send integer, function should return square of it");

// функция, которую проверяем
function sqr($a) {
    return; // она не работает
}

Третий теоретический вариант – это непосредственно контрактное программирование – когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):

/**
 * Настройки соединения должны передаваться в следующем виде
 *
 *     [
 *         'host' => 'localhost',
 *         'port' => 3306,
 *         'name' => 'dbname',
 *         'user' => 'root',
 *         'pass' => ''
 *     ]
 *
 * @param $settings
 */
function setupDb ($settings) {
    // проверяем настройки
    assert(isset($settings['host']), 'Db `host` is required');
    assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer');
    assert(isset($settings['name']), 'Db `name` is required, should be integer');

    // соединяем с БД
    // ...
}

setupDb(['host' => 'localhost']);

Никогда не используйте assert() для проверки входных параметров, ведь фактически assert() интерпретирует строковую переменную (ведёт себя как eval()), а это чревато PHP-инъекцией. И да, это правильное поведение, т.к. просто отключив assert’ы всё что передаётся внутрь будет проигнорировано, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения

Если у вас есть живой опыт использования assert() – поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме – PHP Assertions, с таким же вопросом в конце 🙂

В заключение

Я за вас напишу выводы из данной статьи:

  • Ошибкам бой – их не должно быть в вашем коде
  • Используйте исключения – работу с ними нужно правильно организовать и будет счастье
  • Assert – узнали о них, и хорошо

P.S. Спасибо Максиму Слесаренко за помощь в написании статьи

In this guide, we will show you how to remove warning messages in PHP.

We will also explain why it is generally a bad idea to hide all warning messages.

Hiding all warning messages is basically the same as turning up the music in your car so that you can’t hear the worrying noise that your engine is making.

Hiding PHP warnings with the error_reporting function.

The error_reporting function allows us to tell PHP which errors to report.

For example, if we want to display all error messages except warnings, we can use the following line of code:

//Report all errors except warnings.
error_reporting(E_ALL ^ E_WARNING);

Typically speaking, the error_reporting function should be placed at the top of your code. This is because the function can only control errors that occur in the code below it.

Can I hide PHP notice messages as well?

If you also want to hide notice messages, then you can set the following level in the error_reporting function:

//Only report fatal errors and parse errors.
error_reporting(E_ERROR | E_PARSE);

//This will usually create a division by 0 
//warning message.
echo 1 / 0;

//This will usually create an array to string
//notice message.
echo array();

In the code snippet above, we told PHP that it should only report fatal errors (E_ERROR) and parse errors (E_PARSE).

Afterwards, we created two lines of code:

  • We divided 1 by 0, which would typically result in a “Warning: Division by zero” message.
  • We then attempted to echo out an array. Under normal circumstances, this would cause the following notice: “Notice: Array to string conversion”

If you run the example above, you will see that the page doesn’t display any notices or warnings. Furthermore, if you check the PHP error log, you will see that they also haven’t been logged.

Stopping warning messages from being displayed.

If you simply want to stop warning messages from being displayed, but not prevent them from being logged, then you can use the following piece of code:

//Tell PHP to log errors
ini_set('log_errors', 'On');
//Tell PHP to not display errors
ini_set('display_errors', 'Off');
//Set error_reporting to E_ALL
ini_set('error_reporting', E_ALL );

Here, we are using PHP’s ini_set function to dynamically modify the settings in our php.ini file:

  1. We set log_errors to On, which means that PHP will log warnings to our error log.
  2. We set display_errors to Off. As a result, PHP will not output errors to the screen.
  3. Finally, we set error_reporting to E_ALL.

Note: If you can access your php.ini file, then you should probably edit these values directly instead of changing them on the fly.

Using the @ character to suppress errors.

In some cases, you might not have control over certain warnings.

For example, a GET request to an external API could fail, resulting in a “failed to open stream” warning. To prevent this from occurring, we could use the @ character like so:

//API URL
$url = 'http://example.com/api';

//Attempt to get contents of that URL
$result  = @file_get_contents($url);

As you can see, we have placed the @ (AT) character next to our function call. This means that if file_get_contents fails, it will not throw an E_WARNING message.

This works because the @ character is an error control operator that tells PHP to ignore any errors.

Note that error suppression should be used sparingly. Abusing this control operator can lead to issues that are difficult to debug.

Why should I not suppress all warning messages?

An E_WARNING message is an error that does not prevent the rest of your PHP script from executing. Although it does not halt the script, it is still an error. It means that something in your code is not working the way that it is supposed to be working.

You should always try to address the issue instead of hiding it. Otherwise, it may lead to other bugs further down the line.

If you sweep these warning messages under the rug, you will be limiting your ability to spot serious problems in your application.

For example, what if your online shop had a “division by one” error that was preventing certain users from adding multiple items to their basket? Perhaps this issue only occurs when the user applies an expired coupon. Or maybe it happens after they reduce the quantity of an item in their basket.

Either way, something bad is happening and you don’t know about it.

You cannot rely on end users to report issues. Many of them will either think that they are using the website incorrectly or they will simply forget about it and move on. Internet users are impatient. They usually won’t take the time to fill out contact forms or attach screenshots.

As a result, this lazy approach of “nuking” all warning messages has caused your online shop to lose out on sales.

Error warnings for PHP are helpful during development, where you can use them for debugging. Showing the same errors and warnings in a production environment could pose a security risk as it could expose sensitive and exploitable information about the system.

Warning and error reporting in PHP are configured via the display_error error_reporting directives. display_error defines if errors are displayed at all, while error_reporting allows you to specify the type or level of errors to show.

  1. Open PHP configuration file using your preferred text editor.

    $ sudo vi /etc/php/7.4/apache2/php.ini
  2. Search for display_error directive.

    ; This directive controls whether or not and where PHP will output errors,
    ; notices and warnings too. Error output is very useful during development, but
    ; it could be very dangerous in production environments. Depending on the code
    ; which is triggering the error, sensitive information could potentially leak
    ; out of your application such as database usernames and passwords or worse.
    ; For production environments, we recommend logging errors rather than
    ; sending them to STDOUT.
    ; Possible Values:
    ;   Off = Do not display any errors
    ;   stderr = Display errors to STDERR (affects only CGI/CLI binaries!)
    ;   On or stdout = Display errors to STDOUT
    ; Default Value: On
    ; Development Value: On
    ; Production Value: Off
    ; http://php.net/display-errors
    display_errors = On
  3. Set the value to Off if you don’t want to see any error or warning messages at all.

    display_errors = Off

    Set the value to On instead to further tune the types of messages to display using error_reporting directive.

  4. Search for error_reporting directive.

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; Error handling and logging ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    ; This directive informs PHP of which errors, warnings and notices you would like
    ; it to take action for. The recommended way of setting values for this
    ; directive is through the use of the error level constants and bitwise
    ; operators. The error level constants are below here for convenience as well as
    ; some common settings and their meanings.
    ; By default, PHP is set to take action on all errors, notices and warnings EXCEPT
    ; those related to E_NOTICE and E_STRICT, which together cover best practices and
    ; recommended coding standards in PHP. For performance reasons, this is the
    ; recommend error reporting setting. Your production server shouldn't be wasting
    ; resources complaining about best practices and coding standards. That's what
    ; development servers and development settings are for.
    ; Note: The php.ini-development file has this setting as E_ALL. This
    ; means it pretty much reports everything which is exactly what you want during
    ; development and early testing.
    ;
    ; Error Level Constants:
    ; E_ALL             - All errors and warnings (includes E_STRICT as of PHP 5.4.0)
    ; E_ERROR           - fatal run-time errors
    ; E_RECOVERABLE_ERROR  - almost fatal run-time errors
    ; E_WARNING         - run-time warnings (non-fatal errors)
    ; E_PARSE           - compile-time parse errors
    ; E_NOTICE          - run-time notices (these are warnings which often result
    ;                     from a bug in your code, but it's possible that it was
    ;                     intentional (e.g., using an uninitialized variable and
    ;                     relying on the fact it is automatically initialized to an
    ;                     empty string)
    ; E_STRICT          - run-time notices, enable to have PHP suggest changes
    ;                     to your code which will ensure the best interoperability
    ;                     and forward compatibility of your code
    ; E_CORE_ERROR      - fatal errors that occur during PHP's initial startup
    ; E_CORE_WARNING    - warnings (non-fatal errors) that occur during PHP's
    ;                     initial startup
    ; E_COMPILE_ERROR   - fatal compile-time errors
    ; E_COMPILE_WARNING - compile-time warnings (non-fatal errors)
    ; E_USER_ERROR      - user-generated error message
    ; E_USER_WARNING    - user-generated warning message
    ; E_USER_NOTICE     - user-generated notice message
    ; E_DEPRECATED      - warn about code that will not work in future versions
    ;                     of PHP
    ; E_USER_DEPRECATED - user-generated deprecation warnings
    ;
    ; Common Values:
    ;   E_ALL (Show all errors, warnings and notices including coding standards.)
    ;   E_ALL & ~E_NOTICE  (Show all errors, except for notices)
    ;   E_ALL & ~E_NOTICE & ~E_STRICT  (Show all errors, except for notices and coding standards warnings.)
    ;   E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR  (Show only errors)
    ; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
    ; Development Value: E_ALL
    ; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
    ; http://php.net/error-reporting
    error_reporting = E_ALL
  5. Set error_reporting values to the types of messages to display.

    error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
  6. Restart your web server for changes to take effect.

    $ sudo systemctl restart apache2

Discuss the article:

Comment anonymously. Login not required.

Время на прочтение
6 мин

Количество просмотров 65K

В статье представлена очередная попытка разобраться с ошибками, которые могут встретиться на вашем пути php-разработчика, их возможная классификация, примеры их возникновения, влияние ошибок на ответ клиенту, а также инструкции по написанию своего обработчика ошибок.

Статья разбита на четыре раздела:

  1. Классификация ошибок.
  2. Пример, демонстрирующий различные виды ошибок и его поведение при различных настройках.
  3. Написание собственного обработчика ошибок.
  4. Полезные ссылки.

Классификация ошибок

Все ошибки, условно, можно разбить на категории по нескольким критериям.
Фатальность:

  • Фатальные
    Неустранимые ошибки. Работа скрипта прекращается.
    E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR.
  • Не фатальные
    Устранимые ошибки. Работа скрипта не прекращается.
    E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED.
  • Смешанные
    Фатальные, но только, если не обработаны функцией, определенной пользователем в set_error_handler().
    E_USER_ERROR, E_RECOVERABLE_ERROR.

Возможность перехвата ошибки функцией, определенной в set_error_handler():

  • Перехватываемые (не фатальные и смешанные)
    E_USER_ERROR, E_RECOVERABLE_ERROR, E_WARNING, E_NOTICE, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED.
  • Не перехватываемые (фатальные)
    E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING.

Инициатор:

  • Инициированы пользователем
    E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE.
  • Инициированы PHP
    E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED, E_USER_ERROR, E_RECOVERABLE_ERROR.

Для нас, в рамках данной статьи, наиболее интересны классификации по первым двум критериям, о чем будет рассказано далее.

Примеры возникновения ошибок

Листинг index.php

<?php
// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// подключаем файл с ошибками
require 'errors.php';

Листинг errors.php

<?php
echo "Файл с ошибками. Начало<br>";
/*
 * перехватываемые ошибки (ловятся функцией set_error_handler())
 */
// NONFATAL - E_NOTICE
// echo $undefined_var;
// NONFATAL - E_WARNING
// array_key_exists('key', NULL);
// NONFATAL - E_DEPRECATED
split('[/.-]', "12/21/2012"); // split() deprecated начиная с php 5.3.0
// NONFATAL - E_STRICT
// class c {function f(){}} c::f();
// NONFATAL - E_USER_DEPRECATED
// trigger_error("E_USER_DEPRECATED", E_USER_DEPRECATED);
// NONFATAL - E_USER_WARNING
// trigger_error("E_USER_WARNING", E_USER_WARNING);
// NONFATAL - E_USER_NOTICE
// trigger_error("E_USER_NOTICE", E_USER_NOTICE);

// FATAL, если не обработана функцией set_error_handler - E_RECOVERABLE_ERROR
// class b {function f(int $a){}} $b = new b; $b->f(NULL);
// FATAL, если не обработана функцией set_error_handler - E_USER_ERROR
// trigger_error("E_USER_ERROR", E_USER_ERROR);

/*
 * неперехватываемые (не ловятся функцией set_error_handler())
 */
// FATAL - E_ERROR
// undefined_function();
// FATAL - E_PARSE
// parse_error
// FATAL - E_COMPILE_ERROR
// $var[];

echo "Файл с ошибками. Конец<br>";

Примечание: для полной работоспособности скрипта необходим PHP версии не ниже 5.3.0.

В файле errors.php представлены выражения, инициирующие практически все возможные ошибки. Исключение составили: E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, генерируемые ядром Zend. В теории, встретить их в реальной работе вы не должны.
В следующей таблице приведены варианты поведения этого скрипта в различных условиях (в зависимости от значений директив display_errors и error_reporting):

Группа ошибок Значения директив* Статус ответа сервера Ответ клиенту**
E_PARSE, E_COMPILE_ERROR*** display_errors = off
error_reporting = ANY
500 Пустое значение
display_errors = on
error_reporting = ANY
200 Сообщение об ошибке
E_USER_ERROR, E_ERROR, E_RECOVERABLE_ERROR display_errors = off
error_reporting = ANY
500 Вывод скрипта до ошибки
display_errors = on
error_reporting = ANY
200 Сообщение об ошибке и вывод скрипта до ошибки
Не фатальные ошибки display_errors = off
error_reporting = ANY
и
display_errors = on
error_reporting = 0
200 Весь вывод скрипта
display_errors = on
error_reporting = E_ALL | E_STRICT
200 Сообщение об ошибке и весь вывод скрипта

* Значение ANY означает E_ALL | E_STRICT или 0.
** Ответ клиенту может отличаться от ответов на реальных скриптах. Например, вывод какой-либо информации до включения файла errors.php, будет фигурировать во всех рассмотренных случаях.
*** Если в файле errors.php заменить пример для ошибки E_COMPILE_ERROR на require "missing_file.php";, то ошибка попадет во вторую группу.

Значение, приведенной выше, таблицы можно описать следующим образом:

  1. Наличие в файле скрипта ошибки, приводящей его в «негодное» состояние (невозможность корректно обработать), на выходе даст пустое значение или же только само сообщение об ошибке, в зависимости от значения директивы display_errors.
  2. Скрипт в файле с фатальной ошибкой, не относящейся к первому пункту, будет выполняться в штатном режиме до самой ошибки.
  3. Наличие в файле фатальной ошибки при display_errors = Off обозначит 500 статус ответа.
  4. Не фатальные ошибки, как и следовало ожидать, в контексте возможности исполнения скрипта в целом, на работоспособность не повлияют.

Собственный обработчик ошибок

Для написания собственного обработчика ошибок необходимо знать, что:

  • для получения информации о последней произошедшей ошибке существует функция error_get_last();
  • для определения собственного обработчика ошибок существует функция set_error_handler(), но фатальные ошибки нельзя «перехватить» этой функцией;
  • используя register_shutdown_function(), можно зарегистрировать свою функцию, выполняемую по завершении работы скрипта, и в ней, используя знания из первого пункта, если фатальная ошибка имела место быть, предпринять необходимые действия;
  • сообщение о фатальной ошибке в любом случае попадет в буфер вывода;
  • воспользовавшись функциями контроля вывода можно предотвратить отображение нежелательной информации;
  • при использовании оператора управления ошибками (знак @) функция, определенная в set_error_handler() все равно будет вызвана, но функция error_reporting() в этом случае вернет 0, чем и можно пользоваться для прекращения работы или определения другого поведения своего обработчика ошибок.

Третий пункт поясню: зарегистрированная нами функция при помощи register_shutdown_function() выполнится в любом случае — корректно ли завершился скрипт, либо же был прерван в связи с критичной (фатальной) ошибкой. Второй вариант мы можем однозначно определить, воспользовавшись информацией предоставленной функцией error_get_last(), и, если ошибка все же была, выполнить наш собственный обработчик ошибок.
Продемонстрируем вышесказанное на модифицированном скрипте index.php:

<?php
/**
 * Обработчик ошибок
 * @param int $errno уровень ошибки
 * @param string $errstr сообщение об ошибке
 * @param string $errfile имя файла, в котором произошла ошибка
 * @param int $errline номер строки, в которой произошла ошибка
 * @return boolean
 */
function error_handler($errno, $errstr, $errfile, $errline)
{
    // если ошибка попадает в отчет (при использовании оператора "@" error_reporting() вернет 0)
    if (error_reporting() & $errno)
    {
        $errors = array(
            E_ERROR => 'E_ERROR',
            E_WARNING => 'E_WARNING',
            E_PARSE => 'E_PARSE',
            E_NOTICE => 'E_NOTICE',
            E_CORE_ERROR => 'E_CORE_ERROR',
            E_CORE_WARNING => 'E_CORE_WARNING',
            E_COMPILE_ERROR => 'E_COMPILE_ERROR',
            E_COMPILE_WARNING => 'E_COMPILE_WARNING',
            E_USER_ERROR => 'E_USER_ERROR',
            E_USER_WARNING => 'E_USER_WARNING',
            E_USER_NOTICE => 'E_USER_NOTICE',
            E_STRICT => 'E_STRICT',
            E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
            E_DEPRECATED => 'E_DEPRECATED',
            E_USER_DEPRECATED => 'E_USER_DEPRECATED',
        );

        // выводим свое сообщение об ошибке
        echo "<b>{$errors[$errno]}</b>[$errno] $errstr ($errfile на $errline строке)<br />n";
    }

    // не запускаем внутренний обработчик ошибок PHP
    return TRUE;
}

/**
 * Функция перехвата фатальных ошибок
 */
function fatal_error_handler()
{
    // если была ошибка и она фатальна
    if ($error = error_get_last() AND $error['type'] & ( E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR))
    {
        // очищаем буффер (не выводим стандартное сообщение об ошибке)
        ob_end_clean();
        // запускаем обработчик ошибок
        error_handler($error['type'], $error['message'], $error['file'], $error['line']);
    }
    else
    {
        // отправка (вывод) буфера и его отключение
        ob_end_flush();
    }
}

// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// включаем буфферизацию вывода (вывод скрипта сохраняется во внутреннем буфере)
ob_start();
// устанавливаем пользовательский обработчик ошибок
set_error_handler("error_handler");
// регистрируем функцию, которая выполняется после завершения работы скрипта (например, после фатальной ошибки)
register_shutdown_function('fatal_error_handler');

require 'errors.php';

Не забываем, что ошибки смешанного типа, после объявления собственного обработчика ошибок, стали не фатальными. Плюс к этому, весь вывод скрипта до фатальной ошибки вместе с стандартным сообщением об ошибке будет сброшен.

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

Полезные ссылки

  • Первоисточник: php.net/manual/ru/book.errorfunc.php
  • Описание ошибок: php.net/manual/ru/errorfunc.constants.php
  • Функции контроля вывода: php.net/manual/ru/ref.outcontrol.php
  • Побитовые операторы: php.net/manual/ru/language.operators.bitwise.php и habrahabr.ru/post/134557
  • Тематически близкая статья: habrahabr.ru/post/134499

Добавить комментарий