Как найти строку ошибки php

Вчера всё работало, а сегодня не работает / Код не работает как задумано

или

Debugging (Отладка)


В чем заключается процесс отладки? Что это такое?

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


Важное замечание:

Есть много IDE и редакторов кода, которые позволяют производить отладку. Процесс настройки в них у всех различается. Поэтому стОит обратиться к документации по настройке отладки для непосредственно той среды разработки и той версии, в которой работаете именно ВЫ.

На текущий момент будет рассмотрен пример с PHPStorm 2017.


Подготовка

Для начала необходимо, чтобы в PHP имелась библиотека для отладки под названием xdebug. Если её еще нет, то надо установить.

ВАЖНО! Для очень новых версий PHP (например 8), требуется и новый xdebug, а он, в свою очередь, работает на порту 9003. Не пропустите указание правильного порта в IDE!! (Примерно в разделе PHP -> Debug -> Debug Port . Где точно – зависит от конкретной IDE)

Для WINDOWS:

скачать dll, например на xdebug.org.

Обычно все библиотеки лежат в папке ext внутри папки PHP. Туда и надо поместить dll.

Далее в php.ini прописываем настройки:

[Xdebug]
zend_extension="C:/server/php/ext/php_xdebug.dll" // <!-- тут свой путь до dll!!! Это для среды Windows. 
; Для Linux путь должен быть что-то типа zend_extension=/usr/lib/php/20151012/xdebug.so 
xdebug.default_enable = 1
xdebug.remote_enable = 1
xdebug.remote_handler = "dbgp"
xdebug.remote_host = "localhost"
xdebug.remote_port = 9000
xdebug.auto_trace = 0

Перезагружаем сервер, на всякий случай.

Для UBUNTU:

  • sudo apt update ИЛИ sudo apt-get update

  • sudo apt install php-xdebug или если нужнен отладчик для конкретной версии PHP, то sudo apt install php7.0-xdebug где 7.0 указывается версия PHP

  • sudo nano /etc/php/7.0/mods-available/xdebug.ini

    вписываем строки:

     zend_extension=/usr/lib/php/20151012/xdebug.so
     xdebug.remote_autostart = 1
     xdebug.remote_enable = 1
     xdebug.remote_handler = dbgp
     xdebug.remote_host = 127.0.0.1
     xdebug.remote_log = /tmp/xdebug_remote.log
     xdebug.remote_mode = req
    

    Примечание: каталог 20151012, скорее всего, будет другим. cd в /usr/lib/php и проверьте, в каком каталоге в этом формате находится файл xdebug.so, и используйте этот путь. 7.0 – тоже отличается, в зависимости от того, какая версия у вас используется

  • Перезагружаем сервер, на всякий случай.


Теперь если в файле .php написать phpinfo(); то можно будет увидеть в самом низу такую картину:

введите сюда описание изображения

Открываем PHPStorm

  • нажимаем create project from existing files
  • выбираем Web server is installed locally, source files are located under its document root
  • выбираем папку с файлами, и нажав вверху кнопку “Project Root” помечаем папку как корень проекта
  • нажимаем “Next”
  • нажимаем Add new local server

введите сюда описание изображения

  • вводим имя сервера любое и Web Server root URL. В рассматриваемом примере это http://localhost/testy2

введите сюда описание изображения

  • нажимаем “Next” и затем “Finish”

Запуск

Для начала в левой части панели с кодом на любой строке можно кликнуть ЛКМ, тем самым поставив точку останова (breakpoint – брейкпойнт). Это то место, где отладчик автоматически остановит выполнение PHP, как только до него дойдёт. Количество breakpoint’ов не ограничено. Можно ставить везде и много.

введите сюда описание изображения

Если кликнуть ПКМ и во всплывающем меню выбрать Debug (или в верхнем меню – RunDebug), то при первом запуске PHPStorm попросит настроить интерпретатор. Т.е. надо выбрать версию PHP из папки, где он лежит, чтобы шторм знал, какую версию он будет отлаживать.

