Вчера всё работало, а сегодня не работает / Код не работает как задумано
или
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
(или в верхнем меню – Run
→ Debug
), то при первом запуске PHPStorm попросит настроить интерпретатор. Т.е. надо выбрать версию PHP из папки, где он лежит, чтобы шторм знал, какую версию он будет отлаживать.
Теперь можно нажать Debug
!!!
В данном случае, т.к. функция вызывается сразу на той же странице, то при нажатии кнопки Debug
— отладчик моментально вызовет функцию, выполнение “заморозится” на первом же брейкпойнте. В ином случае, для активации требуется исполнить действие, при котором произойдет исполнение нужного участка кода (клик на кнопку, передача POST-запроса с формы с данными и другие действия).
Цифрами обозначены:
- Стэк вызовов, все вложенные вызовы, которые привели к текущему месту кода.
- Переменные. На текущий момент строки ниже номера 3 ещё не выполнились, поэтому определена лишь
$data
- Показывает текущие значения любых переменных и выражений. В любой момент здесь можно нажать на
+
, вписать имя любой переменной и посмотреть её значение в реальном времени. Например:$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, даже самый простой, время от времени в его скриптах могут возникать ошибки. Это может привести к различным неприятным последствиям, от некорректной работы некоторых компонентов сайта (например, формы обратной связи), до недоступности сайта целиком. Как самостоятельно распознать тип ошибки PHP и понять, что с ней делать дальше?
Этот материал поможет вам, во-первых, самостоятельно оценить ситуацию (и, возможно, даже решить ее), а во-вторых, точно ускорит диагностику и решение проблемы при обращении в службу поддержки. Самые ценные советы по устранению наиболее частых ошибок PHP, связанных с лимитами оперативной памяти, вы найдете в конце статьи.
Как обнаружить ошибку PHP на сайте
1. Встроенными средствами браузера
Итак, если на сайте вместо привычной страницы ничего не отображается (вы видите “пустую страницу”), то, вероятнее всего, в одном из скриптов возникла ошибка. В этом можно убедиться, воспользовавшись встроенными «Инструментами разработчика» вашего браузера. В каждом браузере они могут называться немного по-разному, но суть от этого не меняется.
Например, в браузере Google Chrome это вкладка Dev Tools (или «Инструменты разработчика»). В Mozilla Firefox — это расширение Firebug (его нужно установить отдельно в меню Adds On) или же вкладка Developer.
Внутри «Инструментов разработчика» нас интересует вкладка, которая называется Network (или Net, или каким-то похожим образом).
Если на странице сайта присутствует ошибка, в этой вкладке вы увидите код ответа 500 (“Internal Server Error”).
2. Если вывод сообщений об ошибках в браузер отключен
Случается, что вывод сообщений об ошибках в браузер отключён. Чтобы сообщение об ошибке отображалось в браузере, достаточно добавить в файл .htaccess в корневой директории сайта следующую строку:
php_value display_errors on
Файл .htaccess вы найдете по адресу: /home/login/domains/domain.ru/public_html/, где вместо login следует подставить логин вашего аккаунта, а вместо domain.ru — домен вашего сайта.
После сохранения файла .htaccess и обновления страницы вы сможете увидеть ошибку.
Если сайтом используется, например, CMS WordPress, то отображение ошибок можно также включить, заменив в файле wp-config.php:
define(‘WP_DEBUG’, false);
на:
define(‘WP_DEBUG’, true);
3. С помощью журнала ошибок PHP
Иногда по различным причинам отображать ошибки в браузере нежелательно. В этом случае лучше всего сохранять их в какой-нибудь файл, например errors.log — журнал ошибок PHP. Для этого достаточно в файле .htaccess добавить следующую строку:
php_value error_log /home/login/domains/domain.ru/log/errors.log
Здесь /home/login/domains/domain.ru/log/errors.log — это полный путь до файла, в который будут записываться ошибки PHP (если файла с таким именем нет, он будет создан автоматически при появлении ошибки).
Теперь, если мы снова зайдем на сайт с ошибкой (либо обновим страницу с ошибкой), то в errors.log будут записаны сообщения об ошибках.
Журнал ошибок PHP можно просмотреть, например, с помощью файлового менеджера в Панели управления, открыв файл errors.log:
Также можно открыть файл с ошибками и нажать кнопку “Включить автообновление”. Таким образом, новые записи в журнале можно просматривать в реальном времени.
Расшифровка ошибок PHP
Как правило, в сообщении об ошибке достаточно подробно указано где именно и при выполнении какой части кода она возникла. Например:
Здесь ошибка заключается в следующем:
Fatal error: Call to undefined function weblizar_get_options() in /home/login/domains/domain.ru/public_html/wp-content/themes/enigma/header.php on line 14
“Вызов неопределенной функции weblizar_get_options() в файле используемой на сайте темы enigma”.
Вероятнее всего, был поврежден один из файлов темы, поэтому можно восстановить только директорию темы ./wp-content/themes/enigma/ , а не всего сайта.
Что делать, в зависимости от типа ошибки PHP
Условно ошибки PHP можно разбить на 4 уровня:
- PARSE ERROR
- FATAL ERROR
- WARNING
- NOTICE
Parse Error
Возникают, если уже на этапе проверки кода интерпретатором PHP найдена ошибка. Чаще всего это синтаксические ошибка (например, пропущенная точка с запятой). Скорее всего, такая ошибка возникла в результате последних внесенных на сайт изменений.
Что делать?
1. Если вы НЕ специалист в PHP, восстановите сайт из последней резервной копии на тот момент, когда сайт работал без ошибок.
2. Если вы специалист и самостоятельно вносили правки в код сайта, вы наверняка сможете отследить синтаксическую ошибку и исправить ее. Но проще все же воспользоваться пунктом 1.
Fatal Error и Warning
Возникают, если при выполнении кода какой-то его участок не может быть выполнен (например, попытка открыть несуществующий файл). Разница между 2-ым и 3-им уровнем в том, что при получении “критической ошибки” (FATAL ERROR) выполнение скрипта завершится, а при получении “предупреждения” (WARNING) — нет.
Что делать?
Восстановите сайт из последней доступной резервной копии на тот момент, когда он работал без ошибок.
Notice
К этому уровню ошибок относятся различные “замечания”, суть которых обычно отображена в тексте ошибки.
Что делать?
Если замечание самостоятельно исправить не получается, обратитесь в службу поддержки или же восстановите сайт из последней доступной резервной копии на тот момент, когда он работал без ошибок.
Частые ошибки PHP и их решение
Fatal Error: Allowed Memory
Означает, что для выполнения какой-либо части кода PHP не хватает выделенной оперативной памяти. При этом лимит памяти ограничен какими-то директивами «изнутри» сайта (то есть где-либо в скриптах сайта, либо директивой memory_limit в файле .htaccess). Чтобы исправить это, измените данный лимит в большую сторону, например, в файле .htaccess.
Для этого найдите в .htaccess такую директиву:
php_value memory_limit 128M
Вместо 128M укажите желаемый размер ограничения. Обратите внимание, что символ «M» (латинская M) указывается слитно со значением.
Помните, что есть максимальные значения памяти, отведенной на выполнение скриптов PHP, предусмотенные вашим тарифом хостинга (например, на тарифах виртуального хостинга это 512 Мб, премиум — 1024 Мб). Уточните эти значения у вашего провайдера, если они не указаны явно.
Fatal Error: Out of memory
То же самое, что и предыдущая ошибка, с той разницей, что достигнут лимит памяти, заданный “снаружи”. Обратите внимание на параметр “Памяти на процесс, Мб, не более“ в условиях пользования нашим сервисом.
Для решения вопроса в данном случае, скорее всего, потребуется либо оптимизация скриптов, чтобы они потребляли меньше памяти, либо разбиение процессов на части. Например, объемную загрузку или выгрузку данных, если она упирается в данный лимит, имеет смысл производить частями.
Также в этом случае мы советуем попробовать отключить акселераторы PHP, если они у вас подключены.
Unable to allocate memory for pool
Сайтам на аккаунте не хватает выделенной на тарифном плане памяти для акселераторов PHP.
Для решения проблемы вы можете отключить использование акселераторов в Панели управления хостингом, в разделе «Управление персональным веб-сервером».
Также, например, можно отключить акселератор APC для определенного сайта, добавив в файл .htaccess корневой директории следующую директиву:
php_value apc.cache_by_default off
Обычно имеет смысл оставлять APC для использования на самом посещаемом из ваших сайтов — это позволит использовать именно на нем преимущества акселератора, не заполняя его память данными с других, менее посещаемых сайтов.
В случаях, когда акселератор объективно необходим для корректной и комфортной работы сайтов и его отключение нежелательно, напишите в службу поддержки.
Мы постараемся предложить возможные варианты решения.
Желаем вам приятной работы!
Антон Шевчук // 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 подсказывает о чём-то – не игнорируйте её:
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');
В моём редакторе подобные функции будут зачёркнуты:
Обрабатываемые
Этот вид, которые разводит сам разработчик кода, я их уже давно не встречал, не рекомендую их вам заводить:
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 это решили исправить, но как обычно завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:
- при возникновении фатальных ошибок типа
E_ERROR
или фатальных ошибок с возможностью обработкиE_RECOVERABLE_ERROR
PHP выбрасывает исключение - эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
- эти исключения наследуют класс Error
- оба класса Exception и Error реализуют интерфейс Throwable
- вы не можете реализовать интерфейс 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. Спасибо Максиму Слесаренко за помощь в написании статьи
Время на прочтение
5 мин
Количество просмотров 4.2K
Если вы владелец сайта и думаете, что на нём нет ошибок, то вы ошибаетесь. Если вы программист и думаете, что некоторые ошибки можно не обрабатывать, то вы тоже ошибаетесь. В обоих случаях речь идёт о Енотисах (E_NOTICE). Далее вы узнаете, почему им нужно уделять внимание и как сделать удобный инструмент для хранения и обработки ошибок.
В PHP есть несколько уровней ошибок. Все знают что с ошибками уровня E_WARNING и, тем более, E_ERROR нужно бороться, исправлять их. А что вам известно о E_NOTICE? Чаще всего такие ошибки игнорируют, к ним, например, относится обращение к несуществующему элементу массива. Ну нет его, значит и не было, значит результат будет пустым и как-нибудь сам обработается, скажут некоторые.
На самом деле, за E_NOTICE может скрываться более серьёзная ошибка, которая только ожидает своего часа, а точнее полуночи субботы, когда все программисты уже спят и никто её сразу не заметит. Разве что ночной покупатель, увидев ошибку оформления заказа.
Хорошо, давайте обрабатывать E_NOTICE! E_NOTICE могут появляться периодически. Например, когда пользователь заполнил часть полей в форме заказа, а скрипт обратился к элементу, которого нет в $_REQUEST.
Может будем перехватывать все ошибки и отправлять на почту в техподдержку? Отправлять на E-mail — хорошая идея, только если на одном хите будет сгенерировано не меньше сотни ошибок, почта будет завалена сообщениями, а почтовый сервер решит, что его DDoS-ят.
Поэтому, действовать будем осторожнее. Для начала отсортируем ошибки по категориям. Скорее всего получится так:
-
Ошибки ядра CMS — теоретически могут появиться, трогать их не будем, ибо ядро.
-
Чужие ошибки (если работает несколько программистов).
-
Свои ошибки.
-
Дубли ошибок — не совсем категория, но их тоже нужно обработать.
Где хранить ошибки, как вести их учёт? Если всё складывать в один лог-файл, то со временем он разрастается. Работать с ним не удобно, так как в результате получаем «портянку» из ошибок, часто с дублями — это хорошо знакомо программистам.
Создаём систему учёта
Поскольку на момент написания статьи по поисковому запросу «php manage errors» вы не найдёте ничего лучше, чем запись в файл или вывод в браузер, то напишем свою систему учёта.
Она должна:
-
отбрасывать дубли, делать ошибки уникальными;
-
позволять работать над ошибками нескольким программистам;
-
группировать ошибки, перемещать между группами.
Цель системы учёта — выделить из общего потока ошибок, те которые нужно обработать вручную. Далее по порядку. Напомню, что перехват ошибок в системе можно настроить в файле /bitrix/.settings.php.
Нужно указать свой класс-обработчик, который должен быть унаследован от ExceptionHandlerLog и реализовывать методы write(), initialize(), а также путь к файлу класса.
При появлении ошибки в обработчике нам доступна информация:
-
текст ошибки c названием файла и строкой;
-
трассировка.
Если получить хеш от пути к файлу, в котором возникла ошибка, номера строки и текста сообщения, то у каждой ошибки будет уникальный идентификатор, который поможет бороться с дублями.
О способах хранения ошибок
Стоит ли дублировать сообщения об ошибках на почту и прочие мессенджеры? Все сообщения — не стоит, потому что на одном хите их может быть более сотни. Как насчёт БД? БД — это хороший выбор, до тех пор пока не возникнет ошибка, препятствующая записи ошибки. Риск выглядит теоретическим, но это возможно.
Преимущества |
Недостатки |
Применение |
|
Файловая система |
– запись файла-ошибки – это одна операция (fwrite(), например); – для чтения ошибок можно использовать текстовый редактор; – файлы можно пересылать, обмениваться, делать общими; |
– нужно написать свой скрипт для вывода списка, управления; |
Может использоваться для учёта всех типов ошибок. |
База данных |
– ошибок может быть много, с подробным описанием; – поиск, добавление, удаление средствами СУБД; – можно добавлять дополнительные поля (комментарии, статус и прочие). |
– нужно написать свой скрипт для вывода списка, управления; – есть риск появления ошибок, которые будут препятствовать записи в БД; – сбор ошибок создаст дополнительную нагрузку на БД сайта; |
Может использоваться для учёта всех типов ошибок. |
|
– сообщений может быть много; – сообщения могут содержать подробный текст ошибок. |
– при большом потоке сообщений об ошибках с ними становится сложно работать; |
Оповещения об ошибках типа E_ERROR, E_WARNING (т.е. единичные случаи). |
Мессенджеры (XMPP) |
– мгновенная доставка; – сообщений может быть много; |
– нужна программа-клиент (Psi, например); – сообщений может быть много, но лучше, если они будут короткими, т.к. большие логи неудобно читать. |
Вывод фактов появления ошибок, без подробного описания. |
SMS – сообщения |
– быстрая доставка; – не нужен интернет. |
– нужно оплачивать; – сообщения должны быть короткими. |
Сообщения о критических ошибках. |
Специальная система хранения логов: Kibana+Elasticsearch |
– просмотр логов; – фильтрация логов; – поиск; – мониторинг и аналитика. |
Это отдельное ПО, которое нужно настраивать, поддерживать и оплачивать вычислительные мощности. |
Логи сохраняются структурировано в формате JSON для взаимодействия по Rest API. Система не приспособлена для добавления новых источников данных, так как тесно связана с Elastic. |
Наш выбор — файловая система. Да, не так красиво, но зато наглядно, быстро и надёжно. Плюс к этому, файлами можно обмениваться и это решает задачу совместной работы.
Какие категории ошибок будут в нашей системе? Предлагаю такие:
-
“новая” – тут всё понятно;
-
“обработанная” – устранена программистом или не представляет вреда;
-
“системная” – ошибка ядра, её не исправляем;
-
“пропущена” – не требует обработки, т.к. создана намеренно, в тестовой среде.
Так как решили использовать файлы, то перечисленные категории – это папки. Изменение категории ошибки – это перемещение файла между папками.
Для борьбы с дублями будем для каждой ошибки создавать хеш, он соответствует названию файла. А в файл помещать подробную информацию – дату/время, трассировку, можно даже комментарии программиста.
Работа над ошибками
Перейдём к алгоритмам. Как будет работать сохранение одной ошибки:
-
перехват в классе обработчике;
-
создание хеша (ID);
-
сохранение подробной информации в файл (в папку “новые”).
Работать с массой файлов не удобно, поэтому напишем простой скрипт, который
-
выводит список категорий (выпадающий список);
-
выводит список ошибок выбранной категории (в виде ссылок на файлы);
-
позволяет переносить ошибку в выбранную категорию.
Тогда действия программиста:
-
выбрать категорию “новые”;
-
кликнуть на ссылку на ошибку;
-
обработать ошибку, переместить её в одну из категорий.
Список ошибок для обработки
Может быть ситуация, когда программист обработал ошибку, но она возникла вновь. В этом случае обработчик ошибок перед сохранением файла должен проверять, нет ли файла с таким же названием в папке “обработанные”. Если файл ошибки там есть, то выходит, что он не обработан и нужно переместить его обратно в “новые”.
А чтобы программист об этом узнал, можно и почту использовать, поскольку такие случаи будут единичными. Другой вариант — выводить на следующем хите всплывающее окно со списком повторяющихся ошибок.
Заключение
Развивая систему, на одном из проектов мы разработали панель управления, которая программно изменяет настройки .settings, способы оповещения программиста и прочие параметры обработки.
Конечно, для обработки даже 10% ошибок E_NOTICE на большом сайте потребуется время. Но работа над ошибками превращается из чистки авгиевых конюшен в прозрачный процесс.
Источник: блог ИНТЕРВОЛГИ.
- PHP и MySQL
- Основы 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 не вручную, а например через 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, вызвана с меньшим или большим количеством фактических параметров по сравнению с тем, на обработку которого она рассчитана. Если задано больше параметров, чем требуется, то никаких затруднений не возникает, но если используется меньше параметров по сравнению с ожидаемым, то возникает ошибка.
Обработка исключений
Отладка программ
Оценить статью: