Ошибки в PHP
Всем известная истина звучит, что не ошибается только тот, кто ничего не делает. В этой статье мы рассмотрим, какие ошибки бывают, а также кратко рассмотрим функцию error reporting, используемую для контроля отображения errors. Но для начала (in first) изучим каждый вид дефектов отдельно, обратив внимание на наиболее распространенные.
Error в PHP: разновидности
Фатальные
Fatal error в PHP — одна из наиболее серьезных проблем. Такие дефекты появляются и при компиляции, и во время работы парсера либо PHP-скрипта. Основной нюанс заключается в том, что происходит прерывание исполнения скрипта.
Ниже рассмотрим основные разновидности фатальных ошибок:
- E_PARSE. Грубый недостаток в синтаксисе. PHP-интерпретатор не понимает, что вы вообще от него хотите. Пример — разработчик забыл закрыть (поставил лишнюю) фигурную либо круглую скобку либо написал код на непонятном интерпретатору языке. Здесь важно понимать следующее: код файла с parse error выполнен не будет, поэтому, если вы захотите включить отображение ошибок в этом же файле, где появилась parse error, такое не сработает.
- E_ERROR. Интерпретатор PHP понимает, что хочет разработчик, но выполнить это не может по разным причинам. Выполнение скрипта будет прервано, однако произойдет именно в месте возникновения проблемы, то есть код сработает до того места, где находится ошибка. Примеры:
— не удалось обнаружить подключаемый файл PHP;
— было выброшено, но не было обработано исключение;
— разработчик пытался вызвать метод класса, причем данный метод не существует;
— отсутствует свободная память (превышен лимит директивы memory_limit).
Нередко проблема возникает во время чтения либо загрузки больших файлов, поэтому надо быть особенно внимательным в вопросах потребляемой памяти.
Не фатальные
В данном случае выполнение скрипта не прерывается, однако именно эти дефекты часто находят тестировщики программного обеспечения. И именно эти недоработки доставляют наибольшие хлопоты начинающим программистам на PHP.
- E_WARNING. Нередко встречаются, если разработчик подключает файл с использованием include, а данного файла или нет на сервере, или была допущена ошибка при указании пути. Другая причина E_WARNING — использование неправильного типа аргументов при вызове функций. Но вообще причин много — все не перечислишь;
- E_NOTICE. Распространены наиболее широко. Вдобавок к этому, существуют любители, которые отключают вывод ошибок, в результате чего клепают E_NOTICE просто пачками. Эти errors сами по себе тривиальны:
— обращение к неопределенной переменной;
— обращение к элементу массива, когда элемент не существует;
— обращение к несуществующей константе;
— проблема, возникающая, если не конвертируются типы данных и т. п.
Чтобы избежать таких недоработок, надо быть внимательным, особенно к тому, что подсказывает IDE — игнорировать подсказки точно не стоит;
- E_DEPRECATED. Язык программирования PHP станет ругаться при использовании устаревших функций (т. е. функций, которые помечены в качестве deprecated);
- E_STRICT. Это тоже история про то, что нужно писать код правильно и обращать внимание на подсказки со стороны IDE, дабы потом не было мучительно больно и стыдно. К примеру, если вы вызовете нестатический метод как статику, код, отображенный ниже, функционировать будет, но ведь это как-то неправильно. Почему? Потому что в дальнейшем возможно появление существенных ошибок, если метод класса изменится, и появится обращение к $this:
echo «It’s test for me. It is not fatal error»;
Но вообще тип E_STRICT больше актуален для PHP 5.6, поэтому он практически выпилен из 7-й версии языка.
Пользовательские
Этот «балаган» разводится самим разработчиком. Злоупотреблять такими errors не рекомендуется:
- E_USER_WARNING — некритическая ошибка;
- E_USER_ERROR — критическая;
- E_USER_NOTICE — речь идет о сообщениях, которые ошибками не являются.
Отдельно надо сказать про E_USER_DEPRECATED — напоминает о том, что метод либо функция устарели, то есть пришло время переписать код. Чтобы создать эту и подобные ошибки, применяется функция trigger_error:
Раз основные разновидности проблем уже были рассмотрены, пришло время дать пояснение относительно работы директивы display_errors:
- когда если display_errors = on, в случае ошибки веб-браузер получит html c кодом 200 и текстом ошибки;
- когда display_errors = off, для фатальных ошибок код реквеста будет 500, причем результат не вернется пользователю. Для остальных ошибок программный код будет работать неверно, однако он «никому про это не расскажет».
Error reporting
Для того чтобы ошибки в PHP не остались незамеченными, их нужно отслеживать с помощью отчетов (reports). Такой report можно получить посредством функции error_reporting() , а включить отображение ошибок можно, используя директиву display_errors:
Функция error reporting является встроенной. Она позволяет контролировать, какие именно errors станут отображаться и сообщаться (reported) разработчику. Не стоит забывать и о том, что в PHP ini существует директива error_reporting, причем во время выполнения функция error_reporting() задает значение этой директивы.
Полезные ссылки на тематические материалы:
- https://www.php.net/manual/ru/function.error-reporting.php;
- https://www.netangels.pro/article/php-errors/;
- https://habr.com/ru/post/440744/;
- https://www.karashchuk.com/PHP/error_reporting-display_errors-display_startup_errors/.
Как обрабатывать Fatal Error в PHP
В одном из наших проектов (социальная генеалогическая сеть), о котором я писал в данном топике, мы используем очередь отложенных событий, реализованную на мемкеше. Ее архитектура такова: приложение записывает в эту очередь различные события и данные, относящиеся к ним (тип события, входящие параметры, и функция обработчик этого события). После чего менеджер(-ы) очереди разбирают эту очередь и выполняют отложенные события. В частности такая очередь используется для сбора статистики, но также и для других более критичных к выполнению задач.
Поэтому очень важно обеспечить high availability для менеджера(-ов) очереди.
Но т.к. ф-ия обработчик очереди к нам приходит из вне, то за качество этого обработчика события мы не отвечаем, т.е. если обработчик вдруг выбросит ошибку, то нам ее нужно обработать и продолжить работу менеджера очереди. Но иногда случается, что обработчики выбрасывают фатальные ошибки (Fatal Error), и это может стать проблемой…
Для треккинга процессов (демонов), очень удобно пользоваться наблюдателями за процессами, такими как monit, мы используем monit для мониторинга сисстемных демонов. Кстати, на хабре недавно была статья о моните.
Но речь не о нем 🙂
Я попросил одного из разработчиков моей команды сделать нормальный обработчик фатальной ошибки в коде менеджера очереди, а именно форк нового инстанса обработчика и логирование ошибки по типу события. На это я получил ответ, что в php фатальные ошибки обрабатывать в принципе невозможно и позорно об этом не знать и что: «компьютерные науки на текущем этапе своего эволюционного развития еще не располагают алгоритмами способными решить поставленную задачу опираясь на возможности php коректно. »
После этого я написал такой код, который обрабатывает фатальные, а также все другие ошибки в php приложении. Если кому-то еще он поможет, то я буду только рад.
ini_set( «display_errors» , «on» );
error_reporting(E_ALL);
ini_set( ‘html_errors’ , ‘on’ );
function fatal_error_handler($buffer) if (preg_match( «|(Fatal error:)(.+)(
file_put_contents( «php://stderr» , «before fork (pid: » . getmypid() . «)\n» );
system( «php tester.php » . getmypid() . » &» );
return «ERROR CAUGHT, check log file» ;
>
return $buffer;
>
//code between ob_start and ob_end_flush is included by MQ Handler, so we know nothing about it, and this code could fire a Fatal Error
if (isset($_SERVER[ «argv» ][1])) file_put_contents( «php://stderr» , «kill : » .var_export(posix_kill($_SERVER[ ‘argv’ ][1], 15), true ). «\n» );
>
ob_start( «fatal_error_handler» );
set_error_handler( «handle_error» );
while ( true ) //Just a Warning
//$a = 9/0;
sleep(10);
file_put_contents( «php://stderr» , «live\n» );
//Fatal error — вызов необъявленной ф-ии
if (rand(1,10) % 2 == 1) ololo(123);
>
>
echo «Program still executing. » ;
* This source code was highlighted with Source Code Highlighter .
Небольшое объяснение по текущему коду.
Fatal Error — мы ловим через буферизацию вывода в ф-ии fatal_error_handler
Остальные ошибки (все кроме фатальных) обрабатываются ф-ией handle_error
Если ошибок нет — код выполняется нормально 🙂
Да, это также не является единственным средством high availability и отказоустойчивости.
Мы пытаемся запустить демон каждую минуту по крону, а код демона начинается ф-ией
if (!checkSingleProcess()) exit;
>
function checkSingleProcess() $res = exec( ‘ps aux | grep mq_manager.php | grep -v grep | grep -v ‘ .getmypid(), $output, $ return );
return $output == array();
>
* This source code was highlighted with Source Code Highlighter .
т.е. если демон запущен, то мы прекращаем выполнение.
Открыт ко всем мнения и по возможности буду отвечать на все комментарии.
UPD: обратите внимание на ini_set(‘html_errors’, ‘on’); я потратил с пол часа времени не понимая почему обработчик не работает из-под CLI. Дело было как раз в HTML-ных ошибках. Т.к. из-под CLI они давались без HTML тегов, и условие preg_match(«|(Fatal error:)(.+)(
UUPD: Немного обновил код, дело в том, что форкая новый процесс через ф-ию system нужно позаботиться о том, чтобы убить процесс который форкал текущий, т.к. функция обработчик будет ждать результата выполнении ф-ии system, а он как извесно не вернется, ведь мы же создаем демона, в связи с этим вы получите кучу процессов, висящих в памяти, которые в конце концов забьют ее полностью.