введите сюда описание изображения

Теперь можно нажать Debug!!!

В данном случае, т.к. функция вызывается сразу на той же странице, то при нажатии кнопки Debug — отладчик моментально вызовет функцию, выполнение “заморозится” на первом же брейкпойнте. В ином случае, для активации требуется исполнить действие, при котором произойдет исполнение нужного участка кода (клик на кнопку, передача POST-запроса с формы с данными и другие действия).

введите сюда описание изображения

Цифрами обозначены:

  1. Стэк вызовов, все вложенные вызовы, которые привели к текущему месту кода.
  2. Переменные. На текущий момент строки ниже номера 3 ещё не выполнились, поэтому определена лишь $data
  3. Показывает текущие значения любых переменных и выражений. В любой момент здесь можно нажать на +, вписать имя любой переменной и посмотреть её значение в реальном времени. Например: $data или $nums[0], а можно и $nums[i] и item['test']['data'][$name[5]][$info[$key[1]]] и т.д. На текущий момент строки ниже номера 3 ещё не выполнились, поэтому $sum и $output обозначены красным цветом с надписью “cannot evaluate expression”.

Процесс

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

введите сюда описание изображения

Show Execution Point (Alt+F10) — переносит в файл и текущую линию отлаживаемого скрипта. Например, если файлов много, решили посмотреть что в других вкладках, а потом забыли где у вас отладка 🙂

Step Over (F8) — делает один шаг, не заходя внутрь функции. Т.е. если на текущей линии есть какая-то функция, а не просто переменная со значением, то при клике данной кнопки, отладчик не будет заходить внутрь неё.

Step Into (F7) — делает шаг. Но в отличие от предыдущей, если есть вложенный вызов (например функция), то заходит внутрь неё.

Step Out (Shift+F8) — выполняет команды до завершения текущей функции. Удобно, если случайно вошли во вложенный вызов и нужно быстро из него выйти, не завершая при этом отладку.

Rerun (Ctrl+F5) — перезапускает отладку.

Resume Program(F9) — продолжает выполнение скрипта с текущего момента. Если больше нет других точек останова, то отладка заканчивается и скрипт продолжает работу. В ином случае работа прерывается на следующей точке останова.

Stop (Ctrl+F2) — завершает отладку.

View Breakpoints (Ctrl+Shift+F8) — просмотр всех установленных брейкпойнтов.

Mute Breakpoints — отключает брейкпойнты.

Итак, в текущем коде видно значение входного параметра:

  • $data = "23 24 11 18" — строка с данными через пробел
  • $nums = (4) ["23", "24", "11", "18"] — массив, который получился из входной переменной.

введите сюда описание изображения

Если нажмем F8 2 раза, то окажемся на строке 7; во вкладках Watches и Variables и в самой странице с кодом увидим, что переменная $sum была инициализирована и её значение равно 0.

Если теперь нажмем F8, то попадем внутрь цикла foreach и, нажимая теперь F8, пока не окончится цикл, можно будет наблюдать на каждой итерации, как значения $num и $sum постоянно изменяются. Тем самым мы можем проследить шаг за шагом весь процесс изменения любых переменных и значений на любом этапе, который интересует.

Дальнейшие нажатия F8 переместят линию кода на строки 11, 12 и, наконец, 15.


Дополнительно

Если нажать на View Breakpoints в левой панели, то можно не только посмотреть все брейкпойнты, но в появившемся окне можно еще более тонко настроить условие, при котором на данной отметке надо остановиться.
В функции выше, например, нужно остановиться только когда $sum превысит значение 20.

введите сюда описание изображения

Это удобно, если останов нужен только при определённом значении, а не всегда (особенно в случае с циклами).

(PHP 7, PHP 8)

Error::getLineПолучает номер строки, в которой произошла ошибка

Описание

final public Error::getLine(): int

Список параметров

У этой функции нет параметров.

Возвращаемые значения

Возвращает номер строки, в которой произошла ошибка.

Примеры

Пример #1 Пример использованияError::getLine()


