Виды фатальных ошибок

Антон Шевчук // 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]') . " done\n");
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]'') . " done\n");
    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. Спасибо Максиму Слесаренко за помощь в написании статьи

Всем известная истина звучит, что не ошибается только тот, кто ничего не делает. В этой статье мы рассмотрим, какие ошибки бывают, а также кратко рассмотрим функцию error reporting, используемую для контроля отображения errors. Но для начала (in first) изучим каждый вид дефектов отдельно, обратив внимание на наиболее распространенные.

Error в PHP: разновидности

Ошибки в PHP бывают:

— фатальные (fatal);

— не фатальные;

— пользовательские.   

Фатальные

Fatal error в PHP — одна из наиболее серьезных проблем. Такие дефекты появляются и при компиляции, и во время работы парсера либо PHP-скрипта. Основной нюанс заключается в том, что происходит прерывание исполнения скрипта.

Ниже рассмотрим основные разновидности фатальных ошибок:

  1. E_PARSE. Грубый недостаток в синтаксисе. PHP-интерпретатор не понимает, что вы вообще от него хотите. Пример — разработчик забыл закрыть (поставил лишнюю) фигурную либо круглую скобку либо написал код на непонятном интерпретатору языке. Здесь важно понимать следующее: код файла с parse error выполнен не будет, поэтому, если вы захотите включить отображение ошибок в этом же файле, где появилась parse error, такое не сработает.
  2. E_ERROR. Интерпретатор PHP понимает, что хочет разработчик, но выполнить это не может по разным причинам. Выполнение скрипта будет прервано, однако произойдет именно в месте возникновения проблемы, то есть код сработает до того места, где находится ошибка. Примеры:

— не удалось обнаружить подключаемый файл PHP;

— было выброшено, но не было обработано исключение;

— разработчик пытался вызвать метод класса, причем данный метод не существует;

— отсутствует свободная память (превышен лимит директивы memory_limit).

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

Не фатальные

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

Разновидности:

  • E_WARNING. Нередко встречаются, если разработчик подключает файл с использованием include, а данного файла или нет на сервере, или была допущена ошибка при указании пути. Другая причина E_WARNING — использование неправильного типа аргументов при вызове функций. Но вообще причин много — все не перечислишь;
  • E_NOTICE. Распространены наиболее широко. Вдобавок к этому, существуют любители, которые отключают вывод ошибок, в результате чего клепают E_NOTICE просто пачками. Эти errors сами по себе тривиальны:

— обращение к неопределенной переменной;

— обращение к элементу массива, когда элемент не существует;

— обращение к несуществующей константе;

— проблема, возникающая, если не конвертируются типы данных и т. п.

Чтобы избежать таких недоработок, надо быть внимательным, особенно к тому, что подсказывает IDE — игнорировать подсказки точно не стоит;

  • E_DEPRECATED. Язык программирования PHP станет ругаться при использовании устаревших функций (т. е. функций, которые помечены в качестве deprecated);
  • E_STRICT. Это тоже история про то, что нужно писать код правильно и обращать внимание на подсказки со стороны IDE, дабы потом не было мучительно больно и стыдно. К примеру, если вы вызовете нестатический метод как статику, код, отображенный ниже, функционировать будет, но ведь это как-то неправильно. Почему? Потому что в дальнейшем возможно появление существенных ошибок, если метод класса изменится, и появится обращение к $this:

class Strict {

    public function test() {

        echo «It’s test for me. It is not fatal error»;

    }

}

Strict::test();   

Но вообще тип E_STRICT больше актуален для PHP 5.6, поэтому он практически выпилен из 7-й версии языка.

Пользовательские

Этот «балаган» разводится самим разработчиком. Злоупотреблять такими errors не рекомендуется:

  • E_USER_WARNING — некритическая ошибка;
  • E_USER_ERROR — критическая;
  • E_USER_NOTICE — речь идет о сообщениях, которые ошибками не являются.

Отдельно надо сказать про E_USER_DEPRECATED — напоминает о том, что  метод либо функция устарели, то есть пришло время переписать код. Чтобы создать эту и подобные ошибки, применяется функция trigger_error:

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

  • когда если display_errors = on, в случае ошибки веб-браузер получит html c кодом 200 и текстом ошибки;
  • когда display_errors = off, для фатальных ошибок код реквеста будет 500, причем результат не вернется пользователю. Для остальных ошибок программный код будет работать неверно, однако он «никому про это не расскажет».

Error reporting

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

<?php

error_reporting(E_ALL);

ini_set(‘display_errors’, 1);

Функция error reporting является встроенной. Она позволяет контролировать, какие именно errors станут отображаться и сообщаться (reported) разработчику. Не стоит забывать и о том, что в PHP ini существует директива error_reporting, причем во время выполнения функция error_reporting() задает значение этой директивы.


Полезные ссылки на тематические материалы:

  • https://www.php.net/manual/ru/function.error-reporting.php;
  • https://www.netangels.pro/article/php-errors/;
  • https://habr.com/ru/post/440744/;
  • https://www.karashchuk.com/PHP/error_reporting-display_errors-display_startup_errors/.

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

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

В прошлом, обрабатывать фатальные ошибки было практически невозможно. Обработчик, установленный set_error_handler вызван не будет, скрипт просто будет завершен.
В PHP 7 при возникновении фатальных ошибок (E_ERROR) и фатальных ошибок с возможностью обработки (E_RECOVERABLE_ERROR) будет выброшен exception, а не произойдет завершение скрипта. Но определенные ошибки, например «out of memory», по прежнему приведут к остановке. Не перехваченные ошибки в PHP 7, будут «фатальны», так же как и в php 5.*.

Обратите внимание, что другие виды ошибок, такие как warinng и notice остаются без изменения в php 7.

Исключения выброшенные из E_ERROR и E_RECOVERABLE_ERROR не наследуются от Exception. Это разделение было сделано, чтобы предотвратить обработку этих ошибок кодом, написанным под 5.*. Исключения для фатальных ошибок теперь являются экземпляром нового класса: Error. Как и любые другие исключения, Error может отловлен, обработан и выполнен finally блок.

Throwable

Оба класса, и Error и Exception реализуют новый интерфейс Throwable.
Новая иерархия исключения состоит в следующем:

interface Throwable
    |- Exception implements Throwable
        |- ...
    |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- AssertionError extends Error

Если Throwable определить в коде PHP 7, то выглядит это так:

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;
}

