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 ¶
19 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 ¶
18 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 ¶
17 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__.
"\n\n---ERRNO---\n". print_r( $errno, true).
"\n\n---ERRSTR---\n". print_r( $errstr, true).
"\n\n---ERRFILE---\n". print_r( $errfile, true).
"\n\n---ERRLINE---\n". print_r( $errline, true).
"\n\n---ERRCONTEXT---\n".print_r( $errcontext, true).
"\n\nBacktrace of errorHandler()\n".
print_r( debug_backtrace(), true);
}
function
a( )
{
//echo "a()'s backtrace\n".print_r( debug_backtrace(), true);
asdfasdf; // oops
}
function
b()
{
//echo "b()'s backtrace\n".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.
Время на прочтение
7 мин
Количество просмотров 31K
Что я понимаю под правильной обработкой:
- Универсальное решение, которое можно вставить в любой существующий код;
- Легко расширяемое решение;
- В PHP аж три «механизма ошибок»: собственно ошибки (error), исключения (exception) и утверждения (assertion). Свести три механизма к одному — exception. В комментариях к предыдущей статье на эту тему выражалось мнение, что exception это плохой и/или сложный метод обработки ошибок. Я так не считаю и готов это обсудить в комментариях;
- Опциональное логирование;
- Общий обработчик exception, который будет поддерживать разные форматы вывода и debug/production режимы;
- В debug режиме должен выводится trace. Требования к trace: компактный, понятный и по возможности ссылки на открытие файлов в IDE.
Универсальное решение
Для этого сделаем класс exceptionHandlerClass. В exceptionHandlerClass будут храниться настройки и статические методы — обработчики error, exception и assertion. Еще нам нужны методы setupHandlers и restoreHandlers. Первый метод настроит перехват ошибок. Error и assertion обработчики будут бросать ErrorException. Exception обработчик будет обрабатывать необработанные Exception и в зависимости от настроек выводить соответствующий ответ. restoreHandlers вернет все обработчики в изначальное состояние — это поможет при встраивании класса в код с существующим механизмом обработки ошибок. Подключение выглядит так:
- require ‘exceptionHandler/exceptionHandlerClass.php’;
- exceptionHandlerClass::setupHandlers();
включение debug режима (по умолчанию выключен) :
- exceptionHandlerClass::$debug = true;
Форматы вывода
Проще объяснить на примере: для вывода trace на веб странице я оберну его в таги pre и применю htmlspecialchars(), с другой стороны этот же trace при выводе в консоль будет не удобно читать, было бы проще, если бы это был plainText. Если нужно вывести ошибку как ответ SоapServer, то это должен быть правильно сформированный XML документ (SoapFault). Если скрипт выводит бинарные данные, например изображение, то удобней выводить ошибки через WildFire. Во всех этих ситуация нужно просто применить разные форматы вывода.
Для разных форматов будем создавать разные классы. Я для начала реализую два формата вывода exceptionHandlerOutputWeb(для веба) и exceptionHandlerOutputCli(для командной строки). Так же нам понадобиться класс фабрика (exceptionHandlerOutputFactory), в котором будет инкапсулирована логика, когда какой формат вывода применить.
- public function getExceptionHandlerOutput(){
- if(php_sapi_name() == ‘cli’){
- return new exceptionHandlerOutputCli();
- }
- return new exceptionHandlerOutputWeb();
- }
При вызове setupHandlers можно установить формат вывода, передав экземпляр класса exceptionHandlerOutput* или exceptionHandlerOutputFactory*.
- exceptionHandlerClass::setupHandlers(new exceptionHandlerOutputAjax());
Благодаря такой архитектуре можно легко расширять форматы. Для создания нового формата достаточно создать класс, который будет наследоваться от абстрактного класса exceptionHandlerOutput и реализовать один метод (output).
- class exceptionHandlerOutputAjax extends exceptionHandlerOutput{
- public function output($exception, $debug){
- header(‘HTTP/1.0 500 Internal Server Error’, true, 500);
- header(‘Status: 500 Internal Server Error’, true, 500);
- $response = array(
- ‘error’ => true,
- ‘message’ => »,
- );
- if($debug){
- $response[‘message’] = $exception->getMessage();
- } else {
- $response[‘message’] = self::$productionMessage;
- }
- exit(json_encode($response));
- }
- }
Если нужна более сложная логика для автоматического выбора формата вывода, нужно создать класс, наследуемый от exceptionHandlerOutputFactory и реализовать метод getExceptionHandlerOutput.
- class exceptionHandlerOutputAjaxFactory extends exceptionHandlerOutputDefaultFactory{
- public function getExceptionHandlerOutput() {
- if( self::detect() ){
- return new exceptionHandlerOutputAjax();
- }
- parent::getExceptionHandlerOutput();
- }
- public static function detect(){
- return (!empty($_SERVER[‘HTTP_X_REQUESTED_WITH’])
- && strtolower($_SERVER[‘HTTP_X_REQUESTED_WITH’]) == ‘xmlhttprequest’);
- }
- }
- exceptionHandlerClass::setupHandlers(new exceptionHandlerOutputAjaxFactory());
Логирование
Как я и сказал выше логирование можно включать по желанию. Для этого в exceptionHandlerClass создан метод exceptionLog
- public static function exceptionLog($exception, $logPriority = null){
- if(!is_null(self::$exceptionHandlerLog)){
- self::$exceptionHandlerLog->log($exception, $logPriority);
- }
- }
если нужно включить логирование, то достаточно сделать следующее:
- exceptionHandlerClass::$exceptionHandlerLog = new exceptionHandlerSimpleLog();
Класс для логирования должен наследоваться от абстрактного exceptionHandlerLog и реализовывать метод log
- class exceptionHandlerSimpleLog extends exceptionHandlerLog{
- public function log($exception, $logType){
- switch ($logType){
- case self::uncaughtException:
- error_log($exception->getMessage());
- break;
- }
- }
- }
logType это одна из констант объявленных exceptionHandlerLog
- const uncaughtException = 0; //необработанные исключения
- const caughtException = 1; //вызов метода логирования вне обработчиков ошибок
- const ignoredError = 2; //ошибки маскированные @, логируются если выключена опция scream
- const lowPriorityError = 3; //ошибки которые не превращаются exception
- const assertion = 4; //assertion
Имея logType и exception разработчик может сам решить какие искллючения и как логировать. Например, uncaughtException можно высылать по почте, ignoredError с severity E_ERROR логировать в файл итп.
Trace
При выводе trace я хочу видеть тип исключения, сообщение и собственно сам trace. В trace для каждого вызова должно выводится, какая функция вызвалась, список «кратких» параметров, файл и строка где произошел вызов. Что такое «краткие» параметры объясню на примерах: если функцию вызвали со строкой длиной в 1000 символов, то наличие этой строки в trace ничем не поможет при решении проблемы, а только затруднит чтение trace, это же касается массивов с большой вложенностью. Вывод trace на экран просто должен подсказать, где искать. Чтобы разобраться, что именно происходит нужно дебажить с помощью xdebug или примитивных var_dump() и die(), кому как больше нравится.
Пример trace:
[ErrorException]: E_WARNING - mysql_connect(): Can't connect to MySQL server on 'localhost' (10061)
#0: mysql_connect()
D:\projects1\d\index.php:19
#1: testClass::test1("длиная строка…eeeeeery long string"(56))
D:\projects1\d\index.php:22
#2: testClass->test2(testClass(), -∞, i:iTest, c:testClass, fa:testClass::test2)
D:\projects1\d\index.php:27
#3: testAll(r:stream, fs:testClass::test1)
D:\projects1\d\index.php:30
Легенда
- r: — resource
- fs: — function (callable string)
- fa: — function (callable array)
- i: — interface (string)
- c: — class (string)
- ∞/INF — infinity
- testClass() — object of type testClass
- «»(n) — string, в скобках указана длина, … — место где вырезана часть строки
- array(n) — array, в скобках указана длина
И самое полезное… ссылки на открытие файлов в IDE прямо из trace.
При нажатии на ссылку в IDE откроется соответствующий файл на соответствующей строке.
Для консольного режима (консоль NetBeans):
- exceptionHandlerOutputCli::setFileLinkFormat(‘: in %f on line %l’);
Для веб режима (TextMate):
- exceptionHandlerOutputWeb::setFileLinkFormat(‘txmt://open/?file://%f&line=%l’);
Можно реализовать для NetbBeans (или другого IDE). Для этого нужно: зарегистрировать протокол; сделать обработчик этого протокола (самое простое — bat файл). В обработчике через командную строку вызвать NetBeans с соответствующим файлом и строкой. Но это тема для следующей статьи.
Код писался за два дня так, что возможны мелкие недочеты. Скачать (не было времени, чтобы выложить в репозиторий).
UPD: перенесено в блог PHP
UPD2: в продолжение темы работа с исключениями в PHP
Антон Шевчук // 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]') . " done\n");
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]'') . " done\n");
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. Спасибо Максиму Слесаренко за помощь в написании статьи
on
June 25, 2020
Errors are undesirable for users and you should do everything in your control to keep users away from them. However, they are of utmost importance for developers. They allow developers to understand the inaccuracies and vulnerabilities in their code by alerting them when their code breaks. They also provide relevant information about what went wrong, where, and what can be done to make amends. Absence of intelligent error reporting can not only make it extremely difficult to debug your project but can also let unnoticed, unresolved inconsistencies bleed into production code.
Most errors in PHP are by default not reported. This might be helpful for web applications in production, where you don’t want users to come across obscure error messages. However, during development, it is imperative to enable error messages — to be alerted about potential inaccuracies in your code before it is released for production.
In this post, we will look at what an error in PHP is, the different types of errors, and how you can enable error reporting in PHP.
Use these links to jump ahead in the tutorial:
What is a PHP error?
Common PHP Error Codes
Turn on Error Reporting in PHP
How to Log Errors in PHP
Conclusion
What is a PHP Error?
An error is an indication of something going wrong in your web application. It is usually encountered when a part of your code has not been properly implemented, for example — mistakes in the syntax, logical inconsistencies, inattention to handling invalid user input, etc. An error in programming paradigms can be as trivial as a missed semicolon or an unclosed parenthesis to something as big as an undefined class function or an unhandled invalid user input.
Let us look at a few common PHP error categories and codes.
Types of PHP Errors
There are primarily four types of errors in PHP —
- Fatal run-time errors
- Warning errors
- Parse errors (Syntax errors)
- Notice errors
Note: We are able to access error messages in the example outputs shown below because error reporting has been turned on for visualization. We will look at how one can enable error messages in the next section.
Fatal run-time errors (E_ERROR | Code 1)
As the name suggests, these errors can not be recovered from. They are encountered when the operation specified in your code can not be performed. As a result, execution is halted.
For example — if you try to call a function that has not been defined, a fatal run-time error would be raised.
<?php
function foo() {
echo "Function foo called.";
}
boo(); // undefined function 'boo'
?>
Warning errors (E_WARNING | Code 2)
A warning error is more subtle in that regard. It does not halt execution — just acts as a friendly reminder of something incorrect in your code, that might pose a bigger problem in the future.
The most common example of a warning error being raised is when you include a missing file in your code.
<?php
include('filename.txt'); // arbitrary file that is not present
echo "Hello world";
?>
Notice how the warning error has not forced the code to halt, allowing the print statement below to be executed.
Parse errors (E_PARSE | Code 4)
Parse errors are encountered as a result of purely syntactical mistakes in one’s code. These errors are generated during compilation, and as a result your code exits before it is run.
Parse error examples include — missing semicolons, unused or unclosed brackets, quotes, etc. Below is an example of the same.
<?php
echo "Hello world";
echo Hello world // no quotes or semicolon used
?>
Notice errors (E_NOTICE | Code
Notice errors are similar to warning errors in that they are encountered during run-time and they don’t halt code execution. They indicate that something is incorrect in the script that even though doesn’t interrupt execution, should be fixed.
A common example would be trying to use an undefined variable in your code.
<?php
$a = 1;
$c = $a + $b; // undefined variable $b
echo "Hello world";
?>
Turn on Error Reporting in PHP
Error reporting in PHP is usually disabled by default. It can be enabled in three primary ways —
- Directly from code
- By editing the php.ini configuration file
- By editing the .htaccess file.
Before we look into the various methods, a word of caution is necessary. Make sure to disable error reporting for your web application’s production code. You wouldn’t want your users to come across obscure error messages when using your website.
Directly from code
Using the error_reporting() function
To configure error reporting in your PHP project, you can use the error_reporting function. This allows you to manipulate error reporting at run-time.
error_reporting ([ int $level ] ) : int
PHP has various types of errors that we looked at earlier in this post. These types can be provided to the error_reporting function as an argument to specify the kinds of errors that should be reported.
To report all PHP errors you can use the function in any of the two ways shown below.
<?php
// Report all PHP errors
error_reporting(E_ALL);
?>
or
<?php
// Report all PHP errors
error_reporting(-1);
?>
Both of the above-mentioned methods serve the same purpose, and therefore you can use either of them to enable error reporting for all types of errors.
Let us see this in action using an example —
<?php
error_reporting(E_ALL);
echo "Hello world";
// xyz is an undefined variable; should raise an error.
echo $xyz;
?>
OUTPUT:
As we can see, a Notice error has been reported. Had we not used the error_reporting function, PHP would have implicitly taken care of it and not reported anything at all.
This only strengthens our above argument about how unresolved issues can go unnoticed if errors are not appropriately reported.
You can also disable error reporting using the function as such —
//Turn off error reporting
error_reporting(0);
For reporting only specific kinds of errors, you can provide the required error types as a parameter to the error_reporting function using the bitwise OR operator as shown below —
// Report only selected kinds of errors
error_reporting(E_WARNING | E_ERROR | E_PARSE | E_NOTICE);
To enable error reporting for all but one (or more) error levels, you can use the below syntax —
// Report all errors except E_WARNING
error_reporting(E_ALL & ~E_WARNING);
Using the ini_set() function
We can also use the ini_set function to configure error reporting. This function allows us to initialize or modify the various configuration options (specified using a .ini file) directly from our code. This function modifies the configuration options only for the duration of the code (run-time).
You can call this function by adding the following line to your code —
ini_set('error_reporting', E_ALL);
We can similarly manipulate error reporting by applying different values for the ‘error_reporting’ option.
// Report selected kinds of errors
ini_set('error_reporting', E_WARNING | E_ERROR | E_PARSE | E_NOTICE);
Even though the ini_set function allows us to easily modify the configuration options directly from our code, it is a better practice to keep your PHP configurations in a separate php.ini file on your system. Let’s look at how we can do that in the next section.
By editing the php.ini configuration file
The php.ini serves as a configuration file for your project. It is read when PHP is initialized and can be used for a variety of things — storing environment variables, configuring server ports, error reporting, memory management, etc. You can find a list of the various configuration directives and their default values here.
Therefore, to enable error reporting in PHP, you need to edit this configuration file. The php.ini file is usually present in the /etc directory on Linux systems. You can locate the file by printing the output of the phpinfo() function in your code. The file’s path is provided under the ‘loaded configuration file’ row of the output.
Note: If the path mentioned in your output refers to a directory (for example- /etc) and not the file, you can create a new php.ini file inside that directory.
To enable error reporting, you can add either of the following lines to the php.ini file, and restart your server for the effects to take place.
error_reporting = E_ALL
or
error_reporting = -1
To also report errors encountered during PHP’s startup sequence, you can use the following directive in your php.ini file —
display_startup_errors = 1
Some posts on the internet also suggest enabling the ‘display_errors’ option for displaying error messages. In most cases, this is not required since that option is usually turned on by default in PHP.
If it is disabled in your system, you can turn it on by adding the following line in your php.ini file —
display_errors = 1
By editing the .htaccess file
The .htaccess (stands for hypertext-access) is a configuration file for manipulating various options provided by Apache-based web-servers.
Even though it can be a little difficult to set up initially based on your operating system, the .htaccess file allows you to exercise control over various server options at the local directory level and the global system level.
To enable error reporting, add the following two lines to your .htaccess file —
php_flag display_startup_errors on
php_flag display_errors on
Even though (as mentioned above) the display_errors property is by default ON, it is likely that your system admin might have disabled that property globally and you might want to enable it for a local project. Overriding the global property using the local .htaccess file can be a handy approach in such situations.
How to Log Errors in PHP
In large-scale PHP web applications that involve many processes like API requests, data processing etc, it is a good practice to store logs of your output and error messages. Good logging measures allow us to keep track of the inner workings of the system for effective debugging and maintenance. Let’s look at how we can create error logs in PHP.
You need to do two things to store error logs for your project —
- Enable ‘log_errors’ option
- Specify the ‘error_log’ file
Once this has been done, all your errors will be automatically logged to the specified error_log file. Just like error reporting, error logging can be set up from any of these three places — directly from code, from the php.ini configuration file and from the .htaccess file
Directly from code
Using the ini_set function we can configure the relevant PHP options as such —
ini_set('log_errors', 1); // enabling error logging
ini_set('error_log', '/path/my-error-file.log'); // specifying log file
After doing this, all our errors would be automatically logged to the specified log file. Let us look at an example —
<?php
ini_set('log_errors', 1); // enabling error logging
ini_set('error_log', '/path/my-error-file.log'); // specifying log file
echo $b; // undefined variable should raise error
?>
Now, let’s open our project root folder and open the ‘my-error-file.log’ file to see if the error message has been logged.
As you can see, a timestamp has also been added to the error message, as we’d expect any effective logging system to do. All errors further encountered in your code will be appended to this file.
Another alternative to logging errors from your code would be to manually do so using the error_log function. You can use the function as shown below to write to a log file as shown below —
<?php
echo "Hello world";
// manually logging error message ->
error_log('My error message ⚠️', 3, 'my-error-file-2.log');
?>
The first and third arguments of the function represent the error message and the log file respectively. The second argument in the above function (3) is an option that specifies that the error message should be appended to the output of the file.
You can read more about the arguments of the error_log function here.
In this case, as can be seen, one has to specify the error message and the logging file each time. Also, in this method, timestamps are not added to the generated log messages by default.
Using the php.ini configuration file
Enabling logging from the configuration file is pretty straightforward. You only need to add two directives to your php.ini file as shown below —
log_errors = on
error_log = /path/my-error-file.log
After saving the file, restart your server to enable the automatic error logging to the specified file.
Using the .htaccess file
You can add the following two lines to your .htaccess file to enable error logging —
php_flag log_errors on
php_value error_log /path/my-error-file.log
This also serves the same purpose — enabling error logging, followed by specifying the output log file.
Conclusion
In this post, we learned about what PHP errors are, their different types (fatal errors, warnings, parse errors, and notices), about how important they are, and how you can enable error reporting for your web application. We looked at three different ways of initiating error reporting and storing log files — directly from code, through the php.ini configuration file, and from the server-based .htaccess file.
Now that you know how important it is to be mindful of errors, go ahead and enable error reporting in your project, maintain error logs, and build yourself a full-proof web application! You can also view your PHP errors in ScoutAPM’s dashboard. Also, make sure to disable error displaying when you release your website for production.
After you have enabled error reporting in your project, you might want to learn about how to handle these raised errors (exceptions) in your code. For this, you can refer to our post on Exception Handling in PHP — PHP Advanced Exceptions.
Stay safe! Keep learning! Happy coding!
I still vividly remember the first time I learned about PHP error handling.
It was in my fourth year of programming, my second with PHP, and I’d applied for a developer position at a local agency. The application required me to send in a code sample (GitHub as we know it didn’t exist back then) so I zipped and sent a simple custom CMS I’d created the previous year.
The email I got back from the person reviewing the code still chills my bones to this day.
«I was a bit worried about your project, but once I turned error reporting off, I see it actually works pretty well».
That was the first time I searched «PHP error reporting», discovered how to enable it, and died inside when I saw the stream of errors that were hidden from me before.
PHP errors and error reporting are something that many developers new to the language might miss initially. This is because, on many PHP based web server installations, PHP errors may be suppressed by default. This means that no one sees or is even aware of these errors.
For this reason, it’s a good idea to know where and how to enable them, especially for your local development environment. This helps you pick up errors in your code early on.
If you Google «PHP errors» one of the first results you will see is a link to the error_reporting function documentation.
This function allows you to both set the level of PHP error reporting, when your PHP script (or collection of scripts) runs, or retrieve the current level of PHP error reporting, as defined by your PHP configuration.
The error_reporting function accepts a single parameter, an integer, which indicates which level of reporting to allow. Passing nothing as a parameter simply returns the current level set.
There is a long list of possible values you can pass as a parameter, but we’ll dive into those later.
For now it’s important to know that each possible value also exists as a PHP predefined constant. So for example, the constant E_ERROR has the value of 1. This means you could either pass 1, or E_ERROR to the error_reporting function, and get the same result.
As a quick example, if we create a php_error_test.php PHP script file, we can see the current error reporting level set, as well as set it to a new level.
<?php
// echo the current error reporting level
echo error_reporting();
<?php
// report all Fatal run-time errors.
echo error_reporting(1);
Error reporting configuration
Using the error_reporting function in this way is great when you just want to see any errors related to the piece of code you’re currently working on.
But it would be better to control which errors are being reported on in your local development environment, and log them somewhere logical, to be able to review as you code. This can be done inside the PHP initialization (or php.ini) file.
The php.ini file is responsible for configuring all the aspects of PHP’s behavior. In it you can set things like how much memory to allocate to PHP scripts, what size file uploads to allow, and what error_reporting level(s) you want for your environment.
If you’re not sure where your php.ini file is located, one way to find out is to create a PHP script which uses the phpinfo function. This function will output all the information relative to your PHP install.
<?php
phpinfo();
As you can see from my phpinfo, my current php.ini file is located at /etc/php/7.3/apache2/php.ini.
Once you’ve found your php.ini file, open it in your editor of choice, and search for the section called ‘Error handling and logging’. Here’s where the fun begins!
Error reporting directives
The first thing you’ll see in that section is a section of comments which include a detailed description of all the Error Level Constants. This is great, because you’ll be using them later on to set your error reporting levels.
Fortunately these constants are also documented in the online PHP Manual, for ease of reference.
Below this list is a second list of Common Values. This shows you how to set some commonly used sets of error reporting value combinations, including the default values, the suggested value for a development environment, and the suggested values for a production environment.
; Common Values:
; E_ALL (Show all errors, warnings and notices including coding standards.)
; E_ALL & ~E_NOTICE (Show all errors, except for notices)
; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.)
; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors)
; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
; Development Value: E_ALL
; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
Finally, at the bottom of all the comments is the current value of your error_reporting level. For local development, I’d suggest setting it to E_ALL, so as to see all errors.
error_reporting = E_ALL
This is usually one of the first things I set when I set up a new development environment. That way I’ll see any and all errors that are reported.
After the error_reporting directive, there are some additional directives you can set. As before, the php.ini file includes descriptions of each directive, but I’ll give a brief description of the important ones below.
The display_errors directive allows you to toggle whether PHP outputs the errors or not. I usually have this set to On, so I can see errors as they happen.
display_errors=On
The display_startup_errors allows for the same On/Off toggling of errors that may occur during PHP’s startup sequence. These are typically errors in your PHP or web server configuration, not specifically your code. It’s recommended to leave this Off, unless you’re debugging a problem and you aren’t sure what’s causing it.
The log_errors directive tells PHP whether or not to log errors to an error log file. This is always On by default, and is recommended.
The rest of the directives can be left as the default, except for maybe the error_log directive, which allows you to specify where to log the errors, if log_errors is on. By default it will log the errors wherever your web server has defined them to be logged.
Custom error logging
I use the Apache web server on Ubuntu, and my project-specific virtual host configurations use the following to determine the location for the error log.
ErrorLog ${APACHE_LOG_DIR}/project-error.log
This means it will log to the default Apache log directory, which is /var/log/apache2, under a file called project-error.log. Usually I replace project with the name of the web project it relates to.
So, depending on your local development environment you may need to tweak this to suit your needs. Alternatively, if you can’t change this at the web server level, you can set it at the php.ini level to a specific location.
error_log = /path/to/php.log
It is worth noting that this will log all PHP errors to this file, and if you’re working on multiple projects that might not be ideal. However, always knowing to check that one file for errors might work better for you, so your mileage may vary.
Find and fix those errors
If you’ve recently started coding in PHP, and you decide to turn error reporting on, be prepared to deal with the fallout from your existing code. You may see some things you didn’t expect, and need to fix.
The advantage though, is now that you know how to turn it all on at the server level, you can make sure you see these errors when they happen, and deal with them before someone else sees them!
Oh, and if you were wondering, the errors I was referring to at the start of this post were related to the fact that I was defining constants incorrectly, by not adding quotes around the constant name.
define(CONSTANT, 'Hello world.');
PHP allowed (and might still allow) this, but it would trigger a notice.
Notice: Use of undefined constant CONSTANT - assumed 'CONSTANT'
This notice was triggered every time I defined a constant, which for that project was about 8 or 9 times. Not great for someone to see 8 or 9 notices at the top of each page…
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started