<?php
try {
throw new
Error("Какое-то сообщение об ошибке");
} catch(
Error $e) {
echo
"Ошибка создана в строке: " . $e->getLine();
}
?>

Результатом выполнения данного примера
будет что-то подобное:

Ошибка создана в строке: 3

There are no user contributed notes for this page.

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

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

  • Веб-серверу может не хватить места на диске;
  • Пользователь мог ввести недопустимое значение в поле формы;
  • Файл или запись базы данных, к которой вы пытались получить доступ, возможно, не существует;
  • Приложение может не иметь разрешения на запись в файл на диске;
  • Служба, к которой приложение должно получить доступ, может быть временно недоступна.

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

Профессиональное приложение должно иметь возможность изящно обрабатывать такие ошибки времени выполнения. Обычно это означает более четкое и точное информирование пользователя о проблеме.

Понимание уровней ошибок

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

Название Значение Описание
E_ERROR 1 Неустранимая ошибка времени выполнения от которой невозможно избавиться. Выполнение скрипта немедленно прекращается.
E_WARNING 2 Предупреждение во время выполнения. Она несущественна, и большинство ошибок попадают в эту категорию. Выполнение скрипта не останавливается.
E_NOTICE 8 Уведомление во время выполнения. Указывает, что скрипт обнаружил что-то, что могло быть ошибкой, хотя такая ситуация также может возникнуть при обычном запуске скрипта.
E_USER_ERROR 256 Сообщение о фатальной пользовательской ошибке. Она похожа на E_ERROR, за исключением того, что она генерируется PHP-скриптом с использованием функции trigger_error().
E_USER_WARNING 512 Предупреждающее сообщение, созданное пользователем без фатального исхода. Она похожа на E_WARNING, за исключением того, что она генерируется PHP-скриптом с использованием функции trigger_error().
E_USER_NOTICE 1024 Сообщение с уведомлением, созданное пользователем. Она похожа на E_NOTICE за исключением того, что она генерируется PHP-скриптом с использованием функции trigger_error().
E_STRICT 2048 Не совсем ошибка, но срабатывает всякий раз, когда PHP встречает код, который может привести к проблемам или несовместимости пересылки.
E_ALL 8191 Все ошибки и предупреждения, кроме E_STRICT до PHP 5.4.0.

Дополнительные сведения об уровнях ошибок см. в справочнике по уровням ошибок PHP.

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

Базовая обработка ошибок с помощью функции die()

Рассмотрим следующий пример, в котором просто попытаемся открыть текстовый файл только для чтения.

<?php
// Пробуем открыть несуществующий файл
$file = fopen("sample.txt", "r"); // Выводит: Warning: fopen(sample.txt) [function.fopen]: failed to open stream: No such file or directory in C:wampwwwprojecttest.php on line 2
?>

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

<?php
if(file_exists("sample.txt")){
    $file = fopen("sample.txt", "r");
} else{
    die("Error: The file you are trying to access doesn't exist.");
}
?>

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

Используемая выше функция die() просто отображает пользовательское сообщение об ошибке и завершает текущий скрипт, если файл sample.txt не найден.

Создание собственного обработчика ошибок

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

Функция пользовательского обработчика ошибок должна иметь возможность обрабатывать как минимум два параметра (errno и errstr), однако она может дополнительно принимать три дополнительных параметра (errfile, errline и errcontext), как описано ниже:

Параметр Описание
Обязательно — следующие параметры обязательны
errno. Задает уровень ошибки в виде целого числа. Это соответствует соответствующей константе уровня ошибки (E_ERROR, E_WARNING и т. д.).
errstr. Задает сообщение об ошибке в виде строки.
Опционально — следующие параметры являются необязательными
errfile. Задает имя файла скрипта, в котором произошла ошибка.
errline. Задает номер строки, в которой произошла ошибка.
errcontext. Задает массив, содержащий все переменные и их значения, которые существовали на момент возникновения ошибки. Полезно для отладки.