Этот интерфейс должен быть знаком. Методы Throwable практически идентичны методам Exception. Разница лишь в том, что Throwable::getPrevious() может вернуть любой экземпляр Throwable, а не просто Exception. Конструкторы Exception и Error принимают любой экземпляр Throwable как предыдущее исключение.
Throwable может быть использован в блоке try/catch для отлова и Exception и Error (и любых других возможных в будущем исключений). Помните, что хорошей практикой является «ловля» исключений определенным классом исключений и обработка каждого типа отдельно. Но и иногда требуется отлавливать любое исключение. В PHP 7 try/catch блок для всех исключений должен использовать Throwable вместо Exception.

try {
    // Code that may throw an Exception or Error.
} catch (Throwable $t) {
    // Handle exception
}

Пользовательские классы не могут реализовывать Throwable. Это было сделано для предсказуемости: только экземпляры Exception или Error могут быть брошены. Кроме того, исключения содержат информацию о том, где объект был создан в stack trace. В пользовательских классах нет необходимых параметров, для хранения этой информации.

Error

Практически все ошибки (E_ERROR, E_RECOVERABLE_ERROR) в PHP 5.x, в PHP 7 выбрасывается экземпляром Error. Как и любые другие исключения, Error может быть пойман используя try/catch блок.

try {
    $undefined->method(); // Throws an Error object in PHP 7.
} catch (Error $e) {
    // Handle error
}

Большинство ошибок, которые были «фатальны» в PHP 5.x в PHP 7 буду выбрасывать простые Error объекты, но некоторые будут выбрасывать объекты подклассов: TypeError, ParseError и AssertionError.

TypeError

Экземпляр TypeError выбрасывается, когда аргументы метода или возвращаемое значение не совпадает с объявленным типом.

function add(int $left, int $right)
{
    return $left + $right;
}

try {
    $value = add('left', 'right');
} catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}

//Result:
//Argument 1 passed to add() must be of the type integer, string given
ParseError

ParseError выбрасывается, когда подключаемый (путем include/require) файл или код в eval содержит ошибки синтаксиса.

try {
    require 'file-with-parse-error.php';
} catch (ParseError $e) {
    echo $e->getMessage(), "\n";
}
AssertionError

Когда условие, заданное методом assert() не выполняется, выбрасывается AssertionError:

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

$test = 1;

assert($test === 0);

Fatal error: Uncaught AssertionError: assert($test === 0)

Метод assert() выполняется и выбрасывается AssertionError только, если они включены в настройках: zend.assertions = 1 и assert.exception = 1.

Использование Error в своём коде

Мы можем использовать класс Error, а также расширить Error, создав собственную иерархию класса Error. Это порождает вопрос: какие исключение должен выбрасывать Exception, а какие Error?
Error должен использоваться для указания проблем в коде, требующих внимания программиста (такие как неправильный тип входящих данных и синтаксические ошибки). Exception должен использоваться, когда исключение может «безопасно» обработаться, и выполнение программы может продолжиться.
Поскольку, объекты Error не могут быть обработаны во время выполнения программы, «ловля» Error должна быть редкостью. В целом, Error должны быть пойманы только для логирования их, необходимой «чистки данных», и отображения ошибки для пользователя.

Ловим исключения и в PHP 5.x и в PHP 7

Чтобы поймать исключения и в php 5.x и в php 7, используя один код, используем несколько блоков catch, ловим Throwable первым, затем Exception. После того, как поддержка PHP 5.x не потребуется, можно просто удалить блок ловли Exception.

try {
    // Code that may throw an Exception or Error.
} catch (Throwable $t) {
    // Executed only in PHP 7, will not match in PHP 5.x
} catch (Exception $e) {
    // Executed only in PHP 5.x, will not be reached in PHP 7
}

Время на прочтение
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

Improve Article

Save Article

