Вчера всё работало, а сегодня не работает / Код не работает как задумано
или
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.

Это удобно, если останов нужен только при определённом значении, а не всегда (особенно в случае с циклами).
tracerdx at tracerdx dot com ¶
17 years ago
I keep seeing qualification lists for error types/error-nums as arrays; In user notes and in the manual itself. For example, in this manual entry's example, when trying to seperate behavior for the variable trace in the error report:
<?php //...
// set of errors for which a var trace will be saved
$user_errors = array(E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE);//and later...if (in_array($errno, $user_errors)) {
//...whatever
}//... ?>
I was under the impression that PHP error code values where bitwise flag values. Wouldn't bitwise masking be better? So I propose a slightly better way:
<?php //...$user_errors = E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE;//...blah...if ($errno & $user_errors) {
//...whatever
}//... ?>
Or for those of you who don't like the idea of using an integer as the condition in an if statement:
<?php
if (($errno & $user_errors) > 0) {
//...whatever
}
?>
I think that's much more efficient than using _yet another_ array() constuct and an in_array().
If I am wrong, and the E_* constants aren't supposed to be used in this fashion (ie, the constans aren't guaranteed to be bitwise, which would be odd since that's how they're setup in the php.ini file), then delete me. I just don't see why one should be using arrays when bitwise comparisons will work, considering the bitwise method should be MUCH more efficient.
shawing at gmail dot com ¶
18 years ago
Although the root user writes to the files 'error_log' and 'access_log', the Apache user has to own the file referenced by 'error_log = filename' or no log entries will be written.
; From php.ini
; Log errors to specified file.
error_log = /usr/local/apache/logs/php.errors
[root@www logs]$ ls -l /usr/local/apache/logs/php.errors
-rw-r--r-- 1 nobody root 27K Jan 27 16:58 php.errors
ptah at se dot linux dot org ¶
18 years ago
PHP5 only (only tested with php5.0).
If you, for some reason, prefer exceptions over errors and have your custom error handler (set_error_handler) wrap the error into an exception you have to be careful with your script.
Because if you, instead of just calling the exception handler, throws the exception, and having a custom exception handler (set_exception_handler). And an error is being triggered inside that exception handler, you will get a weird error:
"Fatal error: Exception thrown without a stack frame in Unknown on line 0"
This error is not particulary informative, is it? :)
This example below will cause this error.
<?php
class PHPErrorException extends Exception
{
private $context = null;
public function __construct
($code, $message, $file, $line, $context = null)
{
parent::__construct($message, $code);
$this->file = $file;
$this->line = $line;
$this->context = $context;
}
};
function
error_handler($code, $message, $file, $line) {
throw new PHPErrorException($code, $message, $file, $line);
}
function
exception_handler(Exception $e)
{
$errors = array(
E_USER_ERROR => "User Error",
E_USER_WARNING => "User Warning",
E_USER_NOTICE => "User Notice",
);
echo
$errors[$e->getCode()].': '.$e->getMessage().' in '.$e->getFile().
' on line '.$e->getLine()."n";
echo $e->getTraceAsString();
}set_error_handler('error_handler');
set_exception_handler('exception_handler');// Throw exception with an /unkown/ error code.
throw new Exception('foo', 0);
?>
There are however, easy fix for this as it's only cause is sloppy code.
Like one, directly call exception_handler from error_handler instead of throwing an exception. Not only does it remedy this problem, but it's also faster. Though this will cause a `regular` unhandled exception being printed and if only "designed" error messages are intended, this is not the ultimate solution.
So, what is there to do? Make sure the code in exception_handlers doesn't cause any errors! In this case a simple isset() would have solved it.
regards, C-A B.
Stephen ¶
16 years ago
If you are using PHP as an Apache module, your default behavior may be to write PHP error messages to Apache's error log. This is because the error_log .ini directive may be set equal to "error_log" which is also the name of Apache's error log. I think this is intentional.
However, you can separate Apache errors from PHP errors if you wish by simply setting a different value for error_log. I write mine in the /var/log folder.
mortonda at dgrmm dot net ¶
16 years ago
Note the example code listed here calls date() every time this is called. If you have a complex source base which calls the custom error handler often, it can end up taking quite a bit of time. I ran a profiler on som code and discovered that 50% of the time was spent in the date function in this error handler.
Anonymous ¶
17 years ago
When configuring your error log file in php.ini, you can use an absolute path or a relative path. A relative path will be resolved based on the location of the generating script, and you'll get a log file in each directory you have scripts in. If you want all your error messages to go to the same file, use an absolute path to the file.
In some application development methodologies, there is the concept of an application root directory, indicated by "/" (even on Windows). However, PHP does not seem to have this concept, and using a "/" as the initial character in a log file path produces weird behavior on Windows.
If you are running on Windows and have set, in php.ini:
error_log = "/php_error.log"
You will get some, but not all, error messages. The file will appear at
c:php_error.log
and contain internally generated error messages, making it appear that error logging is working. However, log messages requested by error_log() do NOT appear here, or anywhere else, making it appear that the code containing them did not get processed.
Apparently on Windows the internally generated errors will interpret "/" as "C:" (or possibly a different drive if you have Windows installed elsewhere - I haven't tested this). However, the error_log process apparently can't find "/" - understandably enough - and the message is dropped silently.
theotek AT nowhere DOT org ¶
16 years ago
It is totally possible to use debug_backtrace() inside an error handling function. Here, take a look:
<?php
set_error_handler('errorHandler');
function
errorHandler( $errno, $errstr, $errfile, $errline, $errcontext)
{
echo 'Into '.__FUNCTION__.'() at line '.__LINE__.
"nn---ERRNO---n". print_r( $errno, true).
"nn---ERRSTR---n". print_r( $errstr, true).
"nn---ERRFILE---n". print_r( $errfile, true).
"nn---ERRLINE---n". print_r( $errline, true).
"nn---ERRCONTEXT---n".print_r( $errcontext, true).
"nnBacktrace of errorHandler()n".
print_r( debug_backtrace(), true);
}
function
a( )
{
//echo "a()'s backtracen".print_r( debug_backtrace(), true);
asdfasdf; // oops
}
function
b()
{
//echo "b()'s backtracen".print_r( debug_backtrace(), true);
a();
}b();
?>
Outputs:
<raw>
Into errorhandler() at line 9
---ERRNO---
8
---ERRSTR---
Use of undefined constant asdfasdf - assumed 'asdfasdf'
---ERRFILE---
/home/theotek/test-1.php
---ERRLINE---
23
---ERRCONTEXT---
Array
(
)
Backtrace of errorHandler()
Array
(
[0] => Array
(
[function] => errorhandler
[args] => Array
(
[0] => 8
[1] => Use of undefined constant asdfasdf - assumed 'asdfasdf'
[2] => /home/theotek/test-1.php
[3] => 23
[4] => Array
(
)
)
)
[1] => Array
(
[file] => /home/theotek/test-1.php
[line] => 23
[function] => a
)
[2] => Array
(
[file] => /home/theotek/test-1.php
[line] => 30
[function] => a
[args] => Array
(
)
)
[3] => Array
(
[file] => /home/theotek/test-1.php
[line] => 33
[function] => b
[args] => Array
(
)
)
)
</raw>
So, the first member of the backtrace's array is not really surprising, except from the missing "file" and "line" members.
The second member of the backtrace seem the be a hook inside the zend engine that is used to trigger the error.
Other members are the normal backtrace.
jbq at caraldi dot com ¶
15 years ago
Precision about error_log when configured with syslog: the syslog() call is done with severity NOTICE.
petrov dot michael () gmail com ¶
16 years ago
I have found that on servers that enforce display_errors to be off it is very inconvenient to debug syntax errors since they cause fatal startup errors. I have used the following method to bypass this limitation:
The syntax error is inside the file "syntax.php", therefore I create a file "syntax.debug.php" with the following code:
<?php
error_reporting(E_ALL);
ini_set('display_errors','On');
include(
'syntax.php');
?>
The 5 line file is guaranteed to be free of errors, allowing PHP to execute the directives within it before including the file which previously caused fatal startup errors. Now those fatal startup errors become run time fatal errors.
Антон Шевчук // Web-разработчик
Не совершает ошибок только тот, кто ничего не делает, и мы тому пример – трудимся не покладая рук над созданием рабочих мест для тестировщиков 🙂
О да, в этой статье я поведу свой рассказа об ошибках в PHP, и том как их обуздать.
Ошибки
Разновидности в семействе ошибок
Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.
Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
Фатальные ошибки
Самый грозный вид ошибок – фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.
E_PARSE
Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:
<?php
/**
Parse error: syntax error, unexpected end of file
*/
{
Или написали на непонятном языке:
<?php /** Parse error: syntax error, unexpected '...' (T_STRING) */ Тут будет ошибка парсера
Лишние скобочки тоже встречаются, и не важно круглые либо фигурные:
<?php /** Parse error: syntax error, unexpected '}' */ }
Отмечу один важный момент – код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:
<?php
// этот код не сработает
error_reporting(E_ALL);
ini_set('display_errors', 1);
// т.к. вот тут
ошибка парсера
E_ERROR
Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин, так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:
Не был найден подключаемый файл:
/** Fatal error: require_once(): Failed opening required 'not-exists.php' (include_path='.:/usr/share/php:/usr/share/pear') */ require_once 'not-exists.php';
Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:
/** Fatal error: Uncaught exception 'Exception' */ throw new Exception();
При попытке вызвать несуществующий метод класса:
/** Fatal error: Call to undefined method stdClass::notExists() */ $stdClass = new stdClass(); $stdClass->notExists();
Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:
/**
Fatal Error: Allowed Memory Size
*/
$arr = array();
while (true) {
$arr[] = str_pad(' ', 1024);
}
Очень часто происходит при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти
Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug:
/**
Fatal error: Maximum function nesting level of '256' reached, aborting!
*/
function deep() {
deep();
}
deep();
Не фатальные
Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик, и именно они доставляют больше всего хлопот у начинающих разработчиков.
E_WARNING
Частенько встречается, когда подключаешь файл с использованием include, а его не оказывается на сервере или ошиблись указывая путь к файлу:
/** Warning: include_once(): Failed opening 'not-exists.php' for inclusion */ include_once 'not-exists.php';
Бывает, если используешь неправильный тип аргументов при вызове функций:
/**
Warning: join(): Invalid arguments passed
*/
join('string', 'string');
Их очень много, и перечислять все не имеет смысла…
E_NOTICE
Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.
Когда обращаются к неопределенной переменной:
/** Notice: Undefined variable: a */ echo $a;
Когда обращаются к несуществующему элементу массива:
<?php /** Notice: Undefined index: a */ $b = array(); $b['a'];
Когда обращаются к несуществующей константе:
/** Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT' */ echo UNKNOWN_CONSTANT;
Когда не конвертируют типы данных:
/** Notice: Array to string conversion */ echo array();
Для избежания подобных ошибок – будьте внимательней, и если вам IDE подсказывает о чём-то – не игнорируйте её:

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_ERRORPHP выбрасывает исключение - эти исключения не наследуют класс 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. Спасибо Максиму Слесаренко за помощь в написании статьи
За последние 24 часа нас посетили 8870 программистов и 836 роботов. Сейчас ищут 372 программиста …
Обработка и протоколирование ошибок
- Введение
- Установка и настройка
- Требования
- Установка
- Настройка во время выполнения
- Типы ресурсов
- Предопределенные константы
- Примеры
- Функции обработки ошибок
- debug_backtrace — Выводит стек вызовов функций в массив
- debug_print_backtrace — Выводит стек вызовов функций
- error_clear_last — Clear the most recent error
- error_get_last — Получение информации о последней произошедшей ошибке
- error_log — Отправляет сообщение об ошибке заданному обработчику ошибок
- error_reporting — Задает, какие ошибки PHP попадут в отчет
- restore_error_handler — Восстанавливает предыдущий обработчик ошибок
- restore_exception_handler — Восстанавливает предыдущий обработчик исключений
- set_error_handler — Задает определенный пользователем обработчик ошибок
- set_exception_handler — Задает пользовательский обработчик исключений
- trigger_error — Вызывает пользовательскую ошибку/предупреждение/уведомление
- user_error — Синоним для trigger_error
Вернуться к: Изменение поведения PHP
Хотя PHP уже давно поддерживает обработку исключений, однако, по сравнению с Java эта поддержка была довольно слабой
Первоначальная поддержка обработки исключений была введена в язык с 5 версии PHP, с двумя простыми встроенными классами исключений — Exception и ErrorException, с поддержкой дополнительных классов через SPL. Идея этого поста состоит в том, чтобы представить читателям современные возможности обработки исключений PHP.
Новый интерфейс
Хотя PHP 7 предоставляет классы Error и Exception, давайте сначала затронем интерфейс Throwable . И Error и Exception классы реализуют Throwable интерфейс — это основа для любого объекта , который может быть брошен с помощью оператора throw. Единственное, что он не может быть реализован непосредственно в классах пользовательского пространства, только через расширение класса Exception. Кроме того, он обеспечивает единую точку для отлова обоих типов ошибок в одном выражении:
<?php
try {
// ваш код
} catch (Throwable $e) {
echo 'Очень хороший способ отловить исключения и ошибки';
}
Список доступных встроенных классов исключений начиная с PHP 7.4:
- Exception
- ErrorException
- Error
- ArgumentCountError
- ArithmeticError
- AssertionError
- DivisionByZeroError
- CompileError
- ParseError
- TypeError
Дополнительные классы исключений можно найти в стандартной библиотеке PHP . И наиболее заметным из расширений JSON является класс JsonException.
THROWABLE
Интерфейс Throwable PHP 7:
interface Throwable
{
public function getMessage(): string; // Error reason
public function getCode(): int; // Error code
public function getFile(): string; // Error begin file
public function getLine(): int; // Error begin line
public function getTrace(): array; // Return stack trace as array like debug_backtrace()
public function getTraceAsString(): string; // Return stack trace as string
public function getPrevious(): Throwable; // Return previous `Trowable`
public function __toString(): string; // Convert into string
}
Вот иерархия Throwable:
interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException
Ошибка, почему?
В предыдущих версиях PHP ошибки обрабатывались совершенно иначе, чем исключения. Если возникала ошибка, то пока она не была фатальной, она могла быть обработана пользовательской функцией.
Проблема заключалась в том, что было несколько фатальных ошибок, которые не могли быть обработаны определяемым пользователем обработчиком ошибок. Это означало, что вы не могли корректно обрабатывать фатальные ошибки в PHP. Было несколько побочных эффектов, которые были проблематичными, такие как потеря контекста времени выполнения, деструкторы не вызывались, да и вообще иметь дело с ними было неудобно. В PHP 7 фатальные ошибки теперь являются исключениями, и мы можем легко их обработать. Фатальные ошибки приводят к возникновению исключений. Вам необходимо обрабатывать нефатальные ошибки с помощью функции обработки ошибок.
Вот пример ловли фатальной ошибки в PHP 7.1. Обратите внимание, что нефатальная ошибка не обнаружена.
<?php
try {
// будет генерировать уведомление, которое не будет поймано
echo $someNotSetVariable;
// фатальная ошибка, которая сейчас на самом деле ловится
someNoneExistentFunction();
} catch (Error $e) {
echo "Error caught: " . $e->getMessage();
}
Этот скрипт выведет сообщение об ошибке при попытке доступа к недопустимой переменной. Попытка вызвать функцию, которая не существует, приведет к фатальной ошибке в более ранних версиях PHP, но в PHP 7.1 вы можете ее перехватить. Вот вывод для скрипта:
Notice: Undefined variable: someNotSetVariable on line 3
Error caught: Call to undefined function someNoneExistentFunction()
Константы ошибок
В PHP много констант, которые используются в отношении ошибок. Эти константы используются при настройке PHP для скрытия или отображения ошибок определенных классов.
Вот некоторые из наиболее часто встречающихся кодов ошибок:
- E_DEPRECATED — интерпретатор сгенерирует этот тип предупреждений, если вы используете устаревшую языковую функцию. Сценарий обязательно продолжит работать без ошибок.
- E_STRICT — аналогично E_DEPRECATED, — указывает на то, что вы используете языковую функцию, которая не является стандартной в настоящее время и может не работать в будущем. Сценарий будет продолжать работать без каких-либо ошибок.
- E_PARSE — ваш синтаксис не может быть проанализирован, поэтому ваш скрипт не запустится. Выполнение скрипта даже не запустится.
- E_NOTICE — движок просто выведет информационное сообщение. Выполнение скрипта не прервется, и ни одна из ошибок не будет выдана.
- E_ERROR — скрипт не может продолжить работу, и завершится. Выдает ошибки, а как они будут обрабатываться, зависит от обработчика ошибок.
- E_RECOVERABLE_ERROR — указывает на то, что, возможно, произошла опасная ошибка, и движок работает в нестабильном состоянии. Дальнейшее выполнение зависит от обработчика ошибок, и ошибка обязательно будет выдана.
Полный список констант можно найти в руководстве по PHP.
Функция обработчика ошибок
Функция set_error_handler() используется, чтобы сообщить PHP как обрабатывать стандартные ошибки, которые не являются экземплярами класса исключений Error. Вы не можете использовать функцию обработчика ошибок для фатальных ошибок. Исключения ошибок должны обрабатываться с помощью операторов try/catch. set_error_handler() принимает callback функцию в качестве своего параметра. Callback-функции в PHP могут быть заданы двумя способами: либо строкой, обозначающей имя функции, либо передачей массива, который содержит объект и имя метода (именно в этом порядке). Вы можете указать защищенные и приватные методы для callable в объекте. Вы также можете передать значение null, чтобы указать PHP вернуться к использованию стандартного механизма обработки ошибок. Если ваш обработчик ошибок не завершает программу и возвращает результат, ваш сценарий будет продолжать выполняться со строки, следующей за той, где произошла ошибка.
PHP передает параметры в вашу функцию обработчика ошибок. Вы можете опционально объявить их в сигнатуре функции, если хотите использовать их в своей функции.
Вот пример:
<?php
function myCustomErrorHandler(int $errNo, string $errMsg, string $file, int $line) {
echo "Ух ты, мой обработчик ошибок получил #[$errNo] в [$file] на [$line]: [$errMsg]";
}
set_error_handler('myCustomErrorHandler');
try {
why;
} catch (Throwable $e) {
echo 'И моя ошибка: ' . $e->getMessage();
}
Если вы запустите этот код в PHP-консоли php -a, вы должны получить похожий вывод:
Error #[2] occurred in [php shell code] at line [3]: [Use of undefined constant why - assumed 'why' (this will throw an Error in a future version of PHP)]
Самые известные PHP библиотеки , которые делают обширное использование РНР set_error_handler() и могут сделать хорошие представления исключений и ошибок являются whoops, и Symony Debug, ErrorHandler компоненты. Я смело рекомендую использовать один из них. Если не собираетесь их использовать в своем проекте, то вы всегда можете черпать вдохновение из их кода. В то время как компонент Debug широко используется в экосистеме Symfony, Whoops остается библиотекой выбора для фреймворка Laravel .
Для подробного и расширенного использования, пожалуйста, обратитесь к руководству по PHP для обработчика ошибок.
Отображение или подавление нефатальной ошибки
Когда ваше приложение выходит в продакшн, логично, что вы хотите скрыть все системные сообщения об ошибках во время работы, и ваш код должен работать без генерации предупреждений или сообщений. Если вы собираетесь показать сообщение об ошибке, убедитесь, что оно сгенерировано и не содержит информации, которая может помочь злоумышленнику проникнуть в вашу систему.
В вашей среде разработки вы хотите, чтобы все ошибки отображались, чтобы вы могли исправить все проблемы, с которыми они связаны, но в процессе работы вы хотите подавить любые системные сообщения, отправляемые пользователю.
Для этого вам нужно настроить PHP, используя следующие параметры в вашем файле php.ini:
- display_errors – может быть установлен в false для подавления сообщений
- log_errors – может использоваться для хранения сообщений об ошибках в файлах журнала
- error_reporting – можно настроить, какие ошибки вызывают отчет
Лучше всего корректно обрабатывать ошибки в вашем приложении. В производственном процессе вы должны скорее регистрировать необработанные ошибки, чем разрешать их отображение пользователю. Функция error_log() может использоваться для отправки сообщения одной из определенных процедур обработки ошибок. Вы также можете использовать функцию error_log() для отправки электронных писем, но лично вы бы предпочли использовать хорошее решение для регистрации ошибок и получения уведомлений при возникновении ошибок, например Sentry или Rollbar .
Существует вещь, называемая оператором контроля ошибок ( @ ), который по своей сути может игнорировать и подавлять ошибки. Использование очень простое — просто добавьте любое выражение PHP с символом «собаки», и сгенерированная ошибка будет проигнорирована. Хотя использование этого оператора может показаться интересным, я призываю вас не делать этого. Мне нравится называть это живым пережитком прошлого.
Больше информации для всех функций, связанных с ошибками PHP, можно найти в руководстве.
Исключения (Exceptions)
Исключения являются основной частью объектно-ориентированного программирования и впервые были представлены в PHP 5.0. Исключением является состояние программы, которое требует специальной обработки, поскольку оно не выполняется ожидаемым образом. Вы можете использовать исключение, чтобы изменить поток вашей программы, например, чтобы прекратить что-либо делать, если некоторые предварительные условия не выполняются.
Исключение будет возникать в стеке вызовов, если вы его не перехватите. Давайте посмотрим на простой пример:
try {
print "это наш блок попыток n";
throw new Exception();
} catch (Exception $e) {
print "что-то пошло не так, есть улов!";
} finally {
print "эта часть всегда выполняется";
}
PHP включает в себя несколько стандартных типов исключений, а стандартная библиотека PHP (SPL) включает в себя еще несколько. Хотя вам не нужно использовать эти исключения, это означает, что вы можете использовать более детальное обнаружение ошибок и отчеты. Классы Exception и Error реализуют интерфейс Throwable и, как и любые другие классы, могут быть расширены. Это позволяет вам создавать гибкие иерархии ошибок и адаптировать обработку исключений. Только класс, который реализует класс Throwable, может использоваться с ключевым словом throw. Другими словами, вы не можете объявить свой собственный базовый класс и затем выбросить его как исключение.
Надежный код может встретить ошибку и справиться с ней. Разумная обработка исключений повышает безопасность вашего приложения и облегчает ведение журнала и отладку. Управление ошибками в вашем приложении также позволит вам предложить своим пользователям лучший опыт. В этом разделе мы рассмотрим, как отлавливать и обрабатывать ошибки, возникающие в вашем коде.
Ловля исключений
Вы должны использовать try/catch структуру:
<?php
class MyCustomException extends Exception { }
function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так.');
}
try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
}
Как видите, есть два предложения catch. Исключения будут сопоставляться с предложениями сверху вниз, пока тип исключения не будет соответствовать предложению catch. Эта очень простая функция throwMyCustomException() генерирует исключение MyCustomException, и мы ожидаем, что оно будет перехвачено в первом блоке. Любые другие исключения, которые произойдут, будут перехвачены вторым блоком. Здесь мы вызываем метод getMessage() из базового класса Exception. Вы можете найти больше информации о дополнительном методе в Exception PHP docs.
Кроме того, можно указать несколько исключений, разделяя их трубой ( | ).
Давайте посмотрим на другой пример:
<?php
class MyCustomException extends Exception { }
class MyAnotherCustomException extends Exception { }
try {
throw new MyAnotherCustomException;
} catch (MyCustomException | MyAnotherCustomException $e) {
echo "Caught : " . get_class($e);
}
Этот очень простой блок catch будет перехватывать исключения типа MyCustomException и MyAnotherCustomException.
Немного более продвинутый сценарий:
// exceptions.php
use SymfonyComponentHttpKernelExceptionNotFoundHttpException;
try {
throw new NotFoundHttpException();
} catch (Exception $e) {
echo 1;
} catch (NotFoundHttpException $e) {
echo 2;
} catch (Exception $e) {
echo 3;
} finally {
echo 4;
}
Это ваш окончательный ответ?
В PHP 5.5 и более поздних, блок finally также может быть указан после или вместо блоков catch. Код внутри блока finally всегда будет выполняться после блоков try и catch независимо от того, было ли выброшено исключение, и до возобновления нормального выполнения. Одним из распространенных применений блока finally является закрытие соединения с базой данных, но, наконец, его можно использовать везде, где вы хотите, чтобы код всегда выполнялся.
<?php
class MyCustomException extends Exception { }
function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так');
}
try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
} finally {
echo "Я всегда тут";
}
Вот хороший пример того, как работают операторы PHP catch/finally:
<?php
try {
try {
echo 'a-';
throw new exception();
echo 'b-';
} catch (Exception $e) {
echo 'пойманный-';
throw $e;
} finally {
echo 'завершенный-';
}
} catch (Exception $e) {
echo 'конец-';
}
Функция обработчика исключений
Любое исключение, которое не было обнаружено, приводит к фатальной ошибке. Если вы хотите изящно реагировать на исключения, которые не перехватываются в блоках перехвата, вам нужно установить функцию в качестве обработчика исключений по умолчанию.
Для этого вы используете функцию set_exception_handler() , которая принимает вызываемый элемент в качестве параметра. Ваш сценарий завершится после того, как вызов будет выполнен.
Функция restore_exception_handler() вернет обработчик исключений к его предыдущему значению.
<?php
class MyCustomException extends Exception { }
function exception_handler($exception) {
echo "Uncaught exception: " , $exception->getMessage(), "n";
}
set_exception_handler('exception_handler');
try {
throw new Exception('Uncaught Exception');
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} finally {
echo "Я всегда тут";
}
print "Не выполнено";
Здесь простая функция exception_handler будет выполняться после блока finally, когда ни один из типов исключений не был сопоставлен. Последний вывод никогда не будет выполнен.
Для получения дополнительной информации обратитесь к документации PHP.
Старый добрый «T_PAAMAYIM_NEKUDOTAYIM»
Вероятно, это было самое известное сообщение об ошибке PHP. В последние годы было много споров по этому поводу. Вы можете прочитать больше в отличном сообщении в блоге от Фила Осетра .
Сегодня я с гордостью могу сказать, что если вы запустите этот код с PHP 7, то сообщение о T_PAAMAYIM_NEKUDOTAYIM больше не будет:
<?php
class foo
{
static $bar = 'baz';
}
var_dump('foo'::$bar);
// Output PHP < 7.0:
// PHP Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM) in php shell code on line 1
// Output PHP > 7.0:
// string(3) "baz"
?>
В заключении
Со времени первого внедрения обработки исключений в PHP прошло много лет, пока мы не получили гораздо более надежную и зрелую обработку исключений, чем в Java. Обработка ошибок в PHP 7 получила много внимания, что делает его хорошим, открывая пространство для будущих улучшений, если мы действительно с сегодняшней точки зрения нуждаемся в них.
В статье представлена очередная попытка разобраться с ошибками, которые могут встретиться на вашем пути php-разработчика, их возможная классификация, примеры их возникновения, влияние ошибок на ответ клиенту, а также инструкции по написанию своего обработчика ошибок.
Статья разбита на четыре раздела:
- Классификация ошибок.
- Пример, демонстрирующий различные виды ошибок и его поведение при различных настройках.
- Написание собственного обработчика ошибок.
- Полезные ссылки.
Классификация ошибок
Все ошибки, условно, можно разбить на категории по нескольким критериям.
Фатальность:
- Фатальные
Неустранимые ошибки. Работа скрипта прекращается.
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR. - Не фатальные
Устранимые ошибки. Работа скрипта не прекращается.
E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED. - Смешанные
Фатальные, но только, если не обработаны функцией, определенной пользователем в set_error_handler().
E_USER_ERROR, E_RECOVERABLE_ERROR.
Возможность перехвата ошибки функцией, определенной в set_error_handler():
- Перехватываемые (не фатальные и смешанные)
E_USER_ERROR, E_RECOVERABLE_ERROR, E_WARNING, E_NOTICE, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED. - Не перехватываемые (фатальные)
E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING.
Инициатор:
- Инициированы пользователем
E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE. - Инициированы PHP
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED, E_USER_ERROR, E_RECOVERABLE_ERROR.
Для нас, в рамках данной статьи, наиболее интересны классификации по первым двум критериям, о чем будет рассказано далее.
Примеры возникновения ошибок
Листинг index.php
<?php
// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// подключаем файл с ошибками
require 'errors.php';
Листинг errors.php
<?php
echo "Файл с ошибками. Начало<br>";
/*
* перехватываемые ошибки (ловятся функцией set_error_handler())
*/
// NONFATAL - E_NOTICE
// echo $undefined_var;
// NONFATAL - E_WARNING
// array_key_exists('key', NULL);
// NONFATAL - E_DEPRECATED
split('[/.-]', "12/21/2012"); // split() deprecated начиная с php 5.3.0
// NONFATAL - E_STRICT
// class c {function f(){}} c::f();
// NONFATAL - E_USER_DEPRECATED
// trigger_error("E_USER_DEPRECATED", E_USER_DEPRECATED);
// NONFATAL - E_USER_WARNING
// trigger_error("E_USER_WARNING", E_USER_WARNING);
// NONFATAL - E_USER_NOTICE
// trigger_error("E_USER_NOTICE", E_USER_NOTICE);
// FATAL, если не обработана функцией set_error_handler - E_RECOVERABLE_ERROR
// class b {function f(int $a){}} $b = new b; $b->f(NULL);
// FATAL, если не обработана функцией set_error_handler - E_USER_ERROR
// trigger_error("E_USER_ERROR", E_USER_ERROR);
/*
* неперехватываемые (не ловятся функцией set_error_handler())
*/
// FATAL - E_ERROR
// undefined_function();
// FATAL - E_PARSE
// parse_error
// FATAL - E_COMPILE_ERROR
// $var[];
echo "Файл с ошибками. Конец<br>";
Примечание: для полной работоспособности скрипта необходим PHP версии не ниже 5.3.0.
В файле errors.php представлены выражения, инициирующие практически все возможные ошибки. Исключение составили: E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, генерируемые ядром Zend. В теории, встретить их в реальной работе вы не должны.
В следующей таблице приведены варианты поведения этого скрипта в различных условиях (в зависимости от значений директив display_errors и error_reporting):
| Группа ошибок | Значения директив* | Статус ответа сервера | Ответ клиенту** |
|---|---|---|---|
| E_PARSE, E_COMPILE_ERROR*** | display_errors = off error_reporting = ANY |
500 | Пустое значение |
| display_errors = on error_reporting = ANY |
200 | Сообщение об ошибке | |
| E_USER_ERROR, E_ERROR, E_RECOVERABLE_ERROR | display_errors = off error_reporting = ANY |
500 | Вывод скрипта до ошибки |
| display_errors = on error_reporting = ANY |
200 | Сообщение об ошибке и вывод скрипта до ошибки | |
| Не фатальные ошибки | display_errors = off error_reporting = ANY и display_errors = on error_reporting = 0 |
200 | Весь вывод скрипта |
| display_errors = on error_reporting = E_ALL | E_STRICT |
200 | Сообщение об ошибке и весь вывод скрипта |
* Значение ANY означает E_ALL | E_STRICT или 0.
** Ответ клиенту может отличаться от ответов на реальных скриптах. Например, вывод какой-либо информации до включения файла errors.php, будет фигурировать во всех рассмотренных случаях.
*** Если в файле errors.php заменить пример для ошибки E_COMPILE_ERROR на require "missing_file.php";, то ошибка попадет во вторую группу.
Значение, приведенной выше, таблицы можно описать следующим образом:
- Наличие в файле скрипта ошибки, приводящей его в «негодное» состояние (невозможность корректно обработать), на выходе даст пустое значение или же только само сообщение об ошибке, в зависимости от значения директивы display_errors.
- Скрипт в файле с фатальной ошибкой, не относящейся к первому пункту, будет выполняться в штатном режиме до самой ошибки.
- Наличие в файле фатальной ошибки при display_errors = Off обозначит 500 статус ответа.
- Не фатальные ошибки, как и следовало ожидать, в контексте возможности исполнения скрипта в целом, на работоспособность не повлияют.
Собственный обработчик ошибок
Для написания собственного обработчика ошибок необходимо знать, что:
- для получения информации о последней произошедшей ошибке существует функция error_get_last();
- для определения собственного обработчика ошибок существует функция set_error_handler(), но фатальные ошибки нельзя «перехватить» этой функцией;
- используя register_shutdown_function(), можно зарегистрировать свою функцию, выполняемую по завершении работы скрипта, и в ней, используя знания из первого пункта, если фатальная ошибка имела место быть, предпринять необходимые действия;
- сообщение о фатальной ошибке в любом случае попадет в буфер вывода;
- воспользовавшись функциями контроля вывода можно предотвратить отображение нежелательной информации;
- при использовании оператора управления ошибками (знак @) функция, определенная в set_error_handler() все равно будет вызвана, но функция error_reporting() в этом случае вернет 0, чем и можно пользоваться для прекращения работы или определения другого поведения своего обработчика ошибок.
Третий пункт поясню: зарегистрированная нами функция при помощи register_shutdown_function() выполнится в любом случае — корректно ли завершился скрипт, либо же был прерван в связи с критичной (фатальной) ошибкой. Второй вариант мы можем однозначно определить, воспользовавшись информацией предоставленной функцией error_get_last(), и, если ошибка все же была, выполнить наш собственный обработчик ошибок.
Продемонстрируем вышесказанное на модифицированном скрипте index.php:
<?php
/**
* Обработчик ошибок
* @param int $errno уровень ошибки
* @param string $errstr сообщение об ошибке
* @param string $errfile имя файла, в котором произошла ошибка
* @param int $errline номер строки, в которой произошла ошибка
* @return boolean
*/
function error_handler($errno, $errstr, $errfile, $errline)
{
// если ошибка попадает в отчет (при использовании оператора "@" error_reporting() вернет 0)
if (error_reporting() & $errno)
{
$errors = array(
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
);
// выводим свое сообщение об ошибке
echo "<b>{$errors[$errno]}</b>[$errno] $errstr ($errfile на $errline строке)<br />n";
}
// не запускаем внутренний обработчик ошибок PHP
return TRUE;
}
/**
* Функция перехвата фатальных ошибок
*/
function fatal_error_handler()
{
// если была ошибка и она фатальна
if ($error = error_get_last() AND $error['type'] & ( E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR))
{
// очищаем буффер (не выводим стандартное сообщение об ошибке)
ob_end_clean();
// запускаем обработчик ошибок
error_handler($error['type'], $error['message'], $error['file'], $error['line']);
}
else
{
// отправка (вывод) буфера и его отключение
ob_end_flush();
}
}
// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// включаем буфферизацию вывода (вывод скрипта сохраняется во внутреннем буфере)
ob_start();
// устанавливаем пользовательский обработчик ошибок
set_error_handler("error_handler");
// регистрируем функцию, которая выполняется после завершения работы скрипта (например, после фатальной ошибки)
register_shutdown_function('fatal_error_handler');
require 'errors.php';
Не забываем, что ошибки смешанного типа, после объявления собственного обработчика ошибок, стали не фатальными. Плюс к этому, весь вывод скрипта до фатальной ошибки вместе с стандартным сообщением об ошибке будет сброшен.
Вообще, рассмотренный пример обработчика ошибок, обработкой, как таковой, не занимается, а только демонстрирует саму возможность. Дальнейшее его поведение зависит от ваших желаний и/или требований. Например, все случаи обращения к обработчику можно записывать в лог, а в случае фатальных ошибок, дополнительно, уведомлять об этом администратора ресурса.
Полезные ссылки
- Первоисточник: php.net/manual/ru/book.errorfunc.php
- Описание ошибок: php.net/manual/ru/errorfunc.constants.php
- Функции контроля вывода: php.net/manual/ru/ref.outcontrol.php
- Побитовые операторы: php.net/manual/ru/language.operators.bitwise.php и habrahabr.ru/post/134557
- Тематически близкая статья: habrahabr.ru/post/134499
На чтение 11 мин Просмотров 1.2к. Опубликовано 16.10.2021
PHP существует довольно давно и разработал свои особенности и особенности. Он также разработал свой собственный вид отчетов об ошибках, который довольно прост. В этом посте мы покажем вам, как легко добавить мониторинг ошибок для PHP.
Содержание
- Что такое ошибка PHP?
- Какие бывают типы ошибок в PHP?
- Ошибки синтаксического анализа или синтаксиса
- Фатальные ошибки
- Предупреждение об ошибках
- Уведомление об ошибках
- Как включить отчеты об ошибках в PHP
- Сколько уровней ошибок доступно в PHP?
- Ошибки отображения PHP
- Что такое предупреждение PHP?
- Как помогают отчеты о сбоях
- Завершение отчета об ошибках PHP
Что такое ошибка PHP?
Ошибка PHP — это структура данных, представляющая что-то, что пошло не так в вашем приложении. В PHP есть несколько конкретных способов вызова ошибок. Один из простых способов имитировать ошибку — использовать die()функцию:
die("something bad happened!");
Это завершит программу PHP и сообщит об ошибке. Когда программа завершается, это то, что мы называем фатальной ошибкой. Позже вы увидите, что мы можем контролировать, как именно обрабатывается ошибка, в случае, если нам нужно вызвать некоторую логику очистки или перенаправить сообщение об ошибке. Вы также можете смоделировать это с помощью trigger_error()функции:
<?php trigger_error("something happened"); //error level is E_USER_NOTICE //You can control error level trigger_error("something bad happened", E_USER_ERROR); ?>
По умолчанию это вызовет в системе некритическое уведомление. Вы можете переопределить уровень ошибки, если вам нужна более серьезная ошибка.
На самом деле в PHP есть две формы ошибок: стандартные обычные ошибки и исключения.
Исключения были введены в PHP 5. Они дают вам легче семантику, как try, throwи catch. Исключение легко создать. Это следует из большого успеха языков со статической типизацией, таких как C # и Java.
throw new Exception("Yo, something exceptional happened);
Перехват и выдача исключений, как правило, более упрощены, чем более традиционная обработка ошибок PHP. Вы также можете иметь более локализованную обработку ошибок, а не только глобальную обработку ошибок с помощью set_error_handler (). Вы можете окружить конкретную логику блоками try / catch, которые заботятся только о конкретных исключениях:
<?php try { doSystemLogic(); } catch (SystemException $e) { echo 'Caught system exception '; } try { doUserLogic(); } catch (Exception $e) { echo 'Caught misc exception '; } ?>
Какие бывают типы ошибок в PHP?
Ошибка PHP — это не одно и то же, но бывает четырех разных типов:
- синтаксические или синтаксические ошибки
- фатальные ошибки
- предупреждения об ошибках
- замечать ошибки
Ошибки синтаксического анализа или синтаксиса
Первая категория ошибок в PHP — это ошибки синтаксического анализа, также называемые синтаксическими ошибками. Они просто означают, что в вашем скрипте есть один или несколько неправильных символов. Возможно, вы пропустили точку с запятой или неправильно поставили скобку. Взгляните на следующий пример:
<?php $age = 25; if ($age >= 18 { echo 'Of Age'; } else { echo 'Minor'; } ?>
Запустив приведенный выше сценарий, я получаю следующую ошибку:
Parse error: syntax error, unexpected '{' in <path> on line 4
С помощью сообщения об ошибке легко увидеть, что в операторе if отсутствует закрывающая скобка. Давайте исправим это:
<?php $age = 25; if ($age >= 18) { echo 'Of Age'; } else { echo 'Minor'; } ?>
Фатальные ошибки
Неустранимые ошибки, как следует из их названия, — это те, которые способны убить — или привести к сбою — приложение. Другими словами, фатальные ошибки — это критические ошибки, означающие, что произошло что-то катастрофическое, и приложение не может продолжать работу.
Часто причиной фатальных ошибок является неопределенный класс, функция или другой артефакт. Если сценарий пытается использовать несуществующую функцию, PHP не знает, что делать, и сценарий необходимо остановить.
Рассмотрим следующий сценарий:
<?php function add($a, $b) { return $a + $b; } echo '2 + 2 is ' . sum(2, 2); ?>
Как видите, сценарий определяет функцию с именем add, а затем пытается вызвать ее с неправильным именем. Эта ситуация приводит к фатальной ошибке:
Fatal error: Uncaught Error: Call to undefined function sum() in F:xampphtdocstest.php:7 Stack trace: #0 {main} thrown in <path> on line 7
Все, что нужно для решения ошибки, — это изменить вызов функции на правильное имя, добавить:
echo '2 + 2 is ' . add(2, 2);
Предупреждение об ошибках
Предупреждающие ошибки — это ошибки, которые не приводят к завершению работы скрипта. Подобно тому, что происходит на других языках, предупреждение в PHP обычно представляет собой что-то, что еще не является серьезной проблемой — или, по крайней мере, не критичной, — но может стать серьезной проблемой в будущем, поэтому вам лучше сохранить глаз на это.
Взгляните на следующий код:
<?php $components = parse_url(); var_dump($components); ?>
После выполнения приведенного выше кода мы получаем следующее предупреждение:
Warning: parse_url() expects at least 1 parameter, 0 given in <path> on line 2
Предупреждение вызывает тот факт, что мы не предоставили параметр функции parse_url. Давайте исправим это:
<?php $components = parse_url('https://example.com'); var_dump($components); ?>
Это устраняет предупреждение:
array(2) { ["scheme"]=> string(5) "https" ["host"]=> string(11) "example.com" }
Уведомление об ошибках
Уведомления об ошибках похожи на предупреждения в том, что они также не останавливают выполнение скрипта. Вы также должны думать об ошибках уведомления как о том, что PHP предупреждает вас о том, что может стать проблемой в будущем. Однако уведомления обычно считаются менее важными или менее важными, чем предупреждения.
Рассмотрим следующий фрагмент кода, который представляет собой измененную версию сценария, использованного в предыдущих разделах:
<?php $numbers = "1,2,5,6"; $parts = explode(",", $integers); echo 'The first number is ' . $parts[0]; ?>
Как видите, сценарий определяет переменную $ numbers, а затем пытается передать переменную с именем $ inteers в функцию explode.
Неопределенные переменные действительно являются одной из основных причин уведомлений в PHP. Чтобы ошибка исчезла, достаточно изменить переменную $ inteers на $ numbers.
Как включить отчеты об ошибках в PHP
Включить отчеты об ошибках в PHP очень просто. Вы просто вызываете функцию в своем скрипте:
<?php error_reporting(E_ALL); //You can also report all errors by using -1 error_reporting(-1); //If you are feeling old school ini_set('error_reporting', E_ALL); ?>
Здесь сказано: «Пожалуйста, сообщайте об ошибках всех уровней». Мы рассмотрим, какие уровни есть позже, но считаем это категорией ошибок. По сути, он говорит: «Сообщайте обо всех категориях ошибок». Вы можете отключить отчет об ошибках, установив 0:
<?php error_reporting(0); ?>
Параметр метода в error_reporting()действительности является битовой маской. Вы можете указать в нем различные комбинации уровней ошибок, используя эту маску, как видите:
<?php error_reporting(E_ERROR | E_WARNING | E_PARSE); ?>
В нем говорится: «сообщать о фатальных ошибках, предупреждениях и ошибках синтаксического анализатора». Вы можете просто разделить их знаком «|» чтобы добавить больше ошибок. Иногда вам могут потребоваться более расширенные настройки отчетов об ошибках. Вы можете использовать операторы битовой маски для составления отчетов по различным критериям:
<?php // Report all errors except E_NOTICE error_reporting(E_ALL & ~E_NOTICE); ?>
Как видите, вы можете гибко определять, о каких ошибках сообщать. Возникает вопрос: о каких типах ошибок и исключений следует сообщать?
Сколько уровней ошибок доступно в PHP?
В PHP 5 целых 16 уровней ошибок. Эти ошибки представляют категорию, а иногда и серьезность ошибки в PHP. Их много, но многочисленные категории позволяют легко определить, где отлаживать ошибку, исходя только из ее уровня. Итак, если вы хотите сделать что-то конкретное только для ошибок пользователя, например, проверку ввода, вы можете определить обработчик условий для всего, что начинается с E_USER. Если вы хотите убедиться, что вы закрыли ресурс, вы можете сделать это, указав на ошибки, оканчивающиеся на _ERROR.
Ошибки в PHP по большей части классифицируются по степени серьезности (предупреждение об ошибке, уведомление) и источнику (пользователь, компилятор, среда выполнения).
Я хочу остановиться на нескольких популярных из них.
Во-первых, у нас есть общие ошибки:
- E_ERROR (значение 1): это типичная фатальная ошибка. Если вы видите этого плохого парня, ваше приложение готово. Перезагрузите и попробуйте еще раз.
- E_WARNING (2): это ошибки, которые не приводят к сбою вашего приложения. Похоже, что большинство ошибок находятся на этом уровне.
Далее у нас есть пользовательские ошибки:
- E_USER_ERROR (256): созданная пользователем версия указанной выше фатальной ошибки. Это часто создается с помощью trigger_error ().
- E_USER_NOTICE (1024): созданная пользователем версия информационного события. Обычно это не оказывает неблагоприятного воздействия на приложение, как и log.info ().
Последняя категория, на которую следует обратить внимание, — это ошибки жизненного цикла приложения, обычно с «core» или «compile» в названии:
- EE_CORE_ERROR (16): Подобно фатальным ошибкам выше, эта ошибка может возникнуть только при запуске приложения PHP.
- EE_COMPILE_WARNING (128): нефатальная ошибка, которая возникает только тогда, когда скрипт PHP не компилируется.
Есть еще несколько ошибок. Вы можете найти их полный список здесь.
Ошибки отображения PHP
Отображение сообщений об ошибках в PHP часто сбивает с толку. Просто погуглите «Отображение сообщения об ошибке PHP» и посмотрите. Почему так?
В PHP вы можете решить, отображать ли ошибки или нет. Это отличается от сообщения о них. Сообщение о них гарантирует, что ошибки не будут проглочены. Но отображение их покажет их пользователю. Вы можете настроить отображение всех ошибок PHP с помощью директив display_errors и display_startup_errors:
<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); ?>
Их включение гарантирует, что они будут отображаться в теле веб-ответа пользователю. Обычно рекомендуется отключать их в среде, не связанной с разработкой. Целочисленный параметр метода также является битовой маской, как в error_reporting(). Здесь также применяются те же правила и параметры для этого параметра.
Что такое предупреждение PHP?
Выше вы заметили, что одним из уровней ошибок является E_WARNING. Вы также могли заметить, что многие уровни ошибок имеют версии предупреждений. Я хочу немного в этом разобраться. Основное различие между предупреждением и ошибкой в PHP заключается в том, завершает ли оно приложение. В PHP большинство ошибок фактически не останавливают выполнение скрипта.
Вот пример:
<?php $x = 1; trigger_error("user warning!", E_USER_WARNING); $x = 3; echo "$x is ${$x}"; ?>
Вы все равно будете видеть, $x is 3несмотря на срабатывание предупреждения. Это может быть полезно, если вы хотите собрать список ошибок проверки. Я лично предпочитаю использовать исключения в наши дни, но ваш опыт может отличаться.
Конечно, вы можете настроить отображение предупреждений PHP или нет. Для этого вы должны использовать конфигурацию display_errors, которую вы видели в предыдущем разделе.
Как помогают отчеты о сбоях
PHP упрощает настройку внешних инструментов отчетов об ошибках, подобных тем, которые предлагает Raygun. Он предоставляет несколько различных ловушек для своей среды выполнения, чтобы обрабатывать ошибки и отправлять их по сети. См. Этот пример, взятый со страницы PHP Raygun :
namespace { // paste your 'requires' statement $client = new Raygun4phpRaygunClient("apikey for your application"); function error_handler($errno, $errstr, $errfile, $errline ) { global $client; $client->SendError($errno, $errstr, $errfile, $errline); } function exception_handler($exception) { global $client; $client->SendException($exception); } set_exception_handler('exception_handler'); set_error_handler("error_handler"); }
Сначала мы объявляем клиента, используя ключ API для безопасности:
$client = new Raygun4phpRaygunClient("apikey for your application");
Затем мы создаем пару функций, которые обрабатывают наши ошибки и исключения:
function error_handler($errno, $errstr, $errfile, $errline ) { global $client; $client->SendError($errno, $errstr, $errfile, $errline); } function exception_handler($exception) { global $client; $client->SendException($exception); }
Обратите внимание, что мы вызываем SendError()функцию, передавая некоторые важные сведения о структуре данных ошибки. Это сделает удаленный вызов Raygun.
Наконец, мы подключаем их к среде выполнения PHP, глобально обрабатывая как традиционные ошибки, так и новые исключения:
set_exception_handler('exception_handler'); set_error_handler("error_handler");
Вот и все. Имея все это на месте, мы можем получить красиво отформатированный отчет об ошибках, который может выглядеть следующим образом:
Завершение отчета об ошибках PHP
Как видите, отчеты об ошибках PHP просты. Вы можете инициировать исключения с помощью специальных функций. Вы также можете запускать исключения, как и в других типизированных языках.
Также вы можете легко подключить свои собственные обработчики и управлять отчетностью и отображением ошибок. Это позволяет нам подключить инструмент Raygun без особых усилий — не стесняйтесь подписаться на пробную версию Raygun и добавить ее в свое приложение за считанные минуты.

От автора: я опять сегодня не выспался! Вчера отлавливал ошибки в написанном скрипте, а сегодня комар жужжал под ухом всю ночь. И кажется, что это насекомое сродни тому багу, который я прошлой ночью еле нашел. В общем, сегодня (раз уж мне не спится) узнаем о кодах ошибок в PHP.
Если их не видно
Причин, почему ошибки не выводятся на экране, может быть несколько:
Их вывод отключен хостером специально – таким образом он заботится о повышении безопасности своего серверного пространства. В том числе и вашего ресурса. И все потому, что в описаниях некоторых ошибок отладчик выводит конфиденциальную информацию. Умелый хакер сможет легко использовать ее для взлома вашего сайта. Например, при указании неправильного пароля при подключении к базе, расположенной на сервере MySQL. В результате на экран выводится описание ошибки, содержащее логин активного пользователя СУБД.
В файле .htaccess.
В настройках ядра языка.

Бесплатный курс по PHP программированию
Освойте курс и узнайте, как создать веб-приложение на PHP с полного нуля
Получить курс сейчас!
Все эти «частные» случаи мы рассматривали в одном из наших предыдущих материалов. Но так как нами используется Денвер, то я кратко опишу решение данной проблемы в нем.
Перед тем, как найти ошибку коде PHP, сначала нужно ее вывести на экран. Если это не происходит, тогда прямая дорога в php.ini.Здесь нужно активировать значения нескольких параметров:
display_errors.
error_reporting.
display_startup_errors.
Данные настройки расположены в разделе «Error handling and logging».

Логов нет!
Как отмечалось ранее, в большинстве случает на хостингах вывод программных багов на веб-страницах отключен. Но это не значит, что они недоступны для отслеживания и отладки владельцам ресурсов. Все данные об ошибках записываются в файлы логов.
В Денвере данная функция (по умолчанию) отключена. Это можно легко исправить, прописав нужный код в конфигурационном файле (в том же разделе).
Снова откройте php.ini и внесите в него выделенные ниже строки. Точнее, одна из них уже должна быть, но лучше проверить. С помощью этих директив (log_errors и error_log) разрешается запись сообщений обо всех ошибках. А также задается путь к файлу логов, с помощью которого вы сможете осуществить PHP проверку кода на ошибки.

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

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

Как видим, в него записалась вся информация о допущенном баге. Здесь же мы натыкаемся на «упоминание» об одном из основных типов ошибок – parse error. Это говорит о том, что мы нарушили синтаксическое правило языка (объявление переменных).
Как можно еще проверить PHP код на ошибки? Применить функцию set_error_handler(), которая позволяет задать пользовательский обработчик. Но ее использование при включенном родном handler не имеет смысла. Кроме этого пользователь может перехватывать не все типы ошибок. Например, фатальные error вам (как и мне) переопределить не удастся. Пример:
|
<?php error_reporting(0); function user_handler($n, $str,$file, $line) { echo «Номер: «.$n.«<br />». «Описание: «.$str.» <br />». «Имя файла: «.$file.«<br />». «Строка кода: «.$line; return true; } set_error_handler(‘user_handler’, E_ALL); require(«nofile.php»); ?> |

Сначала мы отключили нативный обработчик (error_reporting(0)). Затем создали собственную функцию и передали ее в качестве аргумента set_error_handler(). После чего инициировали ошибку, подключив несуществующий «в природе» файл.
Онлайн
Осуществить проверку кода PHP на ошибки также можно с помощью специализированных сервисов. Вот несколько из них: codepad, PHP Assist
А возле меня почему-то комаров стало больше! Или это звездочки летают? Пойду, наверное, посплю, пока ко мне дичь посерьезней (белочка) не прискакала :). Спокойной ночи!

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

Разработка веб-приложения на PHP
Скачайте видеокурс и узнайте, как создать веб-приложение на PHP
Скачать