Вот пример простой пользовательской функции обработки ошибок. Этот обработчик customError() запускается всякий раз, когда возникает ошибка, какой бы тривиальной она ни была. Затем он выводит сведения об ошибке в браузер и останавливает выполнение скрипта.

<?php
// Функция обработчика ошибок
function customError($errno, $errstr){
    echo "<b>Error:</b> [$errno] $errstr";
}
?>

Вам нужно указать PHP, чтобы он использовал вашу пользовательскую функцию обработчика ошибок — просто вызовите встроенную функцию set_error_handler(), передав имя функции.

<?php
// Функция обработчика ошибок
function customError($errno, $errstr){
    echo "<b>Error:</b> [$errno] $errstr";
}
 
// Устанавливаем обработчик ошибок
set_error_handler("customError");
 
// Вызываем ошибку
echo($test);
?>

Регистрация ошибок

Журнал сообщений об ошибках в текстовом файле

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

<?php
function calcDivision($dividend, $divisor){
    if($divisor == 0){
        trigger_error("calcDivision(): The divisor cannot be zero", E_USER_WARNING);
        return false;
    } else{
        return($dividend / $divisor);
    }
}
function customError($errno, $errstr, $errfile, $errline, $errcontext){
    $message = date("Y-m-d H:i:s - ");
    $message .= "Error: [" . $errno ."], " . "$errstr in $errfile on line $errline, ";
    $message .= "Variables:" . print_r($errcontext, true) . "rn";
    
    error_log($message, 3, "logs/app_errors.log");
    die("There was a problem, please try again.");
}
set_error_handler("customError");
echo calcDivision(10, 0);
echo "This will never be printed.";
?>

Отправка сообщений об ошибках по электронной почте

Вы также можете отправить электронное письмо с подробностями об ошибке, используя ту же функцию error_log().

<?php
function calcDivision($dividend, $divisor){
    if ($divisor == 0){
        trigger_error("calcDivision(): The divisor cannot be zero", E_USER_WARNING);
        return false;
    } else{
        return($dividend / $divisor);
    }
}
function customError($errno, $errstr, $errfile, $errline, $errcontext){
    $message = date("Y-m-d H:i:s - ");
    $message .= "Error: [" . $errno ."], " . "$errstr in $errfile on line $errline, ";
    $message .= "Variables:" . print_r($errcontext, true) . "rn";
    
    error_log($message, 1, "webmaster@example.com");
    die("There was a problem, please try again. Error report submitted to webmaster.");
}
set_error_handler("customError");
echo calcDivision(10, 0);
echo "This will never be printed.";
?>

Вызов ошибок

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

Чтобы вызвать ошибку в скрипте, вызовите функцию trigger_error(), передав сообщение об ошибке, которое вы хотите сгенерировать:

trigger_error("There was a problem.");

Рассмотрим следующую функцию, которая вычисляет деление двух чисел.

<?php
function calcDivision($dividend, $divisor){
    return($dividend / $divisor);
}
 
// Вызываем функцию
echo calcDivision(10, 0); // Выводит: Warning: Division by zero in C:wampwwwprojecttest.php on line 3
?>

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

<?php
function calcDivision($dividend, $divisor){
    if($divisor == 0){
        trigger_error("Делитель не может быть нулевым", E_USER_WARNING);
        return false;
    } else{
        return($dividend / $divisor);
    }
}
 
// Вызываем функцию
echo calcDivision(10, 0); // Выводит: Warning: Делитель не может быть нулевым C:wampwwwprojecterror.php on line 4
?>

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

Курс PHP для начинающих

  • PHP и MySQL
  • Основы PHP
  • Нарушения в работе системы PHP

Внимание! Данный курс устарел!
Переходите к новому курсу “PHP для начинающих”.

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

Курс PHP для начинающих

Обработка и отладка ошибок

В процессе функционирования интерпретатора PHP вырабатываются ошибки нескольких типов, которые классифицируются по степени серьезности. Четыре наиболее распространенных типа ошибок описаны ниже. Дополнительные сведения по этой теме приведены по адресу www.php.net/error_reporting.