Like Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    Like Article

    Error is the fault or mistake in a program. It can be several types. Error can occur due to wrong syntax or wrong logic. It is a type of mistakes or condition of having incorrect knowledge of the code.

    There are various types of errors in PHP but it contains basically four main type of errors.

    1. Parse error or Syntax Error: It is the type of error done by the programmer in the source code of the program. The syntax error is caught by the compiler. After fixing the syntax error the compiler compile the code and execute it. Parse errors can be caused dues to unclosed quotes, missing or Extra parentheses, Unclosed braces, Missing semicolon etc
      Example:

      <?php

      $x = "geeks";

      y = "Computer science";

      echo $x;

      echo $y;

      ?>

      Error:

      PHP Parse error:  syntax error, unexpected '=' 
      in /home/18cb2875ac563160a6120819bab084c8.php on line 3
      

      Explanation: In above program, $ sign is missing in line 3 so it gives an error message.

    2. Fatal Error: It is the type of error where PHP compiler understand the PHP code but it recognizes an undeclared function. This means that function is called without the definition of function.
      Example:

      <?php

      function add($x, $y)

      {

          $sum = $x + $y;

          echo "sum = " . $sum;

      }

      $x = 0;

      $y = 20;

      add($x, $y);

      diff($x, $y);

      ?>

      Error:

      PHP Fatal error:  Uncaught Error: 
      Call to undefined function diff() 
      in /home/36db1ad4634ff7deb7f7347a4ac14d3a.php:12
      
      Stack trace:
      #0 {main}
        thrown in /home/36db1ad4634ff7deb7f7347a4ac14d3a.php on line 12
      

      Explanation : In line 12, function is called but the definition of function is not available. So it gives error.

    3. Warning Errors : The main reason of warning errors are including a missing file. This means that the PHP function call the missing file.
      Example:

      <?php 

      $x = "GeeksforGeeks";

      include ("gfg.php");

      echo $x . "Computer science portal";

      ?>

      Error:

      PHP Warning:  include(gfg.php): failed to 
      open stream: No such file or directory in 
      /home/aed0ed3b35fece41022f332aba5c9b45.php on line 5
      PHP Warning:  include(): Failed opening 'gfg.php'
       for inclusion (include_path='.:/usr/share/php') in 
      /home/aed0ed3b35fece41022f332aba5c9b45.php on line 5
      

      Explanation: This program call an undefined file gfg.php which are not available. So it produces error.

    4. Notice Error: It is similar to warning error. It means that the program contains something wrong but it allows the execution of script.
      Example:

      <?php 

      $x = "GeeksforGeeks";

      echo $x;

      echo $geeks;

      ?>

      Error:

      PHP Notice:  Undefined variable: geeks in 
      /home/84c47fe936e1068b69fb834508d59689.php on line 5
      

      Output:

      GeeksforGeeks
      

      Explanation: This program use undeclared variable $geeks so it gives error message.

    PHP error constants and their description :

    • E_ERROR : A fatal error that causes script termination
    • E_WARNING : Run-time warning that does not cause script termination
    • E_PARSE : Compile time parse error.
    • E_NOTICE : Run time notice caused due to error in code
    • E_CORE_ERROR : Fatal errors that occur during PHP’s initial startup (installation)
    • E_CORE_WARNING : Warnings that occur during PHP’s initial startup
    • E_COMPILE_ERROR : Fatal compile-time errors indication problem with script.
    • E_USER_ERROR : User-generated error message.
    • E_USER_WARNING : User-generated warning message.
    • E_USER_NOTICE : User-generated notice message.
    • E_STRICT : Run-time notices.
    • E_RECOVERABLE_ERROR : Catchable fatal error indicating a dangerous error
    • E_DEPRECATED : Run-time notices.

    Like Article

    Save Article

    by Vincy. Last modified on July 3rd, 2022.

    There are various possible error occurrences may happen in PHP. These errors are categorized based on the time of occurrences and based on whether it is recoverable or not.

    And then, this classification is made with respect to how it is triggered to send an error message to the browser. It might be triggered automatically while executing an improper line of code or triggered by the user by using trigger_error() function.

    PHP errors will occur with some improper attempts with PHP scripts like, an invalid line of code execution, an infinite loop that causes default execution time elapse (30 seconds), and etc. Let us start with the major classification of PHP errors as follows.

    1. Fatal error
    2. Parse error
    3. Warning
    4. Notices

    This type of errors is uncaught exceptions that can not be recovered. When this error occurred, then it will stop the execution. Based on the time of occurrence, fatal errors are classified as,

    • Startup fatal error – This will occur when the code cannot be executed with the PHP environment due to the fault that occurred at the time of installation.
    • Compile time fatal error – This kind of error will occur when we attempt to use nonexistent data like file, class, function and etc.
    • Run time fatal error – This will occur during execution. It is similar to compile time fatal error, except Compile time fatal error is generated by the Zend engine based on the time of occurrence.

    Example: PHP Fatal Error

    Let us call a nonexistent function fnSwap() in the following PHP program.

    <?php
    fnSwap();
    echo "Swapped Successfully!";
    ?>
    

    This program will raise the following fatal error at the time of execution which will stop executing a further line that is the echo statement.

    Fatal error: Call to undefined function fnSwap() in ... on line 2
    

    Parse Error

    Parse errors are generated only at compile time which is also called as a syntax error. If anything wrong with PHP syntax, for example, missing semi-colon for the end of the line, will trigger this type of errors to be displayed to the browser.

    <?php
    echo "content to be displayed to the browser!"
    echo "<br/>embedding line break";
    ?>
    

    This program sends parse error to the browser as follows due to the lack of semicolon(;) at the end of the line.

    Parse error: syntax error, unexpected 'echo' (T_ECHO), expecting ',' or ';' in ... on line 3
    

    Warning

    Like fatal errors, PHP warning messages also created based on the three types of warning, that is, Startup warning, Compile time warning and Runtime warning. PHP will create warning message for sending them to the user without halting the execution. An example scenario for warning messages to be created is the divide by zero problem that is as shown in the following PHP program.

    <?php
    $count = 0;
    $total = 200;
    $result = $total / $count;
    echo "RESULT: " . $result;
    ?>
    

    In the above program, since $count has the value 0 and any number divided by zero is undefined, the line on which the division is made will create the following warning notices followed by the string returned by the echo statement with an empty value for $result variable. Meaning that, even after the occurrence of the warning error, the echo statement is executed.

    Warning: Division by zero in ... on line 4
    RESULT:
    

    Notice

    Like other PHP error messages, notice message can be created automatically or by the user by using the PHP trigger_error() function.It is used to send messages to the browser to make the user know about the problem of the code is any, which might cause an error.

    For example, the following program starts with incrementing an uninitialized variable $result to print incremented value to the browser. Since $result is not initialized, it will automatically trigger the notice error on executing this script.

    <?php
    $result += 1;
    echo "RESULT: " . $result;
    ?>
    

    And the notice is,

    Notice: Undefined variable: result in ... on line 2
    RESULT: 1
    

    But program execution will not be terminated because of this PHP notice. Rather, the notice message will be sent to the browser and the echo statement will print the incremented $result value subsequently.

    Note:

    • These are set of predefined error constants in PHP, like, E_ERROR, E_WARNING, E_NOTICE, E_PARSE and etc. Each of them is defined with integer value appropriately. For example, the integer value that is defined for E_ERROR is 1.
    • These error constants are required to be specified with PHP configuration file (php.ini) to display various type of PHP errors while execution.
    • On the other hand, we can override error reporting settings at run time by using the PHP error_reporting() function.
    • Another alternative way of overriding error related directive setting on configuration file is, by enabling PHP flags with the .htaccess file. For example,
      php_flag display_errors on
      

    ↑ Back to Top

    PHP errors are more or less serious code errors that can start from a simple syntax error and end up with fatal errors that cause code execution to stop.

    In this article, PHP errors: a complete guide, let’s look at what types of errors there are, and for each type of error let’s examine the specific examples.

    After that, we will see several methods that allow us to display PHP errors.

    Php Errors Guide

    What are PHP errors?

    When there is a syntax error or an undeclared variable or function it is called PHP code and an error occurs. Thus, we talk about PHP errors when there are problems within the code.

    As we shall see in this article, PHP errors: a complete guide, there are several types of errors. Some may interrupt script execution, while others prevent us from getting the result we expect.

    When we create a script in PHP, we can run into four types of errors. Let’s see what they are.

    Types of PHP errors

    Code errors in PHP can be divided into four categories: notice, warning, parse error, and fatal error.

    Notice

    When such an error occurs in the script code, script execution is not interrupted. An error notice is not a serious error, but this is used to indicate that an error may be present.

    The most common case is when an attempt is made to call a variable that has not been defined.

    When such an error occurs it will be indicated as “Notice error” or as “PHP Notice”.

    Warning

    PHP errors of the type “warning” are part of nonfatal errors. Again, as we have just seen for notice errors, the execution of the script is not interrupted.

    This type of error can occur, for example, when incorrect parameters are passed to a function or when an attempt is made to call a file that is not present.

    Such an error is identified as a “Warning error” or “PHP warning.”

    Parse error

    In PHP, parse errors are syntax errors, so it is an error in the code such as a typo, the absence of a semicolon or quotation marks.

    PHP errors that belong to this category are referred to as “Parse errors” or as “PHP Parse errors.”

    Fatal error

    Fatal errors are critical errors that interrupt script execution. They can occur during initialization, compilation, or during code execution itself.

    These errors are referred to as “Fatal error” or “PHP fatal error.”

    PHP error examples

    Now that we have take a look at the four main categories of PHP errors, let’s look at examples to help us identify the type of errors.

    Php Error Example

    Parse error

    As we have seen, these errors can be generated during code writing. In most cases they can be due to a typo or an oversight.

    Here is an example of a parse error:

    <?php
    //Simulate a "Parse error"
    echo "Hello everyone n"
    echo "Welcome";
    ?>

    The code above returns this error:

    Parse error: syntax error, unexpected token "echo", expecting "," or ";" in [...] on line 4

    In this case, we get this error message because in line 3, at the end of the echo “Hello everyone,” we forgot to insert the semicolon.

    Warning error

    Non-critical errors belongs to this category. Thus, in these cases, code execution is completed even if the error is encountered.

    Let’s look at an example of a warning error:

    <?php
    //Simulate a "Warning error"
    function sum($a,$b) {
    return $a+$b;
    }
    include ("math_functions.php");
    echo sum(7,3);
    ?>

    This code returns the following warning:

    Warning:  include(funzioni_matematiche.php): Failed to open stream: No such file or directory in [...] on line 6
    
    Warning:  include(): Failed opening 'funzioni_matematiche.php' for inclusion (include_path='.:') in [...] on line 6
    10

    In this case, the program continues with the code execution, and as we see, the sum function is executed and the expected result (10) is returned to us.

    Before returning the result, a Warning error warns us that the file math_functions.php is not present on the server.

    Notice error

    Like warning errors, notice errors are not fatal and do not interrupt code execution.

    Here is an example of code that generates a notice error:

    <?php
    //Simulate a "Notice error"
    function name_surname($nome,$cognome) {
        return "Nome: ".$name." Surname: ".$surname;
    }
    $n = "John";
    $c = "Smith";
    echo name_surname($n,$v);
    ?>

    The result will be as follows:

    Notice:  Undefined variable: v in [...] on line 8
    Name: John Smith: 

    In this case we called in the function name_surname the variable $v which had not been declared. We are shown the presence of the error (undefined variable) and the output is returned because the execution of the code continues.

    As we can see, however, in this case the result we get is not as expected because only the “name” field is returned to us.

    The type of errors, and therefore the type of warnings we get, may vary depending on the PHP version we are using.

    In this case, in fact, in PHP 7 we get a Notice error. In PHP 8, however, this error has been reclassified as a Warning error.

    For more details on the reclassification of errors from version 7 to 8 you can refer to the RFC.

    Fatal error

    Fatal errors are critical errors that can occur in several cases, for example, if we try to call a function that does not exist.

    There is an example of a fatal error in this code:

    <?php
    //Simulate a "Fatal error"
    function sum($a,$b) {
        return $a+$b;
    }
    echo multiply(7,3);
    ?>

    Execution returns this error:

    Fatal error:  Uncaught Error: Call to undefined function moltiplica() in [...]:7
    Stack trace:
    #0 {main}
      thrown in [...] on line 7

    In this case we are attempting to call the multiply() function which has not been declared. Keep in mind that when such an error occurs, code execution is aborted.

    How to show PHP errors

    During development it is very easy to run into errors, even simply syntax errors. To be able to identify and fix PHP errors, it is necessary to understand what kind of errors they are and where they are in the code.

    Debug Php Errors

    Remember that error display should be activated only for debugging or development purposes. Remember, therefore, to turn it off once you are finished.

    There are several ways to display PHP errors:

    • add code to the PHP file
    • add directives to the php.ini file
    • enable error display with .htaccess file
    • enable error display from cPanel
    • Consult the error log.

    Let’s see how to show errors by following these methods.

    Edit PHP script

    One of the quickest ways to show PHP errors, is to add code directly into the script.

    In this case we simply add these lines to the beginning of the PHP file:

    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    error_reporting(E_ALL);

    The display_errors directive allows us to enable or disable the display of errors.

    By enabling display_startup_errors, we can also display errors encountered during initialization.

    With the “error_reporting” function we can specify which errors to display. The parameter “E_ALL” allows us to display all errors.

    If we wanted to exclude Notice errors instead, we would simply use this parameter:

    E_ALL & ~E_NOTICE

    Be careful, because with this system errors that cause script execution to stop, such as parse and fatal error, are not shown. In this case you have to consult the log files to see the errors.

    Edit the php.ini file

    To show PHP errors we just need to edit the php.ini file.

    Keep in mind that some providers do not allow you to edit the php.ini file.

    With all our plans from shared hosting to dedicated services such as VPS cloud hosting and dedicated servers, you can change the PHP version and edit the file php.ini.

    If you want to put the service to the test, take advantage of our free hosting for 14 days and do all the testing you want with a trial plan.

    As we will see in the next section of this article, PHP errors: a complete guide, you can also enable the display of errors directly from cPanel, enabling the settings without having to edit the code at all.

    In this case, let’s see what directives you need to add to the php.ini file to enable error display:

    display_errors = on
    error_reporting = E_ALL

    With these directives all errors will be shown on the screen.

    Edit the .htaccess file

    We can also enable error display through the .htaccess file. In this case we just need to add the following lines:

    php_flag display_errors on
    php_flag display_startup_errors on

    Enable errors from cPanel

    From cPanel, you can enable the display of code errors on the screen or consult the error log.

    Keep in mind that for an online site, it is preferable to consult the error log.

    If, however, the site or application is in development, you may need to enable screen display.

    Let’s see how to do it.

    Consult the error log

    On our plans, the error log is active by default. If you want to check for code errors, you can look at the error_log file located in the directory where the error was encountered.

    This file is automatically generated by the system when a code error occurs. You can simply use the file manager of cPanel to locate the file and view it.

    In this example, you can see the error_log file that stored fatal and notice PHP errors from the examples I showed you earlier.

    Php Error Logs

    View PHP errors on screen

    On our hosting plans, by default option the management of the PHP version is entrusted to Cloudlinux. In this case, therefore, you can use the options of the Select PHP Version tool to enable the display of errors on the screen.

    Cpanel Select Php Version Settings

    From the Options tab you can turn on the diplay_errors option as you see in this screenshot.

    Cpanel Diplay Errors Php Selector

    In this screen shot the display_errors option is disabled

    Remember to turn off the option before publishing the site or after you have fixed the errors.

    Conclusion

    We have seen that there are different types of PHP errors and that in some cases they can break the execution of scripts.

    In addition to being able to consult error logs, there are also several ways to enable the display of code errors on the screen. This system helps us for debugging purposes and allows us to identify errors during development.

    Did you know about PHP error classification? Have you ever enabled error display to solve a problem? Let me know in he comments below.

    PHP RFC: Exceptions in the engine (for PHP 7)

    • Date: 2014-09-30

    • Status: Implemented (in PHP 7.0)

    Introduction

    This RFC proposes to allow the use of exceptions in the engine and to allow the replacement of existing fatal or recoverable fatal errors with exceptions.

    As an example of this change, consider the following code-snippet:

    function call_method($obj) {
        $obj->method();
    }
     
    call_method(null); // oops!

    Currently the above code will throw a fatal error 1):

    Fatal error: Call to a member function method() on a non-object in /path/file.php on line 4

    This RFC replaces the fatal error with an EngineException. As such it is now possible to catch this error condition:

    try {
        call_method(null); // oops!
    } catch (EngineException $e) {
        echo "Exception: {$e->getMessage()}n";
    }
     
    // Exception: Call to a member function method() on a non-object

    If the exception is not caught, PHP will continue to throw the same fatal error as it currently does.

    Motivation

    Summary of current error model

    PHP currently supports 16 different error types which are listed below, grouped by severity:

    // Fatal errors
    E_ERROR
    E_CORE_ERROR
    E_COMPILE_ERROR
    E_USER_ERROR
    
    // Recoverable fatal errors
    E_RECOVERABLE_ERROR
    // Parse error
    E_PARSE
    
    // Warnings
    E_WARNING
    E_CORE_WARNING
    E_COMPILE_WARNING
    E_USER_WARNING
    
    // Notices etc.
    E_DEPRECATED
    E_USER_DEPRECATED
    E_NOTICE
    E_USER_NOTICE
    E_STRICT

    The first four errors are fatal, i.e. they will not invoke the error handler, abort execution in the current context and directly jump (bailout) to the shutdown procedure.

    The E_RECOVERABLE_ERROR error type behaves like a fatal error by default, but it will invoke the error handler, which can instruct the engine to ignore the error and continue execution in the context where the error was raised.

    The E_PARSE error normally behaves like a fatal error (e.g. when include is used). However when eval() is used the bailout is not performed (the error handler is still skipped though).

    The remaining errors are all non-fatal, i.e. execution continues normally after they occur. The error handler is invoked for all error types apart from E_CORE_WARNING and E_COMPILE_WARNING.

    Issues with fatal errors

    Cannot be gracefully handled

    The most obvious issue with fatal errors is that they immediately abort execution and as such cannot be gracefully recovered from. This behavior is very problematic in some situations.

    As an example consider a server or daemon written in PHP. If a fatal error occurs during the handling of a request it will abort not only that individual request but kill the entire server/daemon. It would be much preferable to catch the fatal error and abort the request it originated from, but continue to handle other requests.

    Another example is running tests in PHPUnit: If a test throws a fatal error this will abort the whole test-run. It would be more desirable to mark the individual test as failed, but continue running the rest of the testsuite.

    Error handler is not called

    Fatal errors do not invoke the error handler and as such it is hard to apply custom error handling procedures (for display, logging, mailing, …) to them. The only way to handle a fatal error is through a shutdown function:

    register_shutdown_function(function() { var_dump(error_get_last()); });
     
    $null = null;
    $null->foo();
     
    // shutdown function output:
    array(4) {
      ["type"]=> int(1)
      ["message"]=> string(47) "Call to a member function foo() on a non-object"
      ["file"]=> ...
      ["line"]=> ...
    }

    This allows rudimentary handling of fatal errors, but the available information is very limited. In particular the shutdown function is not able to retrieve a stacktrace for the error (which is possible for other error types going through the error handler.)

    Finally blocks will not be invoked

    If a fatal error occurs finally blocks will not be invoked:

    $lock->acquire();
    try {
        doSomething();
    } finally {
        $lock->release();
    }

    If doSomething() in the above example results in a fatal error the finally block will not be run and the lock is not released.

    Destructors are not called

    When a fatal error occurs destructors are not invoked. This means that anything relying on the RAII (Resource Acquisition Is Initialization) will break. Using the lock example again:

    class LockManager {
        private $lock;
        public function __construct(Lock $lock) {
            $this->lock = $lock;
            $this->lock->acquire();
        }
        public function __destruct() {
            $this->lock->release();
        }
    }
     
    function test($lock) {
        $manager = new LockManager($lock); // acquire lock
     
        doSomething();
     
        // automatically release lock via dtor
    }

    If doSomething() in the above example throws a fatal error the destructor of LockManager is not called and as such the lock is not released.

    As both finally blocks and destructors fail in face of fatal errors the only reasonably robust way of releasing critical resources is to use a global registry combined with a shutdown function.

    Issues with recoverable fatal errors

    After acknowledging that the use of fatal errors is problematic, one might suggest to convert fatal errors to recoverable fatal errors where possible. Sadly this also has several issues:

    Execution is continued in same context

    When a recoverable fatal error is dismissed by a custom error handler, execution is continued as if the error never happened. From a core developer perspective this means that a recoverable fatal error needs to be implemented in the same way as a warning is, with the assumption that the following code will still be run.

    This makes it technically complicated to convert fatal errors into recoverable errors, because fatal errors are typically thrown in situation where continuing execution in the current codepath is not possible. For example the use of recoverable errors in argument sending would likely require manual stack and call slot cleanup as well as figuring out which code to run after the error.

    Hard to catch

    While E_RECOVERABLE_ERROR is presented as a “Catchable fatal error” to the end user, the error is actually rather hard to catch. In particular the familiar try/catch structure cannot be used and instead an error handler needs to be employed.

    To catch a recoverable fatal error non-intrusively code along the following lines is necessary:

    set_error_handler(function($errno, $errstr, $errfile, $errline) {
        if ($errno === E_RECOVERABLE_ERROR) {
            throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
        }
        return false;
    });
     
    try {
        new Closure;
    } catch (Exception $e) {
        echo "Caught: {$e->getMessage()}n";
    }
     
    restore_error_handler();

    Performance

    The ability to bypass recoverable fatal errors while still continuing execution in the same code path may cause performance issues in some cases. For example it is currently possible to completely ignore argument type hints with an error handler. As such a JIT compiler may not be able to make strong assumptions about the types of type hinted parameters.

    Solution: Exceptions

    Exceptions provide an approach to error handling that does not suffer from the problems of fatal and recoverable fatal errors. In particular exceptions can be gracefully handled, they will invoke finally blocks and destructors and are easily caught using catch blocks.

    From an implementational point of view they also form a middle ground between fatal errors (abort execution) and recoverable fatal errors (continue in the same codepath). Exceptions typically leave the current codepath right away and make use of automatic cleanup mechanisms (e.g. there is no need to manually clean up the stack). In order to throw an exception from the VM you usually only need to free the opcode operands and invoke HANDLE_EXCEPTION().

    Exceptions have the additional advantage of providing a stack trace.

    Proposal

    This proposal introduces two new exception types:

    • EngineException as the recommended default exception type for exceptions emitted from the executor.

    • ParseException for use with parse errors in particular.

    Additionally the following policy changes are made:

    • It is now allowed to use exceptions in the engine.

    • Existing errors of type E_ERROR, E_RECOVERABLE_ERROR, E_PARSE or E_COMPILE_ERROR can be converted to exceptions.

    • It is discouraged to introduce new errors of type E_ERROR or E_RECOVERABLE_ERROR. Within limits of technical feasibility the use of exceptions is preferred.

    In order to avoid updating many tests the current error messages will be retained if the engine/parse exception is not caught. This may be changed in the future.

    The patch attached to this RFC already converts a large number of fatal and recoverable fatal errors to exceptions. It also converts parse errors to exceptions.

    Hierarchy

    There is some concern that by extending EngineException directly from Exception, previously fatal errors may be accidentally caught by existing catch(Exception $e) blocks (aka Pokemon exception handling). To alleviate this concern it is possible to introduce a new BaseException type with the following inheritance hierarchy:

    BaseException (abstract)
     +- EngineException
     +- ParseException
     +- Exception
         +- ErrorException
         +- RuntimeException
             +- ...
         +- ...

    As such engine/parse exceptions will not be caught by existing catch(Exception $e) blocks.

    Whether such a hierarchy (with a new BaseException type) should be adopted will be subject to a secondary vote.

    Potential issues

    E_RECOVERABLE_ERROR compatibility

    Currently it is possible to silently ignore recoverable fatal errors with a custom error handler. By replacing them with exceptions this capability is removed, thus breaking compatibility.

    I have never seen this possibility used in practice outside some weird hacks (which use ignored recoverable type constraint errors to implement scalar typehints). In most cases custom error handlers throw an ErrorException, i.e. they emulate the proposed behavior with a different exception type.

    E_PARSE compatibility

    Currently parse errors generated during eval() (but not require etc) are non-fatal. This proposal would make eval() throw an exception instead, which would require some code adjustments in cases where the developer wishes to gracefully handle eval() errors.

    As parse errors do not invoke the error handler, handling eval errors is tricky and requires code looking roughly as follows 2):

    set_error_handler(function() { return false; }, 0);
    @$undefinedVariable;
    restore_error_handler();
     
    $oldErrorReporting = error_reporting();
    error_reporting($oldErrorReporting & ~E_PARSE);
    $result = eval($code);
    error_reporting($oldErrorReporting);
     
    $error = error_get_last();
    if ($result === false && $error['type'] === E_PARSE) {
        // Handle $error
    }

    After this RFC errors should be handled as follows instead:

    try {
        $result = eval($code);
    } catch (ParseException $exception) {
        // Handle $exception
    }

    Not all errors converted

    The Zend Engine currently (master on 2014-09-30) contains the following number of fatal-y errors:

    E_ERROR:            182    (note: not counting 636 occurrences in zend_vm_execute.h)
    E_CORE_ERROR:        12
    E_COMPILE_ERROR:    146
    E_PARSE:              1
    E_RECOVERABLE_ERROR: 17

    The count was obtained using git grep “error[^(]*(E_ERROR_TYPE” Zend | wc -l and as such may not be totally accurate, but should be a good approximation.

    The patch attached to the RFC currently (as of 2014-09-30) removes 75 E_ERRORs, 13 E_RECOVERABLE_ERRORs and the one E_PARSE error. While I hope to port more errors to exceptions before the patch is merged, the process is rather time consuming and I will not be able to convert all errors. (Note: The number of occurrences in the source code says rather little about what percentage of “actually thrown” errors this constitutes.)

    Some errors are easy to change to exceptions, others are more complicated. Some are impossible, like the memory limit or execution time limit errors. The E_CORE_ERROR type can’t be converted to use exceptions because it occurs during startup (at least if used correctly). Converting E_COMPILE_ERROR to exceptions would also require some significant changes to the compiler.

    This means that there will always be some truly fatal errors and to a userland developer the distinction between what results in an exception and what in a fatal error may be non-obvious. I don’t think that this is really a problem. Not being able to make everything an exception is no reason to avoid exceptions in the cases where they can be used.

    Backwards compatibility

    The E_ERROR portion of this proposal does not break backwards compatibility: All code that was previously working, will continue to work. The change only relaxes error conditions, which is generally not regarded as breaking BC.

    The E_RECOVERABLE_ERROR part of the proposal may introduce a minor BC break, because it will no longer allow to silently ignore recoverable errors with a custom error handler. As this point is somewhat controversial I’ll have a separate voting option for this.

    The E_PARSE part of the proposal may introduce a minor BC break, because E_PARSE exhibits non-fatal behavior when used with eval().

    Patch

    A patch for this RFC is available at https://github.com/php/php-src/pull/1095.

    The patch introduces basic infrastructure for this change, replaces E_PARSE with ParseException and a number of E_ERROR and E_RECOVERABLE_ERROR with EngineException.

    To simplify porting to exceptions it is possible to throw engine exceptions by passing an additional E_EXCEPTION flag to zend_error (instead of using zend_throw_exception). To free unfetched operands in the VM the FREE_UNFETCHED_OPn pseudo-macros are introduced. An example of the necessary change to port a fatal error to an exception:

    - zend_error_noreturn(E_ERROR, "Cannot pass parameter %d by reference", opline->op2.num);
    + zend_error(E_EXCEPTION | E_ERROR, "Cannot pass parameter %d by reference", opline->op2.num);
    + FREE_UNFETCHED_OP1();
    + HANDLE_EXCEPTION();

    The current patch continues using “Recoverable fatal error” messages even for errors that are not recoverable anymore in the previous sense of the word. These messages will be adjusted afterwards. (The patch tries to separate important functional changes from cosmetic tweaks.)

    Vote

    The primary vote decides if the proposal as outlined in the RFC is accepted and requires a 2/3 majority.

    The secondary vote decides whether or not a BaseException based hierarchy should be introduced, as described in the Hierarchy section of the proposal. Whichever option has more votes wins.

    Voting started on 2015-02-23 and ends on 2015-03-08.

    PHP для начинающих. Обработка ошибок +32

    PHP


    Рекомендация: подборка платных и бесплатных курсов Java — https://katalog-kursov.ru/

    image

    Не совершает ошибок только тот, кто ничего не делает, и мы тому пример — сидим и трудимся не покладая рук, читаем Хабр :)

    В этой статье я поведу свой рассказа об ошибках в 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 (да, данная ошибка может проявиться в таком виде только при включении 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;
    

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

    /**
     * Notice: Undefined index: a
     */
    $b = [];
    $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();
    

    Данный тип ошибок актуален для PHP версии 5.6, и практически все их выпилили из
    7-ки. Почитать подробней можно в соответствующей RFC. Если кто знает где ещё остались данные ошибки, то напишите в комментариях

    E_DEPRECATED

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

    /**
     * Deprecated: Function split() is deprecated
     */
    // данная функция, удалена из PHP 7.0
    // считается устаревшей с PHP 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');

    Хотел обратить внимание, что данный код хоть ещё и встречается для обработки ошибок, и вы возможно вы даже с ним столкнётесь, но он потерял актуальность начиная с 7-ой версии PHP. Что пришло на замену я расскажу чуть погодя.

    Задание

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

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

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

    /**
     * Этот код не вызывает ошибок
     */
    
    // засекаем время выполнения скрипта
    $time= microtime(true);
    
    define('AAA', 'AAA');
    $arr = [];
    for ($i = 0; $i < 10000; $i++) {
        $arr[AAA] = $i;
    }
    
    printf('%f seconds <br/>', microtime(true) - $time);
    

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

    0.002867 seconds
    

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

    /**
     * Этот код содержит ошибку
     */
    
    // засекаем время выполнения скрипта
    $time= microtime(true);
    
    $arr = [];
    for ($i = 0; $i < 10000; $i++) {
        $arr[BBB] = $i; // тут используем константанту, которая у нас не объявлена
    }
    
    printf('%f seconds <br/>', microtime(true) - $time);
    

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

    0.263645 seconds
    

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

    Тестирование проводил на различных версиях PHP и везде разница в десятки раз, так что пусть это будет ещё одним поводом для исправления всех ошибок в коде

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

    В 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 (random_int(0, 1)) {
            throw new Exception("One");
        }
        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 отключают, и это правильно

    Полный список предопределённых исключений вы найдёте в официальном мануале, там же иерархия SPL исключений.

    Задание

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

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

    Единообразие

    — Там ошибки, тут исключения, а можно это всё как-то до кучи сгрести?

    Да запросто, у нас же есть set_error_handler() и никто нам не запретит внутри оного обработчика бросить исключение:

    // Бросаем исключение вместо ошибок
    function errorHandler($severity, $message, $file = null, $line = null)
    {
        // Кроме случаев, когда мы подавляем ошибки с помощью @
        if (error_reporting() === 0) {
            return false;
        }
        throw new ErrorException($message, 0, $severity, $file, $line);
    }
    
    // Будем обрабатывать все-все ошибки
    set_error_handler('errorHandler', E_ALL);
    

    Но данный подход с PHP7 избыточен, со всем теперь справляется Throwable:

    try {
        /** ... **/
    } catch (Throwable $e) {
        // отображение любых ошибок и исключений
        echo $e->getMessage();
    }
    

    Отладка

    Иногда, для отладки кода, нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция 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. Собственно, этот кусочек можно рассматривать как мимикрию под контрактную методологию программирования, и дальше я расскажу вам как я никогда его не использовал :)

    Функция assert() поменяла своё поведение при переходе от версии 5.6 к 7.0, и ещё сильней всё поменялось в версии 7.2, так что внимательней читайте changelog’и PHP ;)

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

    // включаем asserts в php.ini
    // zend.assertions=1
    assert(false, "Remove it!");
    

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

    Warning: assert(): Remove it! failed

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

    // переключаем в режим «исключений»
    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 $message;
    }
    
    // устанавливаем 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']);
    

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

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

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

    В заключение

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

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

    P.S.

    Это репост из серии статей «PHP для начинающих»:

    • Сессия
    • Подключение файлов
    • Обработка ошибок

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

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

    Всем известная истина звучит, что не ошибается только тот, кто ничего не делает. В этой статье мы рассмотрим, какие ошибки бывают, а также кратко рассмотрим функцию error reporting, используемую для контроля отображения errors. Но для начала (in first) изучим каждый вид дефектов отдельно, обратив внимание на наиболее распространенные.

    Error в PHP: разновидности

    Ошибки в PHP бывают:

    — фатальные (fatal);

    — не фатальные;

    — пользовательские.   

    Фатальные

    Fatal error в PHP — одна из наиболее серьезных проблем. Такие дефекты появляются и при компиляции, и во время работы парсера либо PHP-скрипта. Основной нюанс заключается в том, что происходит прерывание исполнения скрипта.

    Ниже рассмотрим основные разновидности фатальных ошибок:

    1. E_PARSE. Грубый недостаток в синтаксисе. PHP-интерпретатор не понимает, что вы вообще от него хотите. Пример — разработчик забыл закрыть (поставил лишнюю) фигурную либо круглую скобку либо написал код на непонятном интерпретатору языке. Здесь важно понимать следующее: код файла с parse error выполнен не будет, поэтому, если вы захотите включить отображение ошибок в этом же файле, где появилась parse error, такое не сработает.
    2. E_ERROR. Интерпретатор PHP понимает, что хочет разработчик, но выполнить это не может по разным причинам. Выполнение скрипта будет прервано, однако произойдет именно в месте возникновения проблемы, то есть код сработает до того места, где находится ошибка. Примеры:

    — не удалось обнаружить подключаемый файл PHP;

    — было выброшено, но не было обработано исключение;

    — разработчик пытался вызвать метод класса, причем данный метод не существует;

    — отсутствует свободная память (превышен лимит директивы memory_limit).

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

    Не фатальные

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

    Разновидности:

    • E_WARNING. Нередко встречаются, если разработчик подключает файл с использованием include, а данного файла или нет на сервере, или была допущена ошибка при указании пути. Другая причина E_WARNING — использование неправильного типа аргументов при вызове функций. Но вообще причин много — все не перечислишь;
    • E_NOTICE. Распространены наиболее широко. Вдобавок к этому, существуют любители, которые отключают вывод ошибок, в результате чего клепают E_NOTICE просто пачками. Эти errors сами по себе тривиальны:

    — обращение к неопределенной переменной;

    — обращение к элементу массива, когда элемент не существует;

    — обращение к несуществующей константе;

    — проблема, возникающая, если не конвертируются типы данных и т. п.

    Чтобы избежать таких недоработок, надо быть внимательным, особенно к тому, что подсказывает IDE — игнорировать подсказки точно не стоит;

    • E_DEPRECATED. Язык программирования PHP станет ругаться при использовании устаревших функций (т. е. функций, которые помечены в качестве deprecated);
    • E_STRICT. Это тоже история про то, что нужно писать код правильно и обращать внимание на подсказки со стороны IDE, дабы потом не было мучительно больно и стыдно. К примеру, если вы вызовете нестатический метод как статику, код, отображенный ниже, функционировать будет, но ведь это как-то неправильно. Почему? Потому что в дальнейшем возможно появление существенных ошибок, если метод класса изменится, и появится обращение к $this:

    class Strict {

        public function test() {

            echo «It’s test for me. It is not fatal error»;

        }

    }

    Strict::test();   

    Но вообще тип E_STRICT больше актуален для PHP 5.6, поэтому он практически выпилен из 7-й версии языка.

    Пользовательские

    Этот «балаган» разводится самим разработчиком. Злоупотреблять такими errors не рекомендуется:

    • E_USER_WARNING — некритическая ошибка;
    • E_USER_ERROR — критическая;
    • E_USER_NOTICE — речь идет о сообщениях, которые ошибками не являются.

    Отдельно надо сказать про E_USER_DEPRECATED — напоминает о том, что  метод либо функция устарели, то есть пришло время переписать код. Чтобы создать эту и подобные ошибки, применяется функция trigger_error:

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

    • когда если display_errors = on, в случае ошибки веб-браузер получит html c кодом 200 и текстом ошибки;
    • когда display_errors = off, для фатальных ошибок код реквеста будет 500, причем результат не вернется пользователю. Для остальных ошибок программный код будет работать неверно, однако он «никому про это не расскажет».

    Error reporting

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

    <?php

    error_reporting(E_ALL);

    ini_set(‘display_errors’, 1);

    Функция error reporting является встроенной. Она позволяет контролировать, какие именно errors станут отображаться и сообщаться (reported) разработчику. Не стоит забывать и о том, что в PHP ini существует директива error_reporting, причем во время выполнения функция error_reporting() задает значение этой директивы.


    Полезные ссылки на тематические материалы:

    • https://www.php.net/manual/ru/function.error-reporting.php;
    • https://www.netangels.pro/article/php-errors/;
    • https://habr.com/ru/post/440744/;
    • https://www.karashchuk.com/PHP/error_reporting-display_errors-display_startup_errors/.

    Антон Шевчук // 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. Спасибо Максиму Слесаренко за помощь в написании статьи

    Фатальная ошибка — это тип ошибки, при которой компьютерная программа закрывается или вся операционная система внезапно закрывается. Этот тип ошибки обычно ассоциируется с синим экраном смерти в Windows, но менее серьезные фатальные ошибки исключения вызывают закрытие только одной программы.

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

    Как появляются фатальные ошибки

    Сообщения о фатальных ошибках обычно появляются, когда программа внезапно завершает работу из-за сбоя какого-либо типа, или когда компьютер Windows внезапно отображает синий экран смерти перед выключением, или когда компьютер MacOS или Linux испытывает панику ядра.

    vm / E + / Getty Images

    При возникновении фатальной ошибки появляется сообщение, подобное одному из следующих:

    • ФАТАЛЬНАЯ ОШИБКА: необработанное исключение xxx в xxx
    • Произошло фатальное исключение xx в xxxx: xxxxxxxx
    • Обнаружена критическая ошибка, продолжение невозможно. Завершение из-за неперехваченного исключения.

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

    Что вызывает фатальную ошибку?

    Когда вы запускаете программу в такой операционной системе, как Windows, и программа обнаруживает что-то неожиданное, она генерирует сообщение, называемое исключением. Эти исключения позволяют программам работать бесперебойно и нормально, даже когда происходит что-то неожиданное.

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

    В зависимости от серьезности ошибки вам может быть предложено продолжить выполнение программы или программа может завершиться автоматически.

    Как исправить фатальную ошибку

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

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

    1. Найдите код ошибки, чтобы найти конкретные инструкции. Некоторые фатальные ошибки довольно просты, но большинство сообщений об ошибках содержат код, который может помочь вам найти конкретную информацию о проблеме. Если ошибка выглядит так:

      • Произошло фатальное исключение 0E в xxxx: xxxxxxxx

      Это 0E это код, который может указать вам правильное направление. Выполните поиск по конкретному коду фатальной ошибки исключения и посмотрите, сможете ли вы найти конкретные инструкции.

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

    2. Обновите программное обеспечение. Возможно, разработчики выпустили патч, предназначенный для решения конкретной проблемы. Большинство программ и приложений либо загружают и устанавливают обновления автоматически, либо предоставляют вам возможность вручную загружать и устанавливать обновления, либо предлагают вам посетить веб-сайт разработчика и загрузить обновление.

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

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

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

    4. Удалите все недавно установленные программы. Между программами может возникнуть непредвиденный конфликт или что-то могло быть повреждено в процессе установки. После удаления недавно установленных программ проверьте, не возникает ли по-прежнему фатальная ошибка исключения. Если этого не произошло, переустановите программы. Если проблема возвращается, отправьте отчет об ошибке разработчику.

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

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

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

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

    8. Освободите место на жестком диске. Проверьте, сколько места на жестком диске, и удалите старые файлы, если диск заполнен. Для бесперебойной работы оставьте около 10% от общего объема памяти.

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

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

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

      Если вы используете баллон со сжатым воздухом, держите баллончик вертикально, чтобы жидкость не попала внутрь компьютера. Не прикасайтесь к компонентам внутри компьютера, если на вас не надет заземляющий браслет. Если вам неудобно ковыряться в компьютере или ноутбуке, обратитесь за профессиональной помощью.

    11. Проверьте оперативную память на наличие проблем. Ошибки памяти вызывают фатальные ошибки, хотя вместо этого вы можете увидеть исключение памяти или ошибку исключения нехватки памяти. Запустите приложение для проверки памяти. Если вы обнаружите, что RAM неисправна, замените неисправный компонент или компоненты.

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

      Этот уровень диагностики сложен, поэтому вам, возможно, придется обратиться за профессиональной помощью.

    Понравилась статья? Поделить с друзьями:
  • Виды управленческих ошибок
  • Виды текстовых ошибок варианты правки
  • Виды реестровых ошибок и причины их возникновения
  • Виды реестровых ошибок земельных участков
  • Виды стилистических ошибок таблица