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.
Антон Шевчук // 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. Спасибо Максиму Слесаренко за помощь в написании статьи
Reg.ru: домены и хостинг
Крупнейший регистратор и хостинг-провайдер в России.
Более 2 миллионов доменных имен на обслуживании.
Продвижение, почта для домена, решения для бизнеса.
Более 700 тыс. клиентов по всему миру уже сделали свой выбор.
Перейти на сайт->
Бесплатный Курс «Практика HTML5 и CSS3»
Освойте бесплатно пошаговый видеокурс
по основам адаптивной верстки
на HTML5 и CSS3 с полного нуля.
Начать->
Фреймворк Bootstrap: быстрая адаптивная вёрстка
Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.
Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.
Верстайте на заказ и получайте деньги.
Получить в подарок->
Бесплатный курс «Сайт на WordPress»
Хотите освоить CMS WordPress?
Получите уроки по дизайну и верстке сайта на WordPress.
Научитесь работать с темами и нарезать макет.
Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!
Получить в подарок->
*Наведите курсор мыши для приостановки прокрутки.
Перед изучением данной статьи вы можете прочитать предыдущую статью из этой серии — «Позднее статическое связывание: ключевое слово static».
Иногда всё идет не так, как надо. Файлы где-то потерялись, серверы баз данных остались не инициализированы, URL-адреса изменились, XML-файлы повреждены, права доступа настроены неправильно, лимиты на дисковую память превышены и бог знает что еще…
В стремлении предусмотреть любую проблему, простой метод может иногда утонуть под тяжестью собственного кода обработки ошибок.
Ниже приведено определение простого класса Conf, который сохраняет, извлекает и определяет данные в XML-файле конфигурации.
class Conf {
private $file;
private $xml;
private $lastmatch;
function __construct( $file ) {
$this->file = $file;
$this->xml = simplexml_load_file($file);
}
function write() {
file_put_contents( $this->file, $this->xml->asXML() );
}
function get( $str ) {
$matches = $this->xml->xpath("/conf/item[@name=\"$str\"]");
if ( count( $matches ) ) {
$this->lastmatch = $matches[0];
return (string)$matches[0];
}
return null;
}
function set( $key, $value ) {
if ( ! is_null( $this->get( $key ) ) ) {
$this->lastmatch[0]=$value;
return;
}
$conf = $this->xml->conf;
$this->xml->addChild('item', $value)->addAttribute( 'name', $key );
}
}
В классе Conf для доступа к парам «имя — значение» используется расширение PHP SimpleXml. Ниже приведен фрагмент файла конфигурации в формате XML, с которым работает наш класс.
<?xml version="1.0"?>
<conf>
<item name="user">bob</item>
<item name="pass">newpass</item>
<item name="host">localhost</item>
</conf>
Конструктору класса Conf передается имя файла конфигурации, которое далее передается функции simplexml_load_file(). Полученный от функции объект типа SimpleXmlElement сохраняется в свойстве $xml.
В методе get() для нахождения элемента item с заданные атрибутом name используется метод xpath объекта SimpleXmlElement. Значение найденного элемента возвращается в вызывающий код. Метод set() либо меняет значение существующего элемента, либо создает новый. И, наконец, метод write() сохраняет данные о новой конфигурации в исходном файле на диске.
Как и многие коды, приведенные в качестве примеров, код класса Conf крайне упрощен. В частности, в нем не предусмотрена обработка ситуаций, когда файл конфигурации не существует или в него нельзя записать данные. Этот код также слишком «оптимистичен». В нем предполагается, что XML-документ должен быть правильно отформатирован и содержать ожидаемые элементы.
Провести тестирование подобных ошибок достаточно просто, но мы должны решить, как на них реагировать, если они возникнут. В целом у нас есть две возможности.
1. Мы можем завершить выполнение программы. Это простой, но радикальный выход. В результате наш скромный класс будет виноват в том, что из-за него потерпел неудачу весь сценарий. Хотя такие методы, как __construct() и write(), удачно расположены в коде с целью обнаружения ошибок, у них нет информации, позволяющей решить, как обрабатывать эти ошибки.
2. Вместо обработки ошибки в классе, мы можем вернуть признак ошибки в том или ином виде. Это может булево или целое значение, например 0 или -1. В некоторых классах можно также сформировать текстовое сообщение об ошибке или набор специальных признаков, чтобы клиентский код мог запросить больше информации в случае неудачного завершения программы.
Во многих PEAR-пакетах сочетаются эти два подхода и возвращается объект ошибок (экземпляр класса PEAR_Error). Наличие этого объекта говорит о том, что произошла ошибка, а подробная информация о ней содержится в самом объекте.
Этот подход в настоящее время не рекомендуется использовать, но многие классы не были обновлены в значительной степени по причине того, что клиентский код зачастую рассчитан на старые стандарты.
Проблема заключается в том, что возвращаемое значение может быть затерто. В PHP нет средств, заставляющих возвращать унифицированное значение.
На момент написания этой статьи в PHP не поддерживались уточнения типа для возвращаемого класса, поэтому ничто не может нам помешать вернуть признак ошибки вместо ожидаемого объекта или значения элементарного типа.
Делая так, мы должны полагаться на то, что клиентский код будет проверять тип возвращаемого объекта после каждого вызова нашего метода, подверженного ошибкам. А это довольно рискованно. Никому нельзя доверять!
Когда в вызывающий код возвращается ошибочное значение, нет никакой гарантии, что клиентский код будет «вооружен» лучше нашего метода и сможет решить, как обрабатывать ошибки. А если не сможет, то проблемы будут появляться снова и снова.
Клиентский метод должен будет определить, как реагировать на ошибочную ситуацию, и, возможно, даже реализовать другую стратегию сообщения об ошибке.
Исключения
С PHP 5 было введено понятие исключений, представляющих собой совершенно другой способ обработки ошибок. Я хочу сказать — совершенно другой для PHP. Но если у вас есть опыт работы с Java или C++, то исключения покажутся вам знакомыми и близкими. Использование исключений позволяет решить все проблемы, о которых мы говорили ранее.
Итак, исключение — это специальный объект, который является экземпляром встроенного класса Exception (или его производного класса). Объекты типа Exception предназначены для хранения информации об ошибках и выдачи сообщений о них.
Конструктору класса Exception передается два необязательных аргумента: строка сообщения и код ошибки. В этом классе существуют также некоторые полезные методы для анализа ошибочной ситуации.
Общедоступные методы класса Exception
getMessage() — получить строку сообщения, переданную конструктору;
getCode() — получить код ошибки (целое число), который был передан конструктору;
getFile() — получить имя файла, в котором было сгенерировано исключение;
getLine() — получить номер строки, в которой было сгенерировано исключение;
getPrevious() — получить вложенный объект типа Exception;
getTrace() — получить многомерный массив, отслеживающий вызовы метода, которые привели к исключению, включая имя метода, класса, файла и значение аргумента;
getTraceAsString() — получить строковую версию данных, возвращенных методом getTrace();
__toString() — вызывается автоматически, когда объект Exception используется в контексте строки. Возвращает строку, описывающую подробности исключения.
Класс Exception крайне полезен для поиска сообщения об ошибке и информации для отладки (в этом отношении особенно полезны методы getTrace() и getTraceAsString()). На самом деле класс Exception почти идентичен классу PEAR_Error, который мы обсуждали выше. Но в нем сохраняется меньше информации об исключениях, чем есть на самом деле.
Генерация исключений
Совместно с объектом Exception используется ключевое слово throw. Оно останавливает выполнение текущего метода и передает ответственность за обработку ошибок назад в вызывающий код. Давайте подкорректируем метод __construct(), чтобы использовать оператор throw.
function __construct( $file ) {
$this->file = $file;
if ( ! file_exists( $file ) ) {
throw new Exception( "Файл '$file' не существует" );
}
$this->xml = simplexml_load_file($file);
}
Аналогичная конструкция может использоваться и в методе write().
function write() {
if ( ! is_writeable( $this->file ) ) {
throw new Exception("Файл '{$this->file}' недоступен для записи");
}
file_put_contents( $this->file, $this->xml->asXML() );
}
Теперь наши методы __construct() и write() могут тщательно проверять ошибки в файле по мере выполнения своей работы. Однако при этом решение о том, как реагировать на любые ошибки, будет приниматься в клиентском коде.
Так как же тогда клиентский код узнает, что возникло исключение и настала пора его обрабатывать? Для этого при вызове метода, в котором может возникнуть исключение, следует использовать оператор try.
Оператор try состоит из ключевого слова try и следующих за ним фигурный скобок. За оператором try должен следовать по меньшей мере один оператор catch, в котором можно обработать любую ошибку, как показано ниже.
try {
$conf = new Conf( dirname(__FILE__)."/conf01.xml" );
print "user: ".$conf->get('user')."\n";
print "host: ".$conf->get('host')."\n";
$conf->set("pass", "newpass");
$conf->write();
} catch ( Exception $e ) {
die( $e->__toString() );
}
Как видите, оператор catch внешне напоминает объявление метода. Когда генерируется исключение, управление передается оператору catch в контексте вызывающего метода, которому автоматически передается в качестве переменной-аргумента объект типа Exception.
Теперь, если при выполнении метода возникнет исключение и вызов этого метода находится внутри оператора try, работа сценария останавливается и управление передается непосредственно оператору catch.
Создание подклассов класса Exception
Вы можете создать классы, расширяющие класс Exception, точно так же, как это делается для любого другого определенного пользователем класса. Существует две причины, по которым возникает необходимость это делать.
Во-первых, можно расширить функциональность класса.
Во-вторых, то, что производный класс определяет новый тип класса, который поможет при обработке ошибок.
В сущности, вы можете определить столько операторов catch, сколько нужно для одного оператора try. То, какой конкретно оператор catch будет вызван, будет зависеть от типа сгенерированного исключения и указанного уточнения типа класса в списке аргументов. Давайте определим некоторые простые классы, расширяющие класс Exception.
class XmlException extends Exception {
private $error;
function __construct( LibXmlError $error ) {
$shortfile = basename( $error->file );
$msg = "[{$shortfile}, строка line {$error->line}, колонка {$error->column}] {$error->message}";
$this->error = $error;
parent::__construct( $msg, $error->code );
}
function getLibXmlError() {
return $this->error;
}
}
class FileException extends Exception { }
class ConfException extends Exception { }
Объект типа LibXmlError создается автоматически, когда средства SimpleXml обнаруживают поврежденный XML-файл. У него есть свойства message и code, и он напоминает класс Exception. Мы пользуется преимуществом этого подобия и используем объект LibXmlError в классе XmlException.
У классов FileException и ConfException не больше функциональных возможностей, чем у родительского класса Exception. Теперь мы можем использовать эти классы в коде и подкорректировать оба метода __construct() и write().
// класс Conf...
function __construct( $file ) {
$this->file = $file;
if ( ! file_exists( $file ) ) {
throw new FileException( "Файл '$file' не существует" );
}
$this->xml = simplexml_load_file($file, null, LIBXML_NOERROR );
if ( ! is_object( $this->xml ) ) {
throw new XmlException( libxml_get_last_error() );
}
print gettype( $this->xml );
$matches = $this->xml->xpath("/conf");
if ( ! count( $matches ) ) {
throw new ConfException( "Корневой элемент conf не найден" );
}
}
function write() {
if ( ! is_writeable( $this->file ) ) {
throw new Exception("Файл '{$this->file}' недоступен для записи");
}
file_put_contents( $this->file, $this->xml->asXML() );
}
Метод __construct() генерирует исключение типа XmlException, FileException или ConfException, в зависимости от вида ошибки, которую он обнаружит.
Обратите внимание на то, что методу simplexml_load_file() передается флаг LIBXML_NOERROR. Это блокирует выдачу предупреждений внутри класса и оставляет программисту свободу действий для их последующей обработки с помощью класса XmlException.
Если обнаружится поврежденный XML-файл, то метод simplexml_load_file() уже не возвращает объект типа SimpleXmlElement. Благодаря классу XmlException в клиентском коде можно будет легко узнать причину ошибки, а с помощью метода libxml_get_last_error() — все подробности этой ошибки.
Метод write() генерирует исключение типа FileException, если свойство $file указывает на файл, недоступный для записи.
Итак, мы установили, что метод __construct() может генерировать одно из трех возможных исключений. Как мы можем этим воспользоваться? Ниже приведен пример кода, в котором создается экземпляр объекта Conf().
class Runner {
static function init() {
try {
$conf = new Conf( dirname(__FILE__)."/conf01.xml" );
print "user: ".$conf->get('user')."\n";
print "host: ".$conf->get('host')."\n";
$conf->set("pass", "newpass");
$conf->write();
} catch ( FileException $e ) {
// Файл не существует, либо недоступен для записи
} catch ( XmlException $e ) {
// Поврежденный XML-файл
} catch ( ConfException $e ) {
// Некорректный формат XML-файла
} catch ( Exception $e ) {
// Ограничитель: этот код не должен никогда вызываться
}
}
}
В этом примере мы предусмотрели оператор catch для каждого типа класса ошибки. То, какой оператор будет вызван, зависит от типа сгенерированного исключения. При этом будет выполнен первый подходящий оператор. Поэтому помните: самый общий тип нужно размещать в конце, а самый специализированный — в начале списка операторов catch.
Например, если бы вы разместили оператор catch для обработки исключения типа Exception перед операторами для обработки исключений типа XmlException и ConfException, ни один из них никогда бы не был вызван. Причина в том, что оба исключения относятся к типу Exception и поэтому будут соответствовать первому оператору.
Первый оператор catch (FileException) вызывается, если есть проблема с файлом конфигурации (если этот файл не существует или в него нельзя ничего записать).
Второй оператор catch (XmlException) вызвается, если происходит ошибка при синтаксическом анализе XML-файла (например, если какой-то элемент не закрыт).
Третий оператор catch (ConfException) вызывается, если корректный в плане формата файл XML не содержит ожидаемый корневой элемент conf.
Последний оператор catch (Exception) не должен вызываться, потому что наши методы генерируют только три исключения, которые обрабатываются явным образом. Вообще, неплохо иметь такой оператор-ограничитель на случай, если в процессе разработки понадобится добавить в код новые исключения.
Преимущество этих «мелких» операторов в том, что они позволяют применить к разным ошибкам различные механизмы восстановления или неудачного завершения. Например, вы можете прекратить выполнение программы, записать в журнал информацию об ошибке и продолжить выполнение или повторно сгенерировать исключение, как показано ниже.
try {
// ...
} catch ( FileException $e ) {
throw $e;
}
Еще один вариант, которым можно воспользоваться, — сгенерировать новое исключение, которое будет перекрывать текущее. Это позволяет привлечь внимание к ошибке, добавить собственную контекстную информацию и в то же время сохранить данные, зафиксированные в исключении, которое обработала ваша программа.
Но что произойдет, если исключение не будет обработано в клиентском коде? Тогда оно автоматически сгенерируется повторно, и его сможет обработать вызывающий код клиентской программы.
Этот процесс будет продолжаться до тех пор, пока исключение не будет обработано либо его уже нельзя будет снова сгенерировать. Тогда произойдет неустранимая ошибка. Вот что произойдет, если в примере нашего кода не будет обработано ни одно исключение.
PHP Fatal error: Uncaught exception 'FileException' with message 'file 'nonexistent/not_there.xml' does not exist' in ...
Итак, когда вы генерируете исключение, то заставляете клиентский код брать на себя ответственность за его обработку. Но это не отказ от ответственности. Исключение должно генерироваться, когда метод обнаруживает ошибку, но не имеет контекстной информации, чтобы правильно ее обработать.
Метод write() в нашем примере «знает», когда попытка сделать запись заканчивается неудачей и почему, но «не знает», что с этим делать. Именно так и должно быть.
Если бы мы сделали класс Conf более «сведущим», чем он есть в настоящее время, он бы потерял свою универсальность и перестал бы быть повторно используемым.
На этом пока всё. В следующем материале мы поговорим о ключевом слове final и о том, как предотвратить наследование в PHP (скоро будет доступен).
Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!
Смотрите также:
|
|
PHP: Получение информации об объекте или классе, методах, свойствах и наследовании |
|
|
CodeIgniter: жив или мертв? |
|
|
Функции обратного вызова, анонимные функции и механизм замыканий |
|
|
Применение функции к каждому элементу массива |
|
|
Слияние массивов. Преобразование массива в строку |
|
|
Деструктор и копирование объектов с помощью метода __clone() |
|
|
Эволюция веб-разработчика или Почему фреймворк — это хорошо? |
|
|
Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.) |
|
|
PHP: Удаление элементов массива |
|
|
Ключевое слово final (завершенные классы и методы в PHP) |
|
|
50 классных сервисов, программ и сайтов для веб-разработчиков |
Наверх
I want to get warning and error messages into php $variables so I save them to my database.
For example when there is any kind of error, warning or similar:
Parse error: syntax error, unexpected T_VARIABLE in /example.php(136) on line 9
Warning: [...]
I want to get them to variable $error_code
How is this done?
John Conde
218k99 gold badges455 silver badges496 bronze badges
asked Feb 23, 2013 at 3:46
lisovaccarolisovaccaro
32.5k99 gold badges259 silver badges410 bronze badges
3
For the simple case of just logging them:
set_error_handler(function($errno, $errstr, $errfile, $errline) use ($db) {
// log in database using $db->query()
});
Instead of just logging them into your database (with the likelihood you will not look at them after a while), you can also let those warnings, notices, etc. generate an Exception:
function exception_error_handler($errno, $errstr, $errfile, $errline)
{
if (error_reporting()) { // skip errors that were muffled
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}
}
set_error_handler("exception_error_handler");
Source: ErrorException
An exception will have more serious side effects, so you should have an exception handler in place to prevent the uncaught exception to cause a white page of death.
answered Feb 23, 2013 at 4:04
Look into set_error_handler()
Sets a user function (error_handler) to handle errors in a script.
This function can be used for defining your own way of handling errors
during runtime, for example in applications in which you need to do
cleanup of data/files when a critical error happens, or when you need
to trigger an error under certain conditions (using trigger_error()).
answered Feb 23, 2013 at 3:49
John CondeJohn Conde
218k99 gold badges455 silver badges496 bronze badges
I’m using error_get_last(); until I find a better solution
$lastError = error_get_last();
echo $lastError ? "Error: ".$lastError["message"]." on line ".$lastError["line"] : "";
// Save to db
answered Feb 23, 2013 at 4:04
lisovaccarolisovaccaro
32.5k99 gold badges259 silver badges410 bronze badges
0
Время на прочтение
6 мин
Количество просмотров 67K
В статье представлена очередная попытка разобраться с ошибками, которые могут встретиться на вашем пути php-разработчика, их возможная классификация, примеры их возникновения, влияние ошибок на ответ клиенту, а также инструкции по написанию своего обработчика ошибок.
Статья разбита на четыре раздела:
- Классификация ошибок.
- Пример, демонстрирующий различные виды ошибок и его поведение при различных настройках.
- Написание собственного обработчика ошибок.
- Полезные ссылки.
Классификация ошибок
Все ошибки, условно, можно разбить на категории по нескольким критериям.
Фатальность:
- Фатальные
Неустранимые ошибки. Работа скрипта прекращается.
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR. - Не фатальные
Устранимые ошибки. Работа скрипта не прекращается.
E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED. - Смешанные
Фатальные, но только, если не обработаны функцией, определенной пользователем в set_error_handler().
E_USER_ERROR, E_RECOVERABLE_ERROR.
Возможность перехвата ошибки функцией, определенной в set_error_handler():
- Перехватываемые (не фатальные и смешанные)
E_USER_ERROR, E_RECOVERABLE_ERROR, E_WARNING, E_NOTICE, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED. - Не перехватываемые (фатальные)
E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING.
Инициатор:
- Инициированы пользователем
E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE. - Инициированы PHP
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED, E_USER_ERROR, E_RECOVERABLE_ERROR.
Для нас, в рамках данной статьи, наиболее интересны классификации по первым двум критериям, о чем будет рассказано далее.
Примеры возникновения ошибок
Листинг index.php
<?php
// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// подключаем файл с ошибками
require 'errors.php';
Листинг errors.php
<?php
echo "Файл с ошибками. Начало<br>";
/*
* перехватываемые ошибки (ловятся функцией set_error_handler())
*/
// NONFATAL - E_NOTICE
// echo $undefined_var;
// NONFATAL - E_WARNING
// array_key_exists('key', NULL);
// NONFATAL - E_DEPRECATED
split('[/.-]', "12/21/2012"); // split() deprecated начиная с php 5.3.0
// NONFATAL - E_STRICT
// class c {function f(){}} c::f();
// NONFATAL - E_USER_DEPRECATED
// trigger_error("E_USER_DEPRECATED", E_USER_DEPRECATED);
// NONFATAL - E_USER_WARNING
// trigger_error("E_USER_WARNING", E_USER_WARNING);
// NONFATAL - E_USER_NOTICE
// trigger_error("E_USER_NOTICE", E_USER_NOTICE);
// FATAL, если не обработана функцией set_error_handler - E_RECOVERABLE_ERROR
// class b {function f(int $a){}} $b = new b; $b->f(NULL);
// FATAL, если не обработана функцией set_error_handler - E_USER_ERROR
// trigger_error("E_USER_ERROR", E_USER_ERROR);
/*
* неперехватываемые (не ловятся функцией set_error_handler())
*/
// FATAL - E_ERROR
// undefined_function();
// FATAL - E_PARSE
// parse_error
// FATAL - E_COMPILE_ERROR
// $var[];
echo "Файл с ошибками. Конец<br>";
Примечание: для полной работоспособности скрипта необходим PHP версии не ниже 5.3.0.
В файле errors.php представлены выражения, инициирующие практически все возможные ошибки. Исключение составили: E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, генерируемые ядром Zend. В теории, встретить их в реальной работе вы не должны.
В следующей таблице приведены варианты поведения этого скрипта в различных условиях (в зависимости от значений директив display_errors и error_reporting):
| Группа ошибок | Значения директив* | Статус ответа сервера | Ответ клиенту** |
|---|---|---|---|
| E_PARSE, E_COMPILE_ERROR*** | display_errors = off error_reporting = ANY |
500 | Пустое значение |
| display_errors = on error_reporting = ANY |
200 | Сообщение об ошибке | |
| E_USER_ERROR, E_ERROR, E_RECOVERABLE_ERROR | display_errors = off error_reporting = ANY |
500 | Вывод скрипта до ошибки |
| display_errors = on error_reporting = ANY |
200 | Сообщение об ошибке и вывод скрипта до ошибки | |
| Не фатальные ошибки | display_errors = off error_reporting = ANY и display_errors = on error_reporting = 0 |
200 | Весь вывод скрипта |
| display_errors = on error_reporting = E_ALL | E_STRICT |
200 | Сообщение об ошибке и весь вывод скрипта |
* Значение ANY означает E_ALL | E_STRICT или 0.
** Ответ клиенту может отличаться от ответов на реальных скриптах. Например, вывод какой-либо информации до включения файла errors.php, будет фигурировать во всех рассмотренных случаях.
*** Если в файле errors.php заменить пример для ошибки E_COMPILE_ERROR на require "missing_file.php";, то ошибка попадет во вторую группу.
Значение, приведенной выше, таблицы можно описать следующим образом:
- Наличие в файле скрипта ошибки, приводящей его в «негодное» состояние (невозможность корректно обработать), на выходе даст пустое значение или же только само сообщение об ошибке, в зависимости от значения директивы display_errors.
- Скрипт в файле с фатальной ошибкой, не относящейся к первому пункту, будет выполняться в штатном режиме до самой ошибки.
- Наличие в файле фатальной ошибки при display_errors = Off обозначит 500 статус ответа.
- Не фатальные ошибки, как и следовало ожидать, в контексте возможности исполнения скрипта в целом, на работоспособность не повлияют.
Собственный обработчик ошибок
Для написания собственного обработчика ошибок необходимо знать, что:
- для получения информации о последней произошедшей ошибке существует функция error_get_last();
- для определения собственного обработчика ошибок существует функция set_error_handler(), но фатальные ошибки нельзя «перехватить» этой функцией;
- используя register_shutdown_function(), можно зарегистрировать свою функцию, выполняемую по завершении работы скрипта, и в ней, используя знания из первого пункта, если фатальная ошибка имела место быть, предпринять необходимые действия;
- сообщение о фатальной ошибке в любом случае попадет в буфер вывода;
- воспользовавшись функциями контроля вывода можно предотвратить отображение нежелательной информации;
- при использовании оператора управления ошибками (знак @) функция, определенная в set_error_handler() все равно будет вызвана, но функция error_reporting() в этом случае вернет 0, чем и можно пользоваться для прекращения работы или определения другого поведения своего обработчика ошибок.
Третий пункт поясню: зарегистрированная нами функция при помощи register_shutdown_function() выполнится в любом случае — корректно ли завершился скрипт, либо же был прерван в связи с критичной (фатальной) ошибкой. Второй вариант мы можем однозначно определить, воспользовавшись информацией предоставленной функцией error_get_last(), и, если ошибка все же была, выполнить наш собственный обработчик ошибок.
Продемонстрируем вышесказанное на модифицированном скрипте index.php:
<?php
/**
* Обработчик ошибок
* @param int $errno уровень ошибки
* @param string $errstr сообщение об ошибке
* @param string $errfile имя файла, в котором произошла ошибка
* @param int $errline номер строки, в которой произошла ошибка
* @return boolean
*/
function error_handler($errno, $errstr, $errfile, $errline)
{
// если ошибка попадает в отчет (при использовании оператора "@" error_reporting() вернет 0)
if (error_reporting() & $errno)
{
$errors = array(
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
);
// выводим свое сообщение об ошибке
echo "<b>{$errors[$errno]}</b>[$errno] $errstr ($errfile на $errline строке)<br />\n";
}
// не запускаем внутренний обработчик ошибок PHP
return TRUE;
}
/**
* Функция перехвата фатальных ошибок
*/
function fatal_error_handler()
{
// если была ошибка и она фатальна
if ($error = error_get_last() AND $error['type'] & ( E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR))
{
// очищаем буффер (не выводим стандартное сообщение об ошибке)
ob_end_clean();
// запускаем обработчик ошибок
error_handler($error['type'], $error['message'], $error['file'], $error['line']);
}
else
{
// отправка (вывод) буфера и его отключение
ob_end_flush();
}
}
// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// включаем буфферизацию вывода (вывод скрипта сохраняется во внутреннем буфере)
ob_start();
// устанавливаем пользовательский обработчик ошибок
set_error_handler("error_handler");
// регистрируем функцию, которая выполняется после завершения работы скрипта (например, после фатальной ошибки)
register_shutdown_function('fatal_error_handler');
require 'errors.php';
Не забываем, что ошибки смешанного типа, после объявления собственного обработчика ошибок, стали не фатальными. Плюс к этому, весь вывод скрипта до фатальной ошибки вместе с стандартным сообщением об ошибке будет сброшен.
Вообще, рассмотренный пример обработчика ошибок, обработкой, как таковой, не занимается, а только демонстрирует саму возможность. Дальнейшее его поведение зависит от ваших желаний и/или требований. Например, все случаи обращения к обработчику можно записывать в лог, а в случае фатальных ошибок, дополнительно, уведомлять об этом администратора ресурса.
Полезные ссылки
- Первоисточник: php.net/manual/ru/book.errorfunc.php
- Описание ошибок: php.net/manual/ru/errorfunc.constants.php
- Функции контроля вывода: php.net/manual/ru/ref.outcontrol.php
- Побитовые операторы: php.net/manual/ru/language.operators.bitwise.php и habrahabr.ru/post/134557
- Тематически близкая статья: habrahabr.ru/post/134499

