Извещение (Notice)

Ошибки такого типа не слишком серьезны и не создают существенных проблем. По умолчанию эти ошибки подавляются. Их появление становится возможным только после изменения степени серьезности регистрируемых в журнале ошибок в файле php.ini.

Устаревшие функции и конструкции (Deprecated)

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

Ошибка использования устаревших функций и конструкций
Предупреждение (Warning)

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

Предупреждение
Неисправимая ошибка (Fatal Error)

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

Неисправимая ошибка

Кроме того, ошибка каждого типа представлена с помощью константы, на которую можно ссылаться в коде: E_USER_NOTICE (извещение), E_USER_WARNING (предупреждение), E_USER_DEPRECATED (устаревшие функции и конструкции) и E_USER_ERROR (неисправимая ошибка). Степень серьезности регистрируемых ошибок можно задать в сценарии вручную, с помощью функции error_reporting(), как показано в следующих примерах:

Код PHP

// Формировать сообщения только о неисправимых ошибках
error_reporting(E_USER_ERROR);

// Формировать предупреждающие сообщения и сообщения о неисправимых ошибках
error_reporting(E_USER_WARNING | E_USER_ERROR);

// Формировать все сообщения, в том числе извещения и сообщения E_STRICT
error_reporting(E_ALL);

// Не показывать никакие сообщения
error_reporting(0);

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

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

Определение обработчика ошибок

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

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

Код PHP

<?php

function error_msg($err_type, $err_msg, $err_file, $err_line)
{
	static $count = 0;
	$count++;
	
	echo "<div style="width:32px; height:32px; background:url(error.png); float:left; margin:0 12px 12px 0;"></div>"
	     ."<b>Ошибка №$count:</b><p>Извините, но на этой странице возникла ошибка. "
	     ."Пожалуйста, отправьте следующее сообщение администратору сайта на странице <a href='help.html'>help</a>.</p>"
		 ."<p>Тип ошибки: <em>$err_type</em>, сообщение: <em>$err_msg</em>, файл: <em>$err_file</em>, номер строки: <em>$err_line</em>"
		 ."<hr color='red'>";
}

// Регистрируем нашу функцию в качестве обработчика ошибок
set_error_handler("error_msg");

// Генерируем ошибку, чтобы проверить вызывается ли функция error_msg
include 'undefined.php';

?>

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

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

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

Инициирование пользовательской ошибки вручную

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

Код PHP

trigger_error("Тестируем сообщение уровня 'Notice'", E_USER_NOTICE);

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

Ведение журнала и отладка

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

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

Код PHP

function error_msg($type, $msg, $file, $line)
{
	// Записать в журнал ошибок
	$log_msg = "Тип ошибки: $type, сообщение: $msg, файл: $file, номер строки: $line, время возникновения: ".time();
	$log_path = "tmp/php_errors.log";
	error_log($log_msg, 3, $log_path);
}

set_error_handler("error_msg");

trigger_error("Тестируем сообщение уровня 'Notice'", E_USER_NOTICE);

Функция error_log() принимает в качестве второго параметра одно из четырех целых чисел, описание которых приведено ниже. Этот параметр задает тип сообщения в сочетании с третьим параметром, который указывает на местонахождение объекта — получателя сообщения об ошибке:

  • 0 — сообщение, регистрируемое с использованием общесистемного механизма ведения журналов операционной системы.

  • 1 — сообщение об ошибке, передаваемое по указанному адресу электронной почты (в качестве четвертого параметра можно ввести дополнительные заголовки).

  • 2 — сообщение об ошибке, передаваемое через отладочное соединение PHP (должна быть разрешена дистанционная отладка).

  • 3 — сообщение об ошибке, добавляемое в конец указанного файла журнала ошибок.

Курс PHP для начинающих

Ошибки, обнаруживаемые интерпретатором PHP

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

Проблемы, связанные с инсталляцией PHP

