Оператор управления ошибками
PHP поддерживает один оператор управления ошибками: знак @
.
В случае, если он предшествует какому-либо выражению в PHP-коде, любые
сообщения об ошибках, генерируемые этим выражением, будут подавлены.
Если пользовательская функция обработчика ошибок установлена с помощью
set_error_handler(), она всё равно будет вызываться,
даже если диагностика была подавлена.
Внимание
До версии PHP 8.0.0 функция error_reporting(), вызываемая внутри пользовательского обработчика ошибок,
всегда возвращала 0
, если ошибка была подавлена оператором @
.
Начиная с PHP 8.0.0, она возвращает значение E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE
.
Любое сообщение об ошибке, сгенерированное выражением, доступно
в элементе массива "message"
, возвращаемого error_get_last().
Результат этой функции будет меняться при каждой ошибке, поэтому его необходимо проверить заранее.
<?php
// Преднамеренная ошибка при работе с файлами
$my_file = @file ('non_existent_file') or
die ("Ошибка при открытии файла: сообщение об ошибке было таким: '" . error_get_last()['message'] . "'");// работает для любых выражений, а не только для функций
$value = @$cache[$key];
// В случае если ключа $key нет, сообщение об ошибке (notice) не будет отображено?>
Замечание:
Оператор@
работает только с
выражениями.
Есть простое правило: если что-то возвращает
значение, значит вы можете использовать перед ним оператор
@
. Например, вы можете использовать@
перед
именем переменной, произвольной функцией или вызовом include и так далее. В то же время вы не можете использовать этот оператор
перед определением функции или класса, условными конструкциями, такими какif
,
foreach и т.д.
Внимание
До PHP 8.0.0 оператор @
мог подавлять критические ошибки, которые прерывали выполнение скрипта.
Например, добавление @
к вызову несуществующей функции,
в случае, если она недоступна или написана неправильно, дальнейшая
работа скрипта приведёт к прерыванию выполнения скрипта без каких-либо уведомлений.
taras dot dot dot di at gmail dot com ¶
14 years ago
I was confused as to what the @ symbol actually does, and after a few experiments have concluded the following:
* the error handler that is set gets called regardless of what level the error reporting is set on, or whether the statement is preceeded with @
* it is up to the error handler to impart some meaning on the different error levels. You could make your custom error handler echo all errors, even if error reporting is set to NONE.
* so what does the @ operator do? It temporarily sets the error reporting level to 0 for that line. If that line triggers an error, the error handler will still be called, but it will be called with an error level of 0
Hope this helps someone
M. T. ¶
13 years ago
Be aware of using error control operator in statements before include() like this:
<?PHP(@include("file.php"))
OR die("Could not find file.php!");?>
This cause, that error reporting level is set to zero also for the included file. So if there are some errors in the included file, they will be not displayed.
Anonymous ¶
9 years ago
This operator is affectionately known by veteran phpers as the stfu operator.
anthon at piwik dot org ¶
12 years ago
If you're wondering what the performance impact of using the @ operator is, consider this example. Here, the second script (using the @ operator) takes 1.75x as long to execute...almost double the time of the first script.
So while yes, there is some overhead, per iteration, we see that the @ operator added only .005 ms per call. Not reason enough, imho, to avoid using the @ operator.
<?php
function x() { }
for ($i = 0; $i < 1000000; $i++) { x(); }
?>
real 0m7.617s
user 0m6.788s
sys 0m0.792s
vs
<?php
function x() { }
for ($i = 0; $i < 1000000; $i++) { @x(); }
?>
real 0m13.333s
user 0m12.437s
sys 0m0.836s
gerrywastaken ¶
14 years ago
Error suppression should be avoided if possible as it doesn't just suppress the error that you are trying to stop, but will also suppress errors that you didn't predict would ever occur. This will make debugging a nightmare.
It is far better to test for the condition that you know will cause an error before preceding to run the code. This way only the error that you know about will be suppressed and not all future errors associated with that piece of code.
There may be a good reason for using outright error suppression in favor of the method I have suggested, however in the many years I've spent programming web apps I've yet to come across a situation where it was a good solution. The examples given on this manual page are certainly not situations where the error control operator should be used.
jcmargentina at gmail dot com ¶
3 years ago
Please be aware that the behaviour of this operator changed from php5 to php7.
The following code will raise a Fatal error no matter what, and you wont be able to suppress it
<?phpfunction query()
{
$myrs = null;
$tmp = @$myrs->free_result();
return
$tmp;
}var_dump(query());
echo
"THIS IS NOT PRINT";
?>
more info at: https://bugs.php.net/bug.php?id=78532&thanks=3
dkellner ¶
6 years ago
There is no reason to NOT use something just because "it can be misused". You could as well say "unlink is evil, you can delete files with it so don't ever use unlink".
It's a valid point that the @ operator hides all errors - so my rule of thumb is: use it only if you're aware of all possible errors your expression can throw AND you consider all of them irrelevant.
A simple example is
<?php
$x
= @$a["name"];?>
There are only 2 possible problems here: a missing variable or a missing index. If you're sure you're fine with both cases, you're good to go. And again: suppressing errors is not a crime. Not knowing when it's safe to suppress them is definitely worse.
man13or at hotmail dot fr ¶
3 years ago
Quick debugging methods :
@print($a);
is equivalent to
if isset($a) echo $a ;
@a++;
is equivalent to
if isset($a) $a++ ;
else $a = 1;
Ryan C ¶
2 years ago
It's still possible to detect when the @ operator is being used in the error handler in PHP8. Calling error_reporting() will no longer return 0 as documented, but using the @ operator does still change the return value when you call error_reporting().
My PHP error settings are set to use E_ALL, and when I call error_reporting() from the error handler of a non-suppressed error, it returns E_ALL as expected.
But when an error occurs on an expression where I tried to suppress the error with the @ operator, it returns: E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR (or the number 4437).
I didn't want to use 4437 in my code in case it changes with different settings or future versions of PHP, so I now use:
<?php
function my_error_handler($err_no, $err_msg, $filename, $linenum) {
if (error_reporting() != E_ALL) {
return false; // Silenced
}// ...
}
?>
If the code needs to work with all versions of PHP, you could check that error_reporting() doesn't equal E_ALL or 0.
And, of course, if your error_reporting settings in PHP is something other than E_ALL, you'll have to change that to whatever setting you do use.
darren at powerssa dot com ¶
12 years ago
After some time investigating as to why I was still getting errors that were supposed to be suppressed with @ I found the following.
1. If you have set your own default error handler then the error still gets sent to the error handler regardless of the @ sign.
2. As mentioned below the @ suppression only changes the error level for that call. This is not to say that in your error handler you can check the given $errno for a value of 0 as the $errno will still refer to the TYPE(not the error level) of error e.g. E_WARNING or E_ERROR etc
3. The @ only changes the rumtime error reporting level just for that one call to 0. This means inside your custom error handler you can check the current runtime error_reporting level using error_reporting() (note that one must NOT pass any parameter to this function if you want to get the current value) and if its zero then you know that it has been suppressed.
<?php
// Custom error handler
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
if ( 0 == error_reporting () ) {
// Error reporting is currently turned off or suppressed with @
return;
}
// Do your normal custom error reporting here
}
?>
For more info on setting a custom error handler see: http://php.net/manual/en/function.set-error-handler.php
For more info on error_reporting see: http://www.php.net/manual/en/function.error-reporting.php
auser at anexample dot com ¶
12 years ago
Be aware that using @ is dog-slow, as PHP incurs overhead to suppressing errors in this way. It's a trade-off between speed and convenience.
bohwaz ¶
11 years ago
If you use the ErrorException exception to have a unified error management, I'll advise you to test against error_reporting in the error handler, not in the exception handler as you might encounter some headaches like blank pages as error_reporting might not be transmitted to exception handler.
So instead of :
<?phpfunction exception_error_handler($errno, $errstr, $errfile, $errline )
{
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}set_error_handler("exception_error_handler");
function
catchException($e)
{
if (error_reporting() === 0)
{
return;
}// Do some stuff
}set_exception_handler('catchException');?>
It would be better to do :
<?phpfunction exception_error_handler($errno, $errstr, $errfile, $errline )
{
if (error_reporting() === 0)
{
return;
}
throw new
ErrorException($errstr, 0, $errno, $errfile, $errline);
}set_error_handler("exception_error_handler");
function
catchException($e)
{
// Do some stuff
}set_exception_handler('catchException');?>
programming at kennebel dot com ¶
16 years ago
To suppress errors for a new class/object:
<?php
// Tested: PHP 5.1.2 ~ 2006-10-13
// Typical Example
$var = @some_function();// Class/Object Example
$var = @new some_class();// Does NOT Work!
//$var = new @some_class(); // syntax error
?>
I found this most useful when connecting to a
database, where i wanted to control the errors
and warnings displayed to the client, while still
using the class style of access.
frogger at netsurf dot de ¶
18 years ago
Better use the function trigger_error() (http://de.php.net/manual/en/function.trigger-error.php)
to display defined notices, warnings and errors than check the error level your self. this lets you write messages to logfiles if defined in the php.ini, output
messages in dependency to the error_reporting() level and suppress output using the @-sign.
karst dot REMOVETHIS at onlinq dot nl ¶
8 years ago
While you should definitely not be too liberal with the @ operator, I also disagree with people who claim it's the ultimate sin.
For example, a very reasonable use is to suppress the notice-level error generated by parse_ini_file() if you know the .ini file may be missing.
In my case getting the FALSE return value was enough to handle that situation, but I didn't want notice errors being output by my API.
TL;DR: Use it, but only if you know what you're suppressing and why.
ricovox ¶
6 years ago
What is PHP's behavior for a variable that is assigned the return value of an expression protected by the Error Control Operator when the expression encounteres an error?
Based on the following code, the result is NULL (but it would be nice if this were confirmed to be true in all cases).
<?php
$var
= 3;
$arr = array(); $var = @$arr['x']; // what is the value of $var after this assignment?
// is it its previous value (3) as if the assignment never took place?
// is it FALSE or NULL?
// is it some kind of exception or error message or error number?
var_dump($var); // prints "NULL"?>
fy dot kenny at gmail dot com ¶
2 years ago
* How to make deprecated super global variable `$php_errormsg` work
>1. modify php.ini
>track_errors = On
>error_reporting = E_ALL & ~E_NOTICE
>2. Please note,if you already using customized error handler,it will prompt `undefined variable`
>please insert code`set_error_handler(null);` before executing code, e.g:
>```php
>set_error_handler(null);
>$my_file = @file ('phpinfo.phpx') or
>die ("<br>Failed opening file: <br>t$php_errormsg");
>```
>(c)Kenny Fang
manisha at mindfiresolutions dot com ¶
8 years ago
Prepending @ before statement like you are doing a crime with yourself.
Joey ¶
5 years ago
In PHP it is extremely beneficial to turn all notices into exceptions. This helps in creating bug free code through finding errors sooner rather than later. It also helps reduce the impact of bugs as code when entering an erroneous state ends sooner rather than later and at all. In the worst case without that you can have much more scenarios where code fails yet appears as though it succeeded.
However there are rare cases in which notices and warnings are produced where the above behavour might be unproductive. Worse yet your error handling will kick out that exception before the function gets to return.
They are rare cases such as socket handling where certain states are expressed through errors causing ambiguity.
Anonymous ¶
9 years ago
I was wondering if anyone (else) might find a directive to disable/enable to error operator would be a useful addition. That is, instead of something like (which I have seen for a few places in some code):
<?phpif (defined(PRODUCTION)) {
@function();
}
else {
function();
}?>
There could be something like this:
<?phpif (defined(PRODUCTION)) {
ini_set('error.silent',TRUE);
}
else {
ini_set('error.silent',FALSE);
}?>
nospam at blog dot fileville dot net ¶
16 years ago
If you want to log all the error messages for a php script from a session you can use something like this:
<?php
session_start();
function error($error, $return=FALSE) {
global $php_errormsg;
if(isset($_SESSION['php_errors'])) {
$_SESSION['php_errors'] = array();
}
$_SESSION['php_errors'][] = $error; // Maybe use $php_errormsg
if($return == TRUE) {
$message = "";
foreach($_SESSION['php_errors'] as $php_error) {
$messages .= $php_error."n";
}
return $messages; // Or you can use use $_SESSION['php_errors']
}
}
?>
Hope this helps someone...
Anonymous ¶
16 years ago
error_reporting()==0 for detecting the @ error suppression assumes that you did not set the error level to 0 in the first place.
However, typically if you want to set your own error handler, you would set the error_reporting to 0. Therefore, an alternative to detect the @ error suppression is required.
me at hesterc dot fsnet dot co dot uk ¶
18 years ago
If you wish to display some text when an error occurs, echo doesn't work. Use print instead. This is explained on the following link 'What is the difference between echo and print?':
http://www.faqts.com/knowledge_base/view.phtml/aid/1/fid/40
It says "print can be used as part of a more complex expression where echo cannot".
Also, you can add multiple code to the result when an error occurs by separating each line with "and". Here is an example:
<?php
$my_file = @file ('non_existent_file') or print 'File not found.' and $string = ' Honest!' and print $string and $fp = fopen ('error_log.txt', 'wb+') and fwrite($fp, $string) and fclose($fp);
?>
A shame you can't use curly brackets above to enclose multiple lines of code, like you can with an if statement or a loop. It could make for a single long line of code. You could always call a function instead.
I have some PHP code. When I run it, a warning message appears.
How can I remove/suppress/ignore these warning messages?
asked Jan 1, 2010 at 0:32
1
You really should fix whatever’s causing the warning, but you can control visibility of errors with error_reporting()
. To skip warning messages, you could use something like:
error_reporting(E_ERROR | E_PARSE);
Sean Bright
118k17 gold badges138 silver badges145 bronze badges
answered Jan 1, 2010 at 0:37
Tatu UlmanenTatu Ulmanen
123k34 gold badges186 silver badges184 bronze badges
4
You can put an @ in front of your function call to suppress all error messages.
@yourFunctionHere();
Mark Amery
141k78 gold badges402 silver badges457 bronze badges
answered Jan 1, 2010 at 0:41
PetPaulsenPetPaulsen
3,3922 gold badges22 silver badges33 bronze badges
11
To suppress warnings while leaving all other error reporting enabled:
error_reporting(E_ALL ^ E_WARNING);
Mark Amery
141k78 gold badges402 silver badges457 bronze badges
answered Feb 11, 2011 at 8:08
KarthikKarthik
1,4183 gold badges17 silver badges29 bronze badges
If you don’t want to show warnings as well as errors use
// Turn off all error reporting
error_reporting(0);
Error Reporting — PHP Manual
MD XF
7,8027 gold badges40 silver badges71 bronze badges
answered Jan 22, 2013 at 3:16
mohan.gademohan.gade
1,0951 gold badge9 silver badges15 bronze badges
0
If you want to suppress the warnings and some other error types (for example, notices) while displaying all other errors, you can do:
error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);
answered Jan 10, 2018 at 17:13
zstatezstate
1,9851 gold badge18 silver badges20 bronze badges
in Core Php to hide warning message set error_reporting(0) at top of common include file or individual file.
In WordPress hide Warnings and Notices add following code in wp-config.php file
ini_set('log_errors','On');
ini_set('display_errors','Off');
ini_set('error_reporting', E_ALL );
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
answered May 12, 2017 at 5:04
1
I do it as follows in my php.ini:
error_reporting = E_ALL & ~E_WARNING & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
This logs only fatal errors and no warnings.
honk
9,05711 gold badges74 silver badges83 bronze badges
answered Feb 27, 2018 at 8:43
navidnavid
9528 silver badges19 bronze badges
0
Not exactly answering the question, but I think this is a better compromise in some situations:
I had a warning message as a result of a printf() statement in a third-party library. I knew exactly what the cause was — a temporary work-around while the third-party fixed their code. I agree that warnings should not be suppressed, but I could not demonstrate my work to a client with the warning message popping up on screen. My solution:
printf('<div style="display:none">');
...Third-party stuff here...
printf('</div>');
Warning was still in page source as a reminder to me, but invisible to the client.
FelixSFD
5,98210 gold badges43 silver badges115 bronze badges
answered Dec 30, 2012 at 20:03
DaveWalleyDaveWalley
80710 silver badges22 bronze badges
4
I think that better solution is configuration of .htaccess In that way you dont have to alter code of application. Here are directives for Apache2
php_flag display_startup_errors off
php_flag display_errors off
php_flag html_errors off
php_value docref_root 0
php_value docref_ext 0
answered May 10, 2014 at 16:34
You could suppress the warning using error_reporting but the much better way is to fix your script in the first place.
Dharman♦
30.4k22 gold badges84 silver badges132 bronze badges
answered Jan 1, 2010 at 0:34
PekkaPekka
440k141 gold badges972 silver badges1085 bronze badges
1
There is already answer with Error Control Operator but it lacks of explanation. You can use @
operator with every expression and it hides errors (except of Fatal Errors).
@$test['test']; //PHP Notice: Undefined variable: test
@(14/0); // PHP Warning: Division by zero
//This is not working. You can't hide Fatal Errors this way.
@customFuntion(); // PHP Fatal error: Uncaught Error: Call to undefined function customFuntion()
For debugging it’s fast and perfect method. But you should never ever use it on production nor permanent include in your local version. It will give you a lot of unnecessary irritation.
You should consider instead:
1. Error reporting settings as mentioned in accepted answer.
error_reporting(E_ERROR | E_PARSE);
or from PHP INI settings
ini_set('display_errors','Off');
2. Catching exceptions
try {
$var->method();
} catch (Error $e) {
// Handle error
echo $e->getMessage();
}
answered May 24, 2020 at 3:28
JsowaJsowa
8,8145 gold badges53 silver badges60 bronze badges
PHP поддерживает один оператор управления ошибками: знак (@).
В случае, если он предшествует какому-либо выражению в PHP-коде, любые
сообщения об ошибках, генерируемые этим выражением, будут проигнорированы.
Если вы установили собственную функцию обработки ошибок с помощью
set_error_handler(), то она все равно будет вызвана,
однако, если внутри этой функции будет вызвана функция
error_reporting(), то она вернет 0, если функция,
вызвавшая данную ошибку, была подавлена с помощью @.
В случае, если установлена опция track_errors
,
все генерируемые сообщения об ошибках будут сохраняться в переменной
$php_errormsg.
Эта переменная будет перезаписываться при каждой новой ошибке,
поэтому в случае необходимости проверяйте ее сразу же.
<?php
// Преднамеренная ошибка при работе с файлами
$my_file = @file ('non_existent_file') or
die ("Ошибка при открытии файла: сообщение об ошибке было таким: '$php_errormsg'");// работает для любых выражений, а не только для функций
$value = @$cache[$key];
// В случае если ключа $key нет, сообщение об ошибке (notice) не будет отображено?>
Замечание:
Оператор @ работает только с
выражениями.
Есть простое правило: если что-то возвращает
значение, значит вы можете использовать перед ним оператор
@. Например, вы можете использовать @ перед
именем переменной, произвольной функцией или вызовом include,
константой и так далее. В то же время вы не можете использовать этот оператор
перед определением функции или класса, условными конструкциями, такими как if,
foreach и т.д.
Также ознакомьтесь с описанием функции error_reporting()
и разделом руководства
Обработка ошибок и функции логирования.
Внимание
На сегодняшний день оператор «@» подавляет
вывод сообщений даже о критических ошибках, прерывающих работу
скрипта. Помимо всего прочего, это означает, что если вы использовали
«@» для подавления ошибок, возникающих при работе какой-либо
функции, в случае если она недоступна или написана неправильно, дальнейшая
работа скрипта будет остановлена без каких-либо уведомлений.
Вернуться к: Операторы
When you are sure your script is perfectly working, you can get rid of warning and notices like this: Put this line at the beginning of your PHP script:
error_reporting(E_ERROR);
Before that, when working on your script, I would advise you to properly debug your script so that all notice or warning disappear one by one.
So you should first set it as verbose as possible with:
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
UPDATE: how to log errors instead of displaying them
As suggested in the comments, the better solution is to log errors into a file so only the PHP developer sees the error messages, not the users.
A possible implementation is via the .htaccess file, useful if you don’t have access to the php.ini file (source).
# Suppress PHP errors
php_flag display_startup_errors off
php_flag display_errors off
php_flag html_errors off
php_value docref_root 0
php_value docref_ext 0
# Enable PHP error logging
php_flag log_errors on
php_value error_log /home/path/public_html/domain/PHP_errors.log
# Prevent access to PHP error log
<Files PHP_errors.log>
Order allow,deny
Deny from all
Satisfy All
</Files>
Антон Шевчук // Web-разработчик
Не совершает ошибок только тот, кто ничего не делает, и мы тому пример – трудимся не покладая рук над созданием рабочих мест для тестировщиков
О да, в этой статье я поведу свой рассказа об ошибках в PHP, и том как их обуздать.
Ошибки
Разновидности в семействе ошибок
Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.
Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:
<?php error_reporting(E_ALL); ini_set('display_errors', 1);
Фатальные ошибки
Самый грозный вид ошибок – фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.
E_PARSE
Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:
<?php /** Parse error: syntax error, unexpected end of file */ {
Или написали на непонятном языке:
<?php /** Parse error: syntax error, unexpected '...' (T_STRING) */ Тут будет ошибка парсера
Лишние скобочки тоже встречаются, и не важно круглые либо фигурные:
<?php /** Parse error: syntax error, unexpected '}' */ }
Отмечу один важный момент – код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:
<?php // этот код не сработает error_reporting(E_ALL); ini_set('display_errors', 1); // т.к. вот тут ошибка парсера
E_ERROR
Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин, так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:
Не был найден подключаемый файл:
/** Fatal error: require_once(): Failed opening required 'not-exists.php' (include_path='.:/usr/share/php:/usr/share/pear') */ require_once 'not-exists.php';
Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:
/** Fatal error: Uncaught exception 'Exception' */ throw new Exception();
При попытке вызвать несуществующий метод класса:
/** Fatal error: Call to undefined method stdClass::notExists() */ $stdClass = new stdClass(); $stdClass->notExists();
Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:
/** Fatal Error: Allowed Memory Size */ $arr = array(); while (true) { $arr[] = str_pad(' ', 1024); }
Очень часто происходит при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти
Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug:
/** Fatal error: Maximum function nesting level of '256' reached, aborting! */ function deep() { deep(); } deep();
Не фатальные
Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик, и именно они доставляют больше всего хлопот у начинающих разработчиков.
E_WARNING
Частенько встречается, когда подключаешь файл с использованием include
, а его не оказывается на сервере или ошиблись указывая путь к файлу:
/** Warning: include_once(): Failed opening 'not-exists.php' for inclusion */ include_once 'not-exists.php';
Бывает, если используешь неправильный тип аргументов при вызове функций:
/** Warning: join(): Invalid arguments passed */ join('string', 'string');
Их очень много, и перечислять все не имеет смысла…
E_NOTICE
Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.
Когда обращаются к неопределенной переменной:
/** Notice: Undefined variable: a */ echo $a;
Когда обращаются к несуществующему элементу массива:
<?php /** Notice: Undefined index: a */ $b = array(); $b['a'];
Когда обращаются к несуществующей константе:
/** Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT' */ echo UNKNOWN_CONSTANT;
Когда не конвертируют типы данных:
/** Notice: Array to string conversion */ echo array();
Для избежания подобных ошибок – будьте внимательней, и если вам IDE подсказывает о чём-то – не игнорируйте её:
E_STRICT
Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывают. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this
:
/** Strict standards: Non-static method Strict::test() should not be called statically */ class Strict { public function test() { echo 'Test'; } } Strict::test();
E_DEPRECATED
Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):
/** Deprecated: Function split() is deprecated */ // популярная функция, всё никак не удалят из PHP // deprecated since 5.3 split(',', 'a,b');
В моём редакторе подобные функции будут зачёркнуты:
Обрабатываемые
Этот вид, которые разводит сам разработчик кода, я их уже давно не встречал, не рекомендую их вам заводить:
E_USER_ERROR
– критическая ошибкаE_USER_WARNING
– не критическая ошибкаE_USER_NOTICE
– сообщения которые не являются ошибками
Отдельно стоит отметить E_USER_DEPRECATED
– этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():
/** * @deprecated Deprecated since version 1.2, to be removed in 2.0 */ function generateToken() { trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED); // ... // code ... // ... }
Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы
display_errors
:
- если
display_errors = on
, то в случае ошибки браузер получит html c текстом ошибки и кодом 200- если же
display_errors = off
, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок – код будет работать неправильно, но никому об этом не расскажет
Приручение
Для работы с ошибками в PHP существует 3 функции:
- set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
- error_get_last() — получает информацию о последней ошибке
- register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого
Теперь немного подробностей об обработке ошибок с использованием set_error_handler()
, в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:
$errno
– первый аргумент содержит тип ошибки в виде целого числа$errstr
– второй аргумент содержит сообщение об ошибке$errfile
– необязательный третий аргумент содержит имя файла, в котором произошла ошибка$errline
– необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка$errcontext
– необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка
В случае если обработчик вернул true
, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:
<?php // включаем отображение всех ошибок, кроме E_NOTICE error_reporting(E_ALL & ~E_NOTICE); ini_set('display_errors', 1); // наш обработчик ошибок function myHandler($level, $message, $file, $line, $context) { // в зависимости от типа ошибки формируем заголовок сообщения switch ($level) { case E_WARNING: $type = 'Warning'; break; case E_NOTICE: $type = 'Notice'; break; default; // это не E_WARNING и не E_NOTICE // значит мы прекращаем обработку ошибки // далее обработка ложится на сам PHP return false; } // выводим текст ошибки echo "<h2>$type: $message</h2>"; echo "<p><strong>File</strong>: $file:$line</p>"; echo "<p><strong>Context</strong>: $". join(', $', array_keys($context))."</p>"; // сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется return true; } // регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок set_error_handler('myHandler', E_ALL);
У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет – пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём
С обработчиком, который написан выше есть одна существенная проблема – он не ловит фатальные ошибки, и вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:
function shutdown() { echo 'Этот текст будет всегда отображаться'; } register_shutdown_function('shutdown');
Данная функция будет срабатывать всегда!
Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль “последних”:
function shutdown() { $error = error_get_last(); if ( // если в коде была допущена ошибка is_array($error) && // и это одна из фатальных ошибок in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR]) ) { // очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях) while (ob_get_level()) { ob_end_clean(); } // выводим описание проблемы echo 'Сервер находится на техническом обслуживании, зайдите позже'; } } register_shutdown_function('shutdown');
Задание
Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.
О прожорливости
Проведём простой тест, и выясним – сколько драгоценных ресурсов кушает самая тривиальная ошибка:
/** * Этот код не вызывает ошибок */ // сохраняем параметры памяти и времени выполнения скрипта $memory = memory_get_usage(); $time= microtime(true); $a = ''; $arr = []; for ($i = 0; $i < 10000; $i++) { $arr[$a] = $i; } printf('%f seconds <br/>', microtime(true) - $time); echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
В результате запуска данного скрипта у меня получился вот такой результат:
0.002867 seconds 984 bytes
Теперь добавим ошибку в цикле:
/** * Этот код содержит ошибку */ // сохраняем параметры памяти и времени выполнения скрипта $memory = memory_get_usage(); $time= microtime(true); $a = ''; $arr = []; for ($i = 0; $i < 10000; $i++) { $arr[$b] = $i; // тут ошиблись с именем переменной } printf('%f seconds <br/>', microtime(true) - $time); echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
Результат ожидаемо хуже, и на порядок (даже на два порядка!):
0.263645 seconds 992 bytes
Вывод однозначен – ошибки в коде приводят к лишней прожорливости скриптов – так что во время разработки и тестирования приложения включайте отображение всех ошибок!
Тестирование проводил на PHP версии 5.6, в седьмой версии результат лучше – 0.0004 секунды против 0.0050 – разница только на один порядок, но в любом случае результат стоит прикладываемых усилий по исправлению ошибок
Где собака зарыта
В PHP есть спец символ «@» – оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:
<?php echo @UNKNOWN_CONSTANT;
При этом обработчик ошибок указанный в set_error_handler()
всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting()
внутри обработчика, в этом случае она вернёт 0
.
Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло
Исключения
В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Думаю да, ведь это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений
Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.
К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение – сохранить в другое место или сообщить пользователю о проблеме.
Исключение – это объект который наследуется от класса Exception
, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, “бросить”) при помощи оператора throw
, и можно перехватить (“поймать”) оператором catch
. Код генерирующий исключение, должен быть окружен блоком try
, для того чтобы можно было перехватить исключение. Каждый блок try
должен иметь как минимум один соответствующий ему блок catch
или finally
:
try { // код который может выбросить исключение if (rand(0, 1)) { throw new Exception('One') } else { echo 'Zero'; } } catch (Exception $e) { // код который может обработать исключение echo $e->getMessage(); }
В каких случаях стоит применять исключения:
- если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
- если используемый вами фреймверк или библиотека декларируют их использование
Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл – помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:
$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs'; // директории может не быть if (!is_dir($directory)) { throw new Exception('Directory `logs` is not exists'); } // может не быть прав на запись в директорию if (!is_writable($directory)) { throw new Exception('Directory `logs` is not writable'); } // возможно кто-то уже создал файл, и закрыл к нему доступ if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) { throw new Exception('System can't create log file'); } fputs($file, date('[H:i:s]') . " donen"); fclose($file);
Соответственно ловить данные исключения будем примерно так:
try { // код который пишет в файл // ... } catch (Exception $e) { // выводим текст ошибки echo 'Не получилось: '. $e->getMessage(); }
В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую – различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:
// исключения файловой системы class FileSystemException extends Exception {} // исключения связанные с директориями class DirectoryException extends FileSystemException { // коды исключений const DIRECTORY_NOT_EXISTS = 1; const DIRECTORY_NOT_WRITABLE = 2; } // исключения связанные с файлами class FileException extends FileSystemException {}
Теперь, если использовать эти исключения то можно получить следующий код:
try { // код который пишет в файл if (!is_dir($directory)) { throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS); } if (!is_writable($directory)) { throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE); } if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) { throw new FileException('System can't open log file'); } fputs($file, date('[H:i:s]'') . " donen"); fclose($file); } catch (DirectoryException $e) { echo 'С директорией возникла проблема: '. $e->getMessage(); } catch (FileException $e) { echo 'С файлом возникла проблема: '. $e->getMessage(); } catch (FileSystemException $e) { echo 'Ошибка файловой системы: '. $e->getMessage(); } catch (Exception $e) { echo 'Ошибка сервера: '. $e->getMessage(); }
Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.
Так, а что будет если не поймать исключение? Вы получите “Fatal Error: Uncaught exception …”. Неприятно.
Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:
// в качестве обработчика событий // будем использовать анонимную функцию set_exception_handler(function($exception) { /** @var Exception $exception */ echo $exception->getMessage(), "<br/>n"; echo $exception->getFile(), ':', $exception->getLine(), "<br/>n"; echo $exception->getTraceAsString(), "<br/>n"; });
Ещё расскажу про конструкцию с использованием блока finally
– этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:
try { // код который может выбросить исключение } catch (Exception $e) { // код который может обработать исключение // если конечно оно появится } finally { // код, который будет выполнен при любом раскладе }
Для понимания того, что это нам даёт приведу следующий пример использования блока finally
:
try { // где-то глубоко внутри кода // соединение с базой данных $handler = mysqli_connect('localhost', 'root', '', 'test'); try { // при работе с БД возникла исключительная ситуация // ... throw new Exception('DB error'); } catch (Exception $e) { // исключение поймали, обработали на своём уровне // и должны его пробросить вверх, для дальнейшей обработки throw new Exception('Catch exception', 0, $e); } finally { // но, соединение с БД необходимо закрыть // будем делать это в блоке finally mysqli_close($handler); } // этот код не будет выполнен, если произойдёт исключение в коде выше echo "Ok"; } catch (Exception $e) { // ловим исключение, и выводим текст echo $e->getMessage(); echo "<br/>"; // выводим информацию о первоначальном исключении echo $e->getPrevious()->getMessage(); }
Т.е. запомните – блок finally
будет выполнен даже в том случае, если вы в блоке catch
пробрасываете исключение выше (собственно именно так он и задумывался).
Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код
Задание
Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира – посмотрите как это круто выглядит у whoops.
PHP7 – всё не так, как было раньше
Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы столкнётесь через год работы PHP разработчиком. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот – в PHP7 это решили исправить, но как обычно завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:
- при возникновении фатальных ошибок типа
E_ERROR
или фатальных ошибок с возможностью обработкиE_RECOVERABLE_ERROR
PHP выбрасывает исключение - эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
- эти исключения наследуют класс Error
- оба класса Exception и Error реализуют интерфейс Throwable
- вы не можете реализовать интерфейс Throwable в своём коде
Интерфейс Throwable
практически полностью повторяет нам Exception
:
interface Throwable { public function getMessage(): string; public function getCode(): int; public function getFile(): string; public function getLine(): int; public function getTrace(): array; public function getTraceAsString(): string; public function getPrevious(): Throwable; public function __toString(): string; }
Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:
try { // файл, который вызывает ошибку парсера include 'e_parse_include.php'; } catch (Error $e) { var_dump($e); }
В результате ошибку поймаем и выведем:
object(ParseError)#1 (7) { ["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)" ["string":"Error":private] => string(0) "" ["code":protected] => int(0) ["file":protected] => string(49) "/www/education/error/e_parse_include.php" ["line":protected] => int(4) ["trace":"Error":private] => array(0) { } ["previous":"Error":private] => NULL }
Как видите – поймали исключение ParseError, которое является наследником исключения Error
, который реализует интерфейс Throwable
, в доме который построил Джек. Ещё есть другие, но не буду мучать – для наглядности приведу иерархию исключений:
interface Throwable |- Exception implements Throwable | |- ErrorException extends Exception | |- ... extends Exception | `- ... extends Exception `- Error implements Throwable |- TypeError extends Error |- ParseError extends Error |- ArithmeticError extends Error | `- DivisionByZeroError extends ArithmeticError `- AssertionError extends Error
TypeError – для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:
try { (function(int $one, int $two) { return; })('one', 'two'); } catch (TypeError $e) { echo $e->getMessage(); }
ArithmeticError – могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:
try { 1 << -1; } catch (ArithmeticError $e) { echo $e->getMessage(); }
DivisionByZeroError – ошибка деления на ноль:
try { 1 / 0; } catch (ArithmeticError $e) { echo $e->getMessage(); }
AssertionError – редкий зверь, появляется когда условие заданное в assert() не выполняется:
ini_set('zend.assertions', 1); ini_set('assert.exception', 1); try { assert(1 === 0); } catch (AssertionError $e) { echo $e->getMessage(); }
При настройках production-серверов, директивы
zend.assertions
иassert.exception
отключают, и это правильно
Задание
Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.
При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7
Отладка
Иногда для отладки кода нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:
<?php function example() { echo '<pre>'; debug_print_backtrace(); echo '</pre>'; } class ExampleClass { public static function method () { example(); } } ExampleClass::method();
В результате выполнения функции debug_print_backtrace()
будет выведен список вызовов приведших нас к данной точке:
#0 example() called at [/www/education/error/backtrace.php:10] #1 ExampleClass::method() called at [/www/education/error/backtrace.php:14]
Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу]
, но я не встречал использования оных.
Assert
Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP, собственно это кусочек контрактной методологии программирования, и дальше я расскажу вам как я никогда его не использовал
Первый случай – это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:
// включаем вывод ошибок error_reporting(E_ALL); ini_set('display_errors', 1); // включаем asserts ini_set('zend.assertions', 1); ini_set('assert.active', 1); assert(false, "Remove it!");
В результате выполнения данного кода получим E_WARNING
:
Warning: assert(): Remove it! failed
PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError
:
// включаем asserts ini_set('zend.assertions', 1); ini_set('assert.active', 1); // переключаем на исключения ini_set('assert.exception', 1); assert(false, "Remove it!");
В результате ожидаемо получаем не пойманный AssertionError
. При необходимости, можно выбрасывать произвольное исключение:
assert(false, new Exception("Remove it!"));
Но я бы рекомендовал использовать метки
@TODO
, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними
Второй вариант использования – это создание некоего подобия TDD, но помните – это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:
// callback-функция для вывода информации в браузер function backlog($script, $line, $code, $message) { echo "<h3>$message</h3>"; highlight_string ($code); } // устанавливаем callback-функцию assert_options(ASSERT_CALLBACK, 'backlog'); // отключаем вывод предупреждений assert_options(ASSERT_WARNING, false); // пишем проверку и её описание assert("sqr(4) == 16", "When I send integer, function should return square of it"); // функция, которую проверяем function sqr($a) { return; // она не работает }
Третий теоретический вариант – это непосредственно контрактное программирование – когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):
/** * Настройки соединения должны передаваться в следующем виде * * [ * 'host' => 'localhost', * 'port' => 3306, * 'name' => 'dbname', * 'user' => 'root', * 'pass' => '' * ] * * @param $settings */ function setupDb ($settings) { // проверяем настройки assert(isset($settings['host']), 'Db `host` is required'); assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer'); assert(isset($settings['name']), 'Db `name` is required, should be integer'); // соединяем с БД // ... } setupDb(['host' => 'localhost']);
Никогда не используйте
assert()
для проверки входных параметров, ведь фактическиassert()
интерпретирует строковую переменную (ведёт себя какeval()
), а это чревато PHP-инъекцией. И да, это правильное поведение, т.к. просто отключив assert’ы всё что передаётся внутрь будет проигнорировано, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения
Если у вас есть живой опыт использования assert()
– поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме – PHP Assertions, с таким же вопросом в конце
В заключение
Я за вас напишу выводы из данной статьи:
- Ошибкам бой – их не должно быть в вашем коде
- Используйте исключения – работу с ними нужно правильно организовать и будет счастье
- Assert – узнали о них, и хорошо
P.S. Спасибо Максиму Слесаренко за помощь в написании статьи