Я не стану читать мораль на тему, что не следует одним махом проходить все этапы инсталляции, не разобравшись в документации, а приведу описание нескольких часто встречающихся признаков нарушения в работе, которые обычно появляются после того, как инсталляция PHP выполняется впервые. Если вы устанавливали PHP не вручную, а например через WAMP-сервер, то приведенные ниже проблемы скорее всего у вас не возникнут.

Признак нарушения в работе: в окне браузера отображается текст файла

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

https://localhostpre>

а не следующий, например:

file://home/httpd/htmlpre>

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

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

Признак нарушения в работе - не найден сервер или хост либо страница не могут быть отображены

Если браузер не может найти используемый сервер, то, по-видимому, имеет место нарушение конфигурации DNS (Domain Name Service — служба доменных имен) или веб-сервера. Если доступ к сайту может быть получен с помощью IP-адреса (например, https://127.0.0.1p>

Если невозможно получить доступ к сайту с помощью IP-адреса после выполнения новой инсталляции, то, по всей видимости, пользователь не смог успешно выполнить привязку IP-адреса к сетевому интерфейсу или настроить конфигурацию домена httpd для обработки запросов, относящихся к конкретному домену. А если доступ к сайту не может быть получен с помощью IP-адреса, притом что применяемая инсталляция уже успешно работала, то наиболее вероятное объяснение состоит в том, что веб-сервер остановлен или недоступен по причине, не относящейся к PHP.

Проблемы формирования страницы

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

Признак нарушения в работе - полностью пустая страница

Появление пустой страницы может быть вызвано самыми разнообразными причинами. Одной из наиболее распространенных становится неисправимая ошибка в коде PHP, после обнаружения которой интерпретатор PHP не может возобновить нормальную работу. Начинайте отладку с верхней части файла PHP, который вы пытаетесь открыть в браузере, поместив вызов функции die() после открывающего дескриптора <?php:

Код PHP
<?php
    die(print "hello");
    ...

Если после обновления страницы вы увидите в браузере слово hello, это означает, что проблемы с веб-сервером и самим модулем PHP исключены. Продолжайте перемещать оператор вызова функции die() в коде PHP дальше, до тех пор, пока снова не появится ошибка, выражающаяся в появлении пустой страницы. Не забывайте, что неудачное завершение сценария может также происходить в результате обработки любых файлов, включенных с помощью require, require_once, include и тому подобных конструкций. Если после ввода оператора вызова функции die() непосредственно перед включенным файлом сценарий работает, а вслед за перемещением оператора вызова функции die() непосредственно после включенного файла перестает работать, то можно сделать вывод, что нарушение в работе возникает из-за включенного файла.

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

Наконец, пустое окно браузера обнаруживается в тех ситуациях, когда интерпретатор PHP сталкивается с достаточно серьезной ошибкой, а средства формирования сообщений об ошибках отключены. По-видимому, средства вывода сообщений об ошибках следует отключать на производственных серверах по соображениям безопасности, но на серверах, применяемых для разработки, средства вывода сообщений об ошибках в браузер оказывают огромную помощь. Проверьте в файле php.ini параметр display_errors и убедитесь в том, что все необходимые параметры заданы правильно. Если же пользователь действительно отвергает возможность вывода сообщений об ошибках в окно браузера, то ему придется широко использовать в составе средств обработки исключении функцию error_log() как показано выше.

Признак нарушения в работе - в окне веб-браузера обнаруживается код PHP

Если в окне браузера обнаруживается в буквальном виде код PHP, а не развертывается код HTML, который должен быть сформирован в этом фрагменте PHP, то, по-видимому, где-то пропущен начальный дескриптор PHP. (При этом предполагается, что интерпретатор PHP функционирует успешно, а в используемой инсталляции предусмотрены правильные форматы дескрипторов PHP.)

Можно легко забыть, что интерпретатор PHP рассматривает включаемые файлы как представленные в коде HTML, а не в коде PHP, если пользователь не укажет интерпретатору иное с помощью открывающего дескриптора в начале файла.

Ошибки при загрузке страницы

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

Признак нарушения в работе — страница не может быть найдена

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

Признак нарушения в работе - сообщение Failed opening [file] for inclusion (He удалось открыть [файл] для включения)

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

Warning Failed opening 'C:InetPubwwwrootasdf.php' for inclusion (ihclude_path='') in [no active file] on line 0

Можно считать, что это сообщение представляет собой вариант сообщения Page cannot be found (He удается найти страницу), относящийся к включаемым файлам. Появление этого сообщения свидетельствует о том, что интерпретатору PHP не удалось загрузить даже первую строку активизированного файла. Активизированный файл отсутствует, поскольку не удается найти файл с указанным именем.

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

Ошибки интерпретации

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

Признак нарушения в работе — сообщение об ошибке интерпретации (Parse error)

Безусловно, количество причин возникновения проблем при интерпретации велико, но признак этого нарушения в работе почти всегда остается одинаковым — сообщение об ошибке интерпретации, Parse error:

Сообщение об ошибке интерпретации (Parse error)

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

Отсутствие точки с запятой

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

Код PHP

$count = 2
$level = 4

Отсутствие знаков доллара

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

Код PHP

count = 2;

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

Код PHP

$count = 2;

echo "Количество: count";

то интерпретатор PHP не выводит сообщение об ошибке интерпретации. Вместо этого в окне отображается строка "Количество: count".

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

Если вам приходится затрачивать значительную часть времени на отладку кода PHP, то для вас может оказаться буквально бесценным редактор текста, позволяющий переходить к строкам с указанными номерами и подсвечивающий все синтаксические ошибки. Я, например, использую удобную IDE Adobe Dreamweaver, которая динамически подсвечивает синтаксические ошибки, а также содержит нумерацию строк, чтобы можно было легко найти другие виды ошибок, которые отображаются в окне браузера:

Подсветка ошибки в Adobe Dreamweaver

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

Проблемы, связанные со сменой режима

Еще одна разновидность нарушений в работе возникает в связи с неправильным переходом в режим PHP и из режима PHP. Если блок PHP не закрыт должным образом, как показано ниже, то возникает ошибка интерпретации:

Код PHP

<?php

$count = 2
$level = 4;

</body>
</html>

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

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

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

Код PHP

// Неправильная строка
echo "Он сказал, - "Что нам делать?"";

// Правильная строка
echo "Он сказал, - "Что нам делать?"";

Другие причины ошибок интерпретации

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

Проблемы, связанные с использованием функций

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

Признак нарушения в работе - сообщение Call to undefined function my_function()

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

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

Признак нарушения в работе - сообщение call to undefined function ()

В данном случае интерпретатор PHP пытается вызвать некоторую функцию, но не имеет даже возможности определить ее имя. Такая ситуация возникает исключительно в тех случаях, если в коде применяется выражение в форме $my_function(), где само имя функции показано как переменная. Это означает, что разработчик, скорее всего, случайно поместил знак $ перед выражением вызова функции my_function(), имеющим смысл, или ошибся, специально используя средство задания имени функции с помощью переменной языка PHP. А поскольку $my_function представляет собой переменную с незаданным значением, то интерпретатор PHP подставляет в качестве значения этой переменной пустую строку (которая не может служить в качестве имени определенной функции), после чего выдает приведенное в названии этого подраздела маловыразительное сообщение об ошибке.

Признак нарушения в работе - сообщение Cannot redeclare my_function()

Причина этой проблемы проста — где-то в используемом коде имеется повторно заданное определение функции my_function(), а такая ситуация в языке PHP является недопустимой. Убедитесь в том, что в коде не применяется конструкция include для включения одного и того же файла с определениями функций больше одного раза. Чтобы предотвратить возникновение такой ошибки, необходимо использовать конструкцию include_once или require_once, но с учетом того предостережения, что ошибка, связанная с повторным включением, при этом не устраняется, а просто перестает обнаруживаться.

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

Признак нарушения в работе - сообщение Wrong parameter count

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

Обработка исключений

Отладка программ

Оценить статью:

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