Table of Contents
- Extending Exceptions
PHP has an exception model similar to that of other programming
languages. An exception can be thrown, and caught («catched») within
PHP. Code may be surrounded in a try block, to facilitate the catching
of potential exceptions. Each try must have at least one corresponding
catch or finally block.
If an exception is thrown and its current function scope has no catch
block, the exception will «bubble up» the call stack to the calling
function until it finds a matching catch block. All finally blocks it encounters
along the way will be executed. If the call stack is unwound all the way to the
global scope without encountering a matching catch block, the program will
terminate with a fatal error unless a global exception handler has been set.
The thrown object must be an instanceof Throwable.
Trying to throw an object that is not will result in a PHP Fatal Error.
As of PHP 8.0.0, the throw keyword is an expression and may be used in any expression
context. In prior versions it was a statement and was required to be on its own line.
catch
A catch block defines how to respond to a thrown exception. A catch
block defines one or more types of exception or error it can handle, and
optionally a variable to which to assign the exception. (The variable was
required prior to PHP 8.0.0.) The first catch block a thrown exception
or error encounters that matches the type of the thrown object will handle
the object.
Multiple catch blocks can be used to catch different classes of
exceptions. Normal execution (when no exception is thrown within the try
block) will continue after that last catch block defined in sequence.
Exceptions can be thrown (or re-thrown) within a catch block. If not,
execution will continue after the catch block that was triggered.
When an exception is thrown, code following the statement will not be
executed, and PHP will attempt to find the first matching catch block.
If an exception is not caught, a PHP Fatal Error will be issued with an
«Uncaught Exception ...» message, unless a handler has
been defined with set_exception_handler().
As of PHP 7.1.0, a catch block may specify multiple exceptions
using the pipe (|) character. This is useful for when
different exceptions from different class hierarchies are handled the
same.
As of PHP 8.0.0, the variable name for a caught exception is optional.
If not specified, the catch block will still execute but will not
have access to the thrown object.
finally
A finally block may also be specified after or
instead of catch blocks. Code within the finally block will always be
executed after the try and catch blocks, regardless of whether an
exception has been thrown, and before normal execution resumes.
One notable interaction is between the finally block and a return statement.
If a return statement is encountered inside either the try or the catch blocks,
the finally block will still be executed. Moreover, the return statement is
evaluated when encountered, but the result will be returned after the finally block
is executed. Additionally, if the finally block also contains a return statement,
the value from the finally block is returned.
Global exception handler
If an exception is allowed to bubble up to the global scope, it may be caught
by a global exception handler if set. The set_exception_handler()
function can set a function that will be called in place of a catch block if no
other block is invoked. The effect is essentially the same as if the entire program
were wrapped in a try—catch block with that function as the catch.
Notes
Note:
Internal PHP functions mainly use
Error reporting, only modern
Object-oriented
extensions use exceptions. However, errors can be easily translated to
exceptions with ErrorException.
This technique only works with non-fatal errors, however.Example #1 Converting error reporting to exceptions
<?php
function exceptions_error_handler($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
}set_error_handler('exceptions_error_handler');
?>
Examples
Example #2 Throwing an Exception
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
return 1/$x;
}
try {
echo
inverse(5) . "n";
echo inverse(0) . "n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "n";
}// Continue execution
echo "Hello Worldn";
?>
The above example will output:
0.2 Caught exception: Division by zero. Hello World
Example #3 Exception handling with a finally block
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
return 1/$x;
}
try {
echo
inverse(5) . "n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "n";
} finally {
echo "First finally.n";
}
try {
echo
inverse(0) . "n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "n";
} finally {
echo "Second finally.n";
}// Continue execution
echo "Hello Worldn";
?>
The above example will output:
0.2 First finally. Caught exception: Division by zero. Second finally. Hello World
Example #4 Interaction between the finally block and return
<?phpfunction test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo
test();
?>
The above example will output:
Example #5 Nested Exception
<?phpclass MyException extends Exception { }
class
Test {
public function testing() {
try {
try {
throw new MyException('foo!');
} catch (MyException $e) {
// rethrow it
throw $e;
}
} catch (Exception $e) {
var_dump($e->getMessage());
}
}
}$foo = new Test;
$foo->testing();?>
The above example will output:
Example #6 Multi catch exception handling
<?phpclass MyException extends Exception { }
class
MyOtherException extends Exception { }
class
Test {
public function testing() {
try {
throw new MyException();
} catch (MyException | MyOtherException $e) {
var_dump(get_class($e));
}
}
}$foo = new Test;
$foo->testing();?>
The above example will output:
Example #7 Omitting the caught variable
Only permitted in PHP 8.0.0 and later.
<?phpclass SpecificException extends Exception {}
function
test() {
throw new SpecificException('Oopsie');
}
try {
test();
} catch (SpecificException) {
print "A SpecificException was thrown, but we don't care about the details.";
}
?>
Example #8 Throw as an expression
Only permitted in PHP 8.0.0 and later.
<?phpclass SpecificException extends Exception {}
function
test() {
do_something_risky() or throw new Exception('It did not work');
}
try {
test();
} catch (Exception $e) {
print $e->getMessage();
}
?>
ask at nilpo dot com ¶
13 years ago
If you intend on creating a lot of custom exceptions, you may find this code useful. I've created an interface and an abstract exception class that ensures that all parts of the built-in Exception class are preserved in child classes. It also properly pushes all information back to the parent constructor ensuring that nothing is lost. This allows you to quickly create new exceptions on the fly. It also overrides the default __toString method with a more thorough one.
<?php
interface IException
{
/* Protected methods inherited from Exception class */
public function getMessage(); // Exception message
public function getCode(); // User-defined Exception code
public function getFile(); // Source filename
public function getLine(); // Source line
public function getTrace(); // An array of the backtrace()
public function getTraceAsString(); // Formated string of trace
/* Overrideable methods inherited from Exception class */
public function __toString(); // formated string for display
public function __construct($message = null, $code = 0);
}
abstract class
CustomException extends Exception implements IException
{
protected $message = 'Unknown exception'; // Exception message
private $string; // Unknown
protected $code = 0; // User-defined exception code
protected $file; // Source filename of exception
protected $line; // Source line of exception
private $trace; // Unknownpublic function __construct($message = null, $code = 0)
{
if (!$message) {
throw new $this('Unknown '. get_class($this));
}
parent::__construct($message, $code);
}
public function
__toString()
{
return get_class($this) . " '{$this->message}' in {$this->file}({$this->line})n"
. "{$this->getTraceAsString()}";
}
}
?>
Now you can create new exceptions in one line:
<?php
class TestException extends CustomException {}
?>
Here's a test that shows that all information is properly preserved throughout the backtrace.
<?php
function exceptionTest()
{
try {
throw new TestException();
}
catch (TestException $e) {
echo "Caught TestException ('{$e->getMessage()}')n{$e}n";
}
catch (Exception $e) {
echo "Caught Exception ('{$e->getMessage()}')n{$e}n";
}
}
echo
'<pre>' . exceptionTest() . '</pre>';
?>
Here's a sample output:
Caught TestException ('Unknown TestException')
TestException 'Unknown TestException' in C:xampphtdocsCustomExceptionCustomException.php(31)
#0 C:xampphtdocsCustomExceptionExceptionTest.php(19): CustomException->__construct()
#1 C:xampphtdocsCustomExceptionExceptionTest.php(43): exceptionTest()
#2 {main}
Johan ¶
11 years ago
Custom error handling on entire pages can avoid half rendered pages for the users:
<?php
ob_start();
try {
/*contains all page logic
and throws error if needed*/
...
} catch (Exception $e) {
ob_end_clean();
displayErrorPage($e->getMessage());
}
?>
christof+php[AT]insypro.com ¶
5 years ago
In case your E_WARNING type of errors aren't catchable with try/catch you can change them to another type of error like this:
<?php
set_error_handler(function($errno, $errstr, $errfile, $errline){
if($errno === E_WARNING){
// make it more serious than a warning so it can be caught
trigger_error($errstr, E_ERROR);
return true;
} else {
// fallback to default php error handler
return false;
}
});
try {
// code that might result in a E_WARNING
} catch(Exception $e){
// code to handle the E_WARNING (it's actually changed to E_ERROR at this point)
} finally {
restore_error_handler();
}
?>
Shot (Piotr Szotkowski) ¶
14 years ago
‘Normal execution (when no exception is thrown within the try block, *or when a catch matching the thrown exception’s class is not present*) will continue after that last catch block defined in sequence.’
‘If an exception is not caught, a PHP Fatal Error will be issued with an “Uncaught Exception …” message, unless a handler has been defined with set_exception_handler().’
These two sentences seem a bit contradicting about what happens ‘when a catch matching the thrown exception’s class is not present’ (and the second sentence is actually correct).
Simo ¶
7 years ago
#3 is not a good example. inverse("0a") would not be caught since (bool) "0a" returns true, yet 1/"0a" casts the string to integer zero and attempts to perform the calculation.
Edu ¶
9 years ago
The "finally" block can change the exception that has been throw by the catch block.
<?php
try{
try {
throw new Exception("Hello");
} catch(Exception $e) {
echo $e->getMessage()." catch inn";
throw $e;
} finally {
echo $e->getMessage()." finally n";
throw new Exception("Bye");
}
} catch (Exception $e) {
echo $e->getMessage()." catch outn";
}
?>
The output is:
Hello catch in
Hello finally
Bye catch out
daviddlowe dot flimm at gmail dot com ¶
5 years ago
Starting in PHP 7, the classes Exception and Error both implement the Throwable interface. This means, if you want to catch both Error instances and Exception instances, you should catch Throwable objects, like this:
<?phptry {
throw new Error( "foobar" );
// or:
// throw new Exception( "foobar" );
}
catch (Throwable $e) {
var_export( $e );
}?>
mlaopane at gmail dot com ¶
4 years ago
<?php/**
* You can catch exceptions thrown in a deep level function
*/function employee()
{
throw new Exception("I am just an employee !");
}
function
manager()
{
employee();
}
function
boss()
{
try {
manager();
} catch (Exception $e) {
echo $e->getMessage();
}
}boss(); // output: "I am just an employee !"
telefoontoestel at nospam dot org ¶
8 years ago
When using finally keep in mind that when a exit/die statement is used in the catch block it will NOT go through the finally block.
<?php
try {
echo "try block<br />";
throw new Exception("test");
} catch (Exception $ex) {
echo "catch block<br />";
} finally {
echo "finally block<br />";
}// try block
// catch block
// finally block
?>
<?php
try {
echo "try block<br />";
throw new Exception("test");
} catch (Exception $ex) {
echo "catch block<br />";
exit(1);
} finally {
echo "finally block<br />";
}// try block
// catch block
?>
Tom Polomsk ¶
8 years ago
Contrary to the documentation it is possible in PHP 5.5 and higher use only try-finally blocks without any catch block.
Sawsan ¶
11 years ago
the following is an example of a re-thrown exception and the using of getPrevious function:
<?php
$name
= "Name";//check if the name contains only letters, and does not contain the word nametry
{
try
{
if (preg_match('/[^a-z]/i', $name))
{
throw new Exception("$name contains character other than a-z A-Z");
}
if(strpos(strtolower($name), 'name') !== FALSE)
{
throw new Exception("$name contains the word name");
}
echo "The Name is valid";
}
catch(Exception $e)
{
throw new Exception("insert name again",0,$e);
}
}
catch (
Exception $e)
{
if ($e->getPrevious())
{
echo "The Previous Exception is: ".$e->getPrevious()->getMessage()."<br/>";
}
echo "The Exception is: ".$e->getMessage()."<br/>";
}?>
lscorionjs at gmail dot com ¶
24 days ago
<?phptry {
$str = 'hi';
throw new Exception();
} catch (Exception) {
var_dump($str);
} finally {
var_dump($str);
}?>
Output:
string(2) "hi"
string(2) "hi"
ilia-yats at ukr dot net ¶
1 month ago
Note some undocumented details about exceptions thrown from 'finally' blocks.
When exception is thrown from 'finally' block, it overrides the original not-caught (or re-thrown) exception. So the behavior is similar to 'return': value returned from 'finally' overrides the one returned earlier. And the original exception is automatically appended to the exceptions chain, i.e. becomes 'previous' for the new one. Example:
<?php
try {
try {
throw new Exception('thrown from try');
} finally {
throw new Exception('thrown from finally');
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
} // will output:
// thrown from finally
// thrown from try
?>
Example with re-throwing:
<?php
try {
try {
throw new Exception('thrown from try');
} catch (Exception $e) {
throw new Exception('thrown from catch');
} finally {
throw new Exception('thrown from finally');
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
} // will output:
// thrown from finally
// thrown from catch
?>
The same happens even if explicitly pass null as previous exception:
<?php
try {
try {
throw new Exception('thrown from try');
} finally {
throw new Exception('thrown from finally', null, null);
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
} // will output:
// thrown from finally
// thrown from try
?>
Also it is possible to pass previous exception explicitly, the 'original' one will be still appended to the chain, e.g.:
<?php
try {
try {
throw new Exception('thrown from try');
} finally {
throw new Exception(
'thrown from finally',
null,
new Exception('Explicitly set previous!')
);
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getPrevious()->getMessage();
} // will output:
// thrown from finally
// Explicitly set previous!
// thrown from try
?>
This seems to be true for versions 5.6-8.2.
Daan ¶
1 year ago
I would like to emphasise that you can not rethrow an Exception inside a catch-block and expect that the next catch-block will handle it.
<?php try {
throw new RuntimeException('error');
} catch (RuntimeException $e) {
throw $e;
} catch (Exception $e) {
// this will not be executed[
}
?>
Содержание
- Fatal error php пример
- Fatal error php это:
- Вывод ошибки «Fatal error php»
- Пример «Fatal error php»
- Пример страницы с ошибкой «Fatal error php»
- Вывод о примере страницы с ошибкой «Fatal error php»
- Как избавиться от ошибки «Fatal error php» с помощью @
- Давайте проведем эксперимент с фатальной ошибкой и собачкой:
- Подавление фатальной ошибки с помощью и @:
- Как избавиться от ошибки «Fatal error php» @ №2
- Показывать ошибки «Fatal error» админу
- Пример показывать ошибки «Fatal error» админу
- Показать массив «Fatal error php»
- Код вывода массива ошибок «Fatal error php»
- Выполнение Кода вывода массива ошибок «Fatal error php»
- Как найти/исправить ошибку «Fatal error php».
- PHP для начинающих. Обработка ошибок // PHP
- Ошибки
- Разновидности в семействе ошибок
- Фатальные ошибки
- Не фатальные
- Обрабатываемые
- Приручение
- О прожорливости
- Где собака зарыта
- Исключения
- PHP7 – всё не так, как было раньше
- Отладка
- Assert
- В заключение
Fatal error php пример
Fatal — роковой, пагубный(прилагательное), еще можно перевести как: «фатальный»
php — язык программирования php.
Fatal error php это:
Fatal error php это такая ошибка, когда дальнейшее выполнение кода php невозможно!
Программа останавливается на последней ошибке такого типа!
Вывод ошибки «Fatal error php»
Пример «Fatal error php»
На любой тестовой странице. давайте вызовем не существующую функцию:
Предположим, что результат нам нужно показать с помощью echo
После вызова этой функции выведем :
Пример страницы с ошибкой «Fatal error php»
Для того, чтобы посмотреть на ошибку «Fatal error в php» перейдите по ссылке :
Вывод о примере страницы с ошибкой «Fatal error php»
Как вы увидели. на странице получили ошибку типа «Fatal error» о несуществующей функции.
Как избавиться от ошибки «Fatal error php» с помощью @
Давайте проведем эксперимент с фатальной ошибкой и собачкой:
И мы получим вот такой код:
Подавление фатальной ошибки с помощью и @:
Выше приведенный пример «Fatal error» помести на отдельной странице:
Как избавиться от ошибки «Fatal error php» @ №2
Выше я уже написал, что :
Главное вы должны запомнить, что : «ошибка — это наш друг, а не враг»!
Поэтому. вы как админ должны видеть все ошибки!
Этим способом я пользуюсь еще со «времен царя гороха» — на момент написания данной статьи я пользуюсь именно этим способом.
Показывать ошибки «Fatal error» админу
Весь движок у меня в файле «__main_config.php».
По условию if если существует сессия админа — загружаем без @.
Для всех других загружаем с @ — это будет белый лист.
Пример показывать ошибки «Fatal error» админу
Показать массив «Fatal error php»
Если вы например хотите, чтобы показать массив, ну, или выполнить свой код в случае ошибки, для этого вам понадобится:
Внутри условия if выводим массив с помощью print_r. Вы можете здесь расположить свой код. Например отсылка на емайл, либо запись в файл. и т.д.
Код вывода массива ошибок «Fatal error php»
Выполнение Кода вывода массива ошибок «Fatal error php»
Ну и нам осталось вывести данный код на отдельной странице.
Как найти/исправить ошибку «Fatal error php».
Для этого вам понадобится:
По пути который указан — найти данный файл.
Скачать его на компьютер.
Для многих редакторов кода поиск по строке осуществляется нажатие кнопки ctrl + G, вставляем номер строки.
Если строка найдена. закомментируем её с помощь слеша вправо:
Закачиваем обратно на сервер — проверяем. эта ли была ошибка.
Источник
PHP для начинающих. Обработка ошибок // PHP
Не совершает ошибок только тот, кто ничего не делает, и мы тому пример – трудимся не покладая рук над созданием рабочих мест для тестировщиков 🙂
О да, в этой статье я поведу свой рассказа об ошибках в PHP, и том как их обуздать.
Ошибки
Разновидности в семействе ошибок
Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.
Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:
Фатальные ошибки
Самый грозный вид ошибок – фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.
E_PARSE
Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:
Или написали на непонятном языке:
Лишние скобочки тоже встречаются, и не важно круглые либо фигурные:
Отмечу один важный момент – код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:
E_ERROR
Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин, так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:
Не был найден подключаемый файл:
Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:
При попытке вызвать несуществующий метод класса:
Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:
Очень часто происходит при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти
Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug:
Не фатальные
Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик, и именно они доставляют больше всего хлопот у начинающих разработчиков.
E_WARNING
Частенько встречается, когда подключаешь файл с использованием include , а его не оказывается на сервере или ошиблись указывая путь к файлу:
Бывает, если используешь неправильный тип аргументов при вызове функций:
Их очень много, и перечислять все не имеет смысла…
E_NOTICE
Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.
Когда обращаются к неопределенной переменной:
Когда обращаются к несуществующему элементу массива:
Когда обращаются к несуществующей константе:
Когда не конвертируют типы данных:
Для избежания подобных ошибок – будьте внимательней, и если вам IDE подсказывает о чём-то – не игнорируйте её:
E_STRICT
Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывают. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this :
E_DEPRECATED
Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):
В моём редакторе подобные функции будут зачёркнуты:
Обрабатываемые
Этот вид, которые разводит сам разработчик кода, я их уже давно не встречал, не рекомендую их вам заводить:
- E_USER_ERROR – критическая ошибка
- E_USER_WARNING – не критическая ошибка
- E_USER_NOTICE – сообщения которые не являются ошибками
Отдельно стоит отметить E_USER_DEPRECATED – этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():
Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы 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 , то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:
У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет – пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём
С обработчиком, который написан выше есть одна существенная проблема – он не ловит фатальные ошибки, и вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:
Данная функция будет срабатывать всегда!
Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль “последних”:
Задание
Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.
О прожорливости
Проведём простой тест, и выясним – сколько драгоценных ресурсов кушает самая тривиальная ошибка:
В результате запуска данного скрипта у меня получился вот такой результат:
Теперь добавим ошибку в цикле:
Результат ожидаемо хуже, и на порядок (даже на два порядка!):
Вывод однозначен – ошибки в коде приводят к лишней прожорливости скриптов – так что во время разработки и тестирования приложения включайте отображение всех ошибок!
Тестирование проводил на PHP версии 5.6, в седьмой версии результат лучше – 0.0004 секунды против 0.0050 – разница только на один порядок, но в любом случае результат стоит прикладываемых усилий по исправлению ошибок
Где собака зарыта
В PHP есть спец символ «@» – оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:
При этом обработчик ошибок указанный в set_error_handler() всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting() внутри обработчика, в этом случае она вернёт 0 .
Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло
Исключения
В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Думаю да, ведь это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений
Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.
К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение – сохранить в другое место или сообщить пользователю о проблеме.
Исключение – это объект который наследуется от класса Exception , содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, “бросить”) при помощи оператора throw , и можно перехватить (“поймать”) оператором catch . Код генерирующий исключение, должен быть окружен блоком try , для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally :
В каких случаях стоит применять исключения:
- если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
- если используемый вами фреймверк или библиотека декларируют их использование
Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл – помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:
Соответственно ловить данные исключения будем примерно так:
В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую – различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:
Теперь, если использовать эти исключения то можно получить следующий код:
Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.
Так, а что будет если не поймать исключение? Вы получите “Fatal Error: Uncaught exception …”. Неприятно.
Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:
Ещё расскажу про конструкцию с использованием блока finally – этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:
Для понимания того, что это нам даёт приведу следующий пример использования блока finally :
Т.е. запомните – блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).
Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код 😉
Задание
Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира – посмотрите как это круто выглядит у whoops.
PHP7 – всё не так, как было раньше
Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы столкнётесь через год работы PHP разработчиком. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот – в PHP7 это решили исправить, но как обычно завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:
- при возникновении фатальных ошибок типа E_ERROR или фатальных ошибок с возможностью обработки E_RECOVERABLE_ERROR PHP выбрасывает исключение
- эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
- эти исключения наследуют класс Error
- оба класса Exception и Error реализуют интерфейс Throwable
- вы не можете реализовать интерфейс Throwable в своём коде
Интерфейс Throwable практически полностью повторяет нам Exception :
Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:
В результате ошибку поймаем и выведем:
Как видите – поймали исключение ParseError, которое является наследником исключения Error , который реализует интерфейс Throwable , в доме который построил Джек. Ещё есть другие, но не буду мучать – для наглядности приведу иерархию исключений:
TypeError – для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:
ArithmeticError – могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:
AssertionError – редкий зверь, появляется когда условие заданное в assert() не выполняется:
При настройках production-серверов, директивы zend.assertions и assert.exception отключают, и это правильно
Задание
Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.
При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7
Отладка
Иногда для отладки кода нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:
В результате выполнения функции debug_print_backtrace() будет выведен список вызовов приведших нас к данной точке:
Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу] , но я не встречал использования оных.
Assert
Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP, собственно это кусочек контрактной методологии программирования, и дальше я расскажу вам как я никогда его не использовал 🙂
Первый случай – это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:
В результате выполнения данного кода получим E_WARNING :
PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError :
В результате ожидаемо получаем не пойманный AssertionError . При необходимости, можно выбрасывать произвольное исключение:
Но я бы рекомендовал использовать метки @TODO , современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними
Второй вариант использования – это создание некоего подобия TDD, но помните – это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:
Третий теоретический вариант – это непосредственно контрактное программирование – когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):
Никогда не используйте assert() для проверки входных параметров, ведь фактически assert() интерпретирует строковую переменную (ведёт себя как eval() ), а это чревато PHP-инъекцией. И да, это правильное поведение, т.к. просто отключив assert’ы всё что передаётся внутрь будет проигнорировано, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения
Если у вас есть живой опыт использования assert() – поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме – PHP Assertions, с таким же вопросом в конце 🙂
В заключение
Я за вас напишу выводы из данной статьи:
- Ошибкам бой – их не должно быть в вашем коде
- Используйте исключения – работу с ними нужно правильно организовать и будет счастье
- Assert – узнали о них, и хорошо
Источник
Антон Шевчук // Web-разработчик

Не совершает ошибок только тот, кто ничего не делает, и мы тому пример – трудимся не покладая рук над созданием рабочих мест для тестировщиков 
О да, в этой статье я поведу свой рассказа об ошибках в PHP, и том как их обуздать.
Ошибки
Разновидности в семействе ошибок
Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.
Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
Фатальные ошибки
Самый грозный вид ошибок – фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.
E_PARSE
Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:
<?php
/**
Parse error: syntax error, unexpected end of file
*/
{
Или написали на непонятном языке:
<?php /** Parse error: syntax error, unexpected '...' (T_STRING) */ Тут будет ошибка парсера
Лишние скобочки тоже встречаются, и не важно круглые либо фигурные:
<?php /** Parse error: syntax error, unexpected '}' */ }
Отмечу один важный момент – код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:
<?php
// этот код не сработает
error_reporting(E_ALL);
ini_set('display_errors', 1);
// т.к. вот тут
ошибка парсера
E_ERROR
Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин, так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:
Не был найден подключаемый файл:
/** Fatal error: require_once(): Failed opening required 'not-exists.php' (include_path='.:/usr/share/php:/usr/share/pear') */ require_once 'not-exists.php';
Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:
/** Fatal error: Uncaught exception 'Exception' */ throw new Exception();
При попытке вызвать несуществующий метод класса:
/** Fatal error: Call to undefined method stdClass::notExists() */ $stdClass = new stdClass(); $stdClass->notExists();
Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:
/**
Fatal Error: Allowed Memory Size
*/
$arr = array();
while (true) {
$arr[] = str_pad(' ', 1024);
}
Очень часто происходит при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти
Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug:
/**
Fatal error: Maximum function nesting level of '256' reached, aborting!
*/
function deep() {
deep();
}
deep();
Не фатальные
Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик, и именно они доставляют больше всего хлопот у начинающих разработчиков.
E_WARNING
Частенько встречается, когда подключаешь файл с использованием include, а его не оказывается на сервере или ошиблись указывая путь к файлу:
/** Warning: include_once(): Failed opening 'not-exists.php' for inclusion */ include_once 'not-exists.php';
Бывает, если используешь неправильный тип аргументов при вызове функций:
/**
Warning: join(): Invalid arguments passed
*/
join('string', 'string');
Их очень много, и перечислять все не имеет смысла…
E_NOTICE
Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.
Когда обращаются к неопределенной переменной:
/** Notice: Undefined variable: a */ echo $a;
Когда обращаются к несуществующему элементу массива:
<?php /** Notice: Undefined index: a */ $b = array(); $b['a'];
Когда обращаются к несуществующей константе:
/** Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT' */ echo UNKNOWN_CONSTANT;
Когда не конвертируют типы данных:
/** Notice: Array to string conversion */ echo array();
Для избежания подобных ошибок – будьте внимательней, и если вам IDE подсказывает о чём-то – не игнорируйте её:
E_STRICT
Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывают. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this:
/**
Strict standards: Non-static method Strict::test() should not be called statically
*/
class Strict {
public function test() {
echo 'Test';
}
}
Strict::test();
E_DEPRECATED
Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):
/**
Deprecated: Function split() is deprecated
*/
// популярная функция, всё никак не удалят из PHP
// deprecated since 5.3
split(',', 'a,b');
В моём редакторе подобные функции будут зачёркнуты:
Обрабатываемые
Этот вид, которые разводит сам разработчик кода, я их уже давно не встречал, не рекомендую их вам заводить:
E_USER_ERROR– критическая ошибкаE_USER_WARNING– не критическая ошибкаE_USER_NOTICE– сообщения которые не являются ошибками
Отдельно стоит отметить E_USER_DEPRECATED – этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():
/**
* @deprecated Deprecated since version 1.2, to be removed in 2.0
*/
function generateToken() {
trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
// ...
// code ...
// ...
}
Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы
display_errors:
- если
display_errors = on, то в случае ошибки браузер получит html c текстом ошибки и кодом 200- если же
display_errors = off, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок – код будет работать неправильно, но никому об этом не расскажет
Приручение
Для работы с ошибками в PHP существует 3 функции:
- set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
- error_get_last() — получает информацию о последней ошибке
- register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого
Теперь немного подробностей об обработке ошибок с использованием set_error_handler(), в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:
$errno– первый аргумент содержит тип ошибки в виде целого числа$errstr– второй аргумент содержит сообщение об ошибке$errfile– необязательный третий аргумент содержит имя файла, в котором произошла ошибка$errline– необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка$errcontext– необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка
В случае если обработчик вернул true, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:
<?php
// включаем отображение всех ошибок, кроме E_NOTICE
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_errors', 1);
// наш обработчик ошибок
function myHandler($level, $message, $file, $line, $context) {
// в зависимости от типа ошибки формируем заголовок сообщения
switch ($level) {
case E_WARNING:
$type = 'Warning';
break;
case E_NOTICE:
$type = 'Notice';
break;
default;
// это не E_WARNING и не E_NOTICE
// значит мы прекращаем обработку ошибки
// далее обработка ложится на сам PHP
return false;
}
// выводим текст ошибки
echo "<h2>$type: $message</h2>";
echo "<p><strong>File</strong>: $file:$line</p>";
echo "<p><strong>Context</strong>: $". join(', $', array_keys($context))."</p>";
// сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется
return true;
}
// регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок
set_error_handler('myHandler', E_ALL);
У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет – пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём
С обработчиком, который написан выше есть одна существенная проблема – он не ловит фатальные ошибки, и вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:
function shutdown() {
echo 'Этот текст будет всегда отображаться';
}
register_shutdown_function('shutdown');
Данная функция будет срабатывать всегда!
Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль “последних”:
function shutdown() {
$error = error_get_last();
if (
// если в коде была допущена ошибка
is_array($error) &&
// и это одна из фатальных ошибок
in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])
) {
// очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях)
while (ob_get_level()) {
ob_end_clean();
}
// выводим описание проблемы
echo 'Сервер находится на техническом обслуживании, зайдите позже';
}
}
register_shutdown_function('shutdown');
Задание
Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.
О прожорливости
Проведём простой тест, и выясним – сколько драгоценных ресурсов кушает самая тривиальная ошибка:
/**
* Этот код не вызывает ошибок
*/
// сохраняем параметры памяти и времени выполнения скрипта
$memory = memory_get_usage();
$time= microtime(true);
$a = '';
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[$a] = $i;
}
printf('%f seconds <br/>', microtime(true) - $time);
echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
В результате запуска данного скрипта у меня получился вот такой результат:
0.002867 seconds 984 bytes
Теперь добавим ошибку в цикле:
/**
* Этот код содержит ошибку
*/
// сохраняем параметры памяти и времени выполнения скрипта
$memory = memory_get_usage();
$time= microtime(true);
$a = '';
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[$b] = $i; // тут ошиблись с именем переменной
}
printf('%f seconds <br/>', microtime(true) - $time);
echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
Результат ожидаемо хуже, и на порядок (даже на два порядка!):
0.263645 seconds 992 bytes
Вывод однозначен – ошибки в коде приводят к лишней прожорливости скриптов – так что во время разработки и тестирования приложения включайте отображение всех ошибок!
Тестирование проводил на PHP версии 5.6, в седьмой версии результат лучше – 0.0004 секунды против 0.0050 – разница только на один порядок, но в любом случае результат стоит прикладываемых усилий по исправлению ошибок
Где собака зарыта
В PHP есть спец символ «@» – оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:
<?php
echo @UNKNOWN_CONSTANT;
При этом обработчик ошибок указанный в set_error_handler() всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting() внутри обработчика, в этом случае она вернёт 0.
Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло
Исключения
В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Думаю да, ведь это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений
Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.
К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение – сохранить в другое место или сообщить пользователю о проблеме.
Исключение – это объект который наследуется от класса Exception, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, “бросить”) при помощи оператора throw, и можно перехватить (“поймать”) оператором catch. Код генерирующий исключение, должен быть окружен блоком try, для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally:
try {
// код который может выбросить исключение
if (rand(0, 1)) {
throw new Exception('One')
} else {
echo 'Zero';
}
} catch (Exception $e) {
// код который может обработать исключение
echo $e->getMessage();
}
В каких случаях стоит применять исключения:
- если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
- если используемый вами фреймверк или библиотека декларируют их использование
Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл – помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:
$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';
// директории может не быть
if (!is_dir($directory)) {
throw new Exception('Directory `logs` is not exists');
}
// может не быть прав на запись в директорию
if (!is_writable($directory)) {
throw new Exception('Directory `logs` is not writable');
}
// возможно кто-то уже создал файл, и закрыл к нему доступ
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
throw new Exception('System can't create log file');
}
fputs($file, date('[H:i:s]') . " donen");
fclose($file);
Соответственно ловить данные исключения будем примерно так:
try {
// код который пишет в файл
// ...
} catch (Exception $e) {
// выводим текст ошибки
echo 'Не получилось: '. $e->getMessage();
}
В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую – различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:
// исключения файловой системы
class FileSystemException extends Exception {}
// исключения связанные с директориями
class DirectoryException extends FileSystemException {
// коды исключений
const DIRECTORY_NOT_EXISTS = 1;
const DIRECTORY_NOT_WRITABLE = 2;
}
// исключения связанные с файлами
class FileException extends FileSystemException {}
Теперь, если использовать эти исключения то можно получить следующий код:
try {
// код который пишет в файл
if (!is_dir($directory)) {
throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS);
}
if (!is_writable($directory)) {
throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE);
}
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
throw new FileException('System can't open log file');
}
fputs($file, date('[H:i:s]'') . " donen");
fclose($file);
} catch (DirectoryException $e) {
echo 'С директорией возникла проблема: '. $e->getMessage();
} catch (FileException $e) {
echo 'С файлом возникла проблема: '. $e->getMessage();
} catch (FileSystemException $e) {
echo 'Ошибка файловой системы: '. $e->getMessage();
} catch (Exception $e) {
echo 'Ошибка сервера: '. $e->getMessage();
}
Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.
Так, а что будет если не поймать исключение? Вы получите “Fatal Error: Uncaught exception …”. Неприятно.
Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:
// в качестве обработчика событий
// будем использовать анонимную функцию
set_exception_handler(function($exception) {
/** @var Exception $exception */
echo $exception->getMessage(), "<br/>n";
echo $exception->getFile(), ':', $exception->getLine(), "<br/>n";
echo $exception->getTraceAsString(), "<br/>n";
});
Ещё расскажу про конструкцию с использованием блока finally – этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:
try {
// код который может выбросить исключение
} catch (Exception $e) {
// код который может обработать исключение
// если конечно оно появится
} finally {
// код, который будет выполнен при любом раскладе
}
Для понимания того, что это нам даёт приведу следующий пример использования блока finally:
try {
// где-то глубоко внутри кода
// соединение с базой данных
$handler = mysqli_connect('localhost', 'root', '', 'test');
try {
// при работе с БД возникла исключительная ситуация
// ...
throw new Exception('DB error');
} catch (Exception $e) {
// исключение поймали, обработали на своём уровне
// и должны его пробросить вверх, для дальнейшей обработки
throw new Exception('Catch exception', 0, $e);
} finally {
// но, соединение с БД необходимо закрыть
// будем делать это в блоке finally
mysqli_close($handler);
}
// этот код не будет выполнен, если произойдёт исключение в коде выше
echo "Ok";
} catch (Exception $e) {
// ловим исключение, и выводим текст
echo $e->getMessage();
echo "<br/>";
// выводим информацию о первоначальном исключении
echo $e->getPrevious()->getMessage();
}
Т.е. запомните – блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).
Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код 
Задание
Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира – посмотрите как это круто выглядит у whoops.
PHP7 – всё не так, как было раньше
Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы столкнётесь через год работы PHP разработчиком. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот – в PHP7 это решили исправить, но как обычно завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:
- при возникновении фатальных ошибок типа
E_ERRORили фатальных ошибок с возможностью обработкиE_RECOVERABLE_ERRORPHP выбрасывает исключение - эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
- эти исключения наследуют класс Error
- оба класса Exception и Error реализуют интерфейс Throwable
- вы не можете реализовать интерфейс Throwable в своём коде
Интерфейс Throwable практически полностью повторяет нам Exception:
interface Throwable
{
public function getMessage(): string;
public function getCode(): int;
public function getFile(): string;
public function getLine(): int;
public function getTrace(): array;
public function getTraceAsString(): string;
public function getPrevious(): Throwable;
public function __toString(): string;
}
Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:
try {
// файл, который вызывает ошибку парсера
include 'e_parse_include.php';
} catch (Error $e) {
var_dump($e);
}
В результате ошибку поймаем и выведем:
object(ParseError)#1 (7) {
["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)"
["string":"Error":private] => string(0) ""
["code":protected] => int(0)
["file":protected] => string(49) "/www/education/error/e_parse_include.php"
["line":protected] => int(4)
["trace":"Error":private] => array(0) { }
["previous":"Error":private] => NULL
}
Как видите – поймали исключение ParseError, которое является наследником исключения Error, который реализует интерфейс Throwable, в доме который построил Джек. Ещё есть другие, но не буду мучать – для наглядности приведу иерархию исключений:
interface Throwable
|- Exception implements Throwable
| |- ErrorException extends Exception
| |- ... extends Exception
| `- ... extends Exception
`- Error implements Throwable
|- TypeError extends Error
|- ParseError extends Error
|- ArithmeticError extends Error
| `- DivisionByZeroError extends ArithmeticError
`- AssertionError extends Error
TypeError – для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:
try {
(function(int $one, int $two) {
return;
})('one', 'two');
} catch (TypeError $e) {
echo $e->getMessage();
}
ArithmeticError – могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:
try {
1 << -1;
} catch (ArithmeticError $e) {
echo $e->getMessage();
}
DivisionByZeroError – ошибка деления на ноль:
try {
1 / 0;
} catch (ArithmeticError $e) {
echo $e->getMessage();
}
AssertionError – редкий зверь, появляется когда условие заданное в assert() не выполняется:
ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);
try {
assert(1 === 0);
} catch (AssertionError $e) {
echo $e->getMessage();
}
При настройках production-серверов, директивы
zend.assertionsиassert.exceptionотключают, и это правильно
Задание
Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.
При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7
Отладка
Иногда для отладки кода нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:
<?php
function example() {
echo '<pre>';
debug_print_backtrace();
echo '</pre>';
}
class ExampleClass {
public static function method () {
example();
}
}
ExampleClass::method();
В результате выполнения функции debug_print_backtrace() будет выведен список вызовов приведших нас к данной точке:
#0 example() called at [/www/education/error/backtrace.php:10] #1 ExampleClass::method() called at [/www/education/error/backtrace.php:14]
Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу], но я не встречал использования оных.
Assert
Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP, собственно это кусочек контрактной методологии программирования, и дальше я расскажу вам как я никогда его не использовал 
Первый случай – это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:
// включаем вывод ошибок
error_reporting(E_ALL);
ini_set('display_errors', 1);
// включаем asserts
ini_set('zend.assertions', 1);
ini_set('assert.active', 1);
assert(false, "Remove it!");
В результате выполнения данного кода получим E_WARNING:
Warning: assert(): Remove it! failed
PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError:
// включаем asserts
ini_set('zend.assertions', 1);
ini_set('assert.active', 1);
// переключаем на исключения
ini_set('assert.exception', 1);
assert(false, "Remove it!");
В результате ожидаемо получаем не пойманный AssertionError. При необходимости, можно выбрасывать произвольное исключение:
assert(false, new Exception("Remove it!"));
Но я бы рекомендовал использовать метки
@TODO, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними
Второй вариант использования – это создание некоего подобия TDD, но помните – это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:
// callback-функция для вывода информации в браузер
function backlog($script, $line, $code, $message) {
echo "<h3>$message</h3>";
highlight_string ($code);
}
// устанавливаем callback-функцию
assert_options(ASSERT_CALLBACK, 'backlog');
// отключаем вывод предупреждений
assert_options(ASSERT_WARNING, false);
// пишем проверку и её описание
assert("sqr(4) == 16", "When I send integer, function should return square of it");
// функция, которую проверяем
function sqr($a) {
return; // она не работает
}
Третий теоретический вариант – это непосредственно контрактное программирование – когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):
/**
* Настройки соединения должны передаваться в следующем виде
*
* [
* 'host' => 'localhost',
* 'port' => 3306,
* 'name' => 'dbname',
* 'user' => 'root',
* 'pass' => ''
* ]
*
* @param $settings
*/
function setupDb ($settings) {
// проверяем настройки
assert(isset($settings['host']), 'Db `host` is required');
assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer');
assert(isset($settings['name']), 'Db `name` is required, should be integer');
// соединяем с БД
// ...
}
setupDb(['host' => 'localhost']);
Никогда не используйте
assert()для проверки входных параметров, ведь фактическиassert()интерпретирует строковую переменную (ведёт себя какeval()), а это чревато PHP-инъекцией. И да, это правильное поведение, т.к. просто отключив assert’ы всё что передаётся внутрь будет проигнорировано, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения
Если у вас есть живой опыт использования assert() – поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме – PHP Assertions, с таким же вопросом в конце 
В заключение
Я за вас напишу выводы из данной статьи:
- Ошибкам бой – их не должно быть в вашем коде
- Используйте исключения – работу с ними нужно правильно организовать и будет счастье
- Assert – узнали о них, и хорошо
P.S. Спасибо Максиму Слесаренко за помощь в написании статьи
«Fatal error php«. Примеры «Fatal error php». Вывести массив ошибки «Fatal error php».
Что такое «Fatal error php»
Для того, чтобы понять, что такое «Fatal error в php» — достаточно перевести это словосочетание:
Перевод «Fatal error php»
Fatal — роковой, пагубный(прилагательное), еще можно перевести как: «фатальный»
error — ошибка(существительное).
php — язык программирования php.
Все равно не понятно, что такое «Fatal error php»
Fatal error php это:
Fatal error php это такая ошибка, когда дальнейшее выполнение кода php невозможно!
Программа останавливается на последней ошибке такого типа!
Вывод ошибки «Fatal error php»
Fatal error: «Название ошибки» in «путь до файла» on line «номер строки»
Пример «Fatal error php»
Для того, чтобы эмулировать «Fatal error в php» вам понадобится(например):
На любой тестовой странице… давайте вызовем не существующую функцию:
fatalErrorPhp();
Предположим, что результат нам нужно показать с помощью echo
После вызова этой функции выведем :
<? echo fatalErrorPhp(); echo ‘Привет мир’;?>
Пример страницы с ошибкой «Fatal error php»
Для того, чтобы посмотреть на ошибку «Fatal error в php» перейдите по ссылке :
fatalErrorPhp
Вывод о примере страницы с ошибкой «Fatal error php»
Как вы увидели… на странице получили ошибку типа «Fatal error» о несуществующей функции…
Как избавиться от ошибки «Fatal error php» с помощью @
Сразу скажу, что с помощью собачки ошибку «Fatal error» подавить можно, но код дальше исполняться не будет!
Давайте проведем эксперимент с фатальной ошибкой и собачкой:
Возьмем ту ошибку, которая у нас была выше и перед данной функцией поставим собачку.
@fatalErrorPhp();
И мы получим вот такой код:
<?
echo @fatalErrorPhp();
echo 'Привет мир';
?>
Подавление фатальной ошибки с помощью и @:
Выше приведенный пример «Fatal error» помести на отдельной странице:
fatalErrorPhp
Как избавиться от ошибки «Fatal error php» @ №2
Выше я уже написал, что :
Главное вы должны запомнить, что : «ошибка — это наш друг, а не враг»!
Поэтому… вы как админ должны видеть все ошибки!
Этим способом я пользуюсь еще со «времен царя гороха» — на момент написания данной статьи я пользуюсь именно этим способом.
Показывать ошибки «Fatal error» админу
Весь движок у меня в файле «__main_config.php».
По условию if если существует сессия админа — загружаем без @.
Для всех других загружаем с @ — это будет белый лист…
Пример показывать ошибки «Fatal error» админу
<?
if ($_SESSION[admin_name])
{
include $file_cms. '/__main_config.php';
}
else
{
@include $file_cms. '/__main_config.php';
}
?>
Показать массив «Fatal error php»
Если вы например хотите, чтобы показать массив, ну, или выполнить свой код в случае ошибки, для этого вам понадобится:
Код вывода массива ошибок «Fatal error php»
<?
ini_set('display_errors', 0);
function phpFatalError()
{
$error = error_get_last();
if ($error['type'] == E_ERROR)
{
echo "<pre>fatal error:n";
print_r($error);
echo "</pre>";
}
}
register_shutdown_function('phpFatalError');
fatalErrorPhp();
?>
Выполнение Кода вывода массива ошибок «Fatal error php»
Ну и нам осталось вывести данный код на отдельной странице…
fatalErrorPhp
Как найти/исправить ошибку «Fatal error php».
Этот тип ошибки «Fatal error» в php исправляется очень просто!
Для этого вам понадобится:
По пути который указан — найти данный файл.
Скачать его на компьютер.
Открыть его в любом редакторе кода.
Для многих редакторов кода поиск по строке осуществляется нажатие кнопки ctrl + G, вставляем номер строки.
Если строка найдена… закомментируем её с помощь слеша вправо:
//здесь строка….
Закачиваем обратно на сервер — проверяем… эта ли была ошибка…
PHP для начинающих. Обработка ошибок +32
PHP
Рекомендация: подборка платных и бесплатных курсов Java — https://katalog-kursov.ru/
Не совершает ошибок только тот, кто ничего не делает, и мы тому пример — сидим и трудимся не покладая рук, читаем Хабр 
В этой статье я поведу свой рассказа об ошибках в 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 (да, данная ошибка может проявиться в таком виде только при включении 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;
Когда обращаются к несуществующему элементу массива:
/**
* Notice: Undefined index: a
*/
$b = [];
$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();
Данный тип ошибок актуален для PHP версии 5.6, и практически все их выпилили из
7-ки. Почитать подробней можно в соответствующей RFC. Если кто знает где ещё остались данные ошибки, то напишите в комментариях
E_DEPRECATED
Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):
/**
* Deprecated: Function split() is deprecated
*/
// данная функция, удалена из PHP 7.0
// считается устаревшей с PHP 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');
Хотел обратить внимание, что данный код хоть ещё и встречается для обработки ошибок, и вы возможно вы даже с ним столкнётесь, но он потерял актуальность начиная с 7-ой версии PHP. Что пришло на замену я расскажу чуть погодя.
Задание
Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.
О прожорливости
Проведём простой тест, и выясним — сколько драгоценных ресурсов кушает самая тривиальная ошибка:
/**
* Этот код не вызывает ошибок
*/
// засекаем время выполнения скрипта
$time= microtime(true);
define('AAA', 'AAA');
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[AAA] = $i;
}
printf('%f seconds <br/>', microtime(true) - $time);
В результате запуска данного скрипта у меня получился вот такой результат:
0.002867 seconds
Теперь добавим ошибку в цикле:
/**
* Этот код содержит ошибку
*/
// засекаем время выполнения скрипта
$time= microtime(true);
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[BBB] = $i; // тут используем константанту, которая у нас не объявлена
}
printf('%f seconds <br/>', microtime(true) - $time);
Результат ожидаемо хуже, и на порядок (даже на два порядка!):
0.263645 seconds
Вывод однозначен — ошибки в коде приводят к лишней прожорливости скриптов — так что во время разработки и тестирования приложения включайте отображение всех ошибок!
Тестирование проводил на различных версиях PHP и везде разница в десятки раз, так что пусть это будет ещё одним поводом для исправления всех ошибок в коде
Где собака зарыта
В 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 (random_int(0, 1)) {
throw new Exception("One");
}
echo "Zero"
} catch (Exception $e) {
// код который может обработать исключение
echo $e->getMessage();
}
В каких случаях стоит применять исключения:
- если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
- если используемый вами фреймворк или библиотека декларируют их использование
Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл — помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:
$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';
// директории может не быть
if (!is_dir($directory)) {
throw new Exception('Directory `logs` is not exists');
}
// может не быть прав на запись в директорию
if (!is_writable($directory)) {
throw new Exception('Directory `logs` is not writable');
}
// возможно кто-то уже создал файл, и закрыл к нему доступ
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
throw new Exception('System can't create log file');
}
fputs($file, date("[H:i:s]") . " donen");
fclose($file);
Соответственно ловить данные исключения будем примерно так:
try {
// код который пишет в файл
// ...
} catch (Exception $e) {
// выводим текст ошибки
echo "Не получилось: ". $e->getMessage();
}
В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую, различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:
// исключения файловой системы
class FileSystemException extends Exception {}
// исключения связанные с директориями
class DirectoryException extends FileSystemException {
// коды исключений
const DIRECTORY_NOT_EXISTS = 1;
const DIRECTORY_NOT_WRITABLE = 2;
}
// исключения связанные с файлами
class FileException extends FileSystemException {}
Теперь, если использовать эти исключения то можно получить следующий код:
try {
// код который пишет в файл
if (!is_dir($directory)) {
throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS);
}
if (!is_writable($directory)) {
throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE);
}
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
throw new FileException('System can't open log file');
}
fputs($file, date("[H:i:s]") . " donen");
fclose($file);
} catch (DirectoryException $e) {
echo "С директорией возникла проблема: ". $e->getMessage();
} catch (FileException $e) {
echo "С файлом возникла проблема: ". $e->getMessage();
} catch (FileSystemException $e) {
echo "Ошибка файловой системы: ". $e->getMessage();
} catch (Exception $e) {
echo "Ошибка сервера: ". $e->getMessage();
}
Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.
Так, а что будет если не поймать исключение? Вы получите «Fatal Error: Uncaught exception …». Неприятно.
Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:
// в качестве обработчика событий
// будем использовать анонимную функцию
set_exception_handler(function($exception) {
/** @var Exception $exception */
echo $exception->getMessage(), "<br/>n";
echo $exception->getFile(), ':', $exception->getLine(), "<br/>n";
echo $exception->getTraceAsString(), "<br/>n";
});
Ещё расскажу про конструкцию с использованием блока finally — этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:
try {
// код который может выбросить исключение
} catch (Exception $e) {
// код который может обработать исключение
// если конечно оно появится
} finally {
// код, который будет выполнен при любом раскладе
}
Для понимания того, что это нам даёт приведу следующий пример использования блока finally:
try {
// где-то глубоко внутри кода
// соединение с базой данных
$handler = mysqli_connect('localhost', 'root', '', 'test');
try {
// при работе с БД возникла исключительная ситуация
// ...
throw new Exception('DB error');
} catch (Exception $e) {
// исключение поймали, обработали на своём уровне
// и должны его пробросить вверх, для дальнейшей обработки
throw new Exception('Catch exception', 0, $e);
} finally {
// но, соединение с БД необходимо закрыть
// будем делать это в блоке finally
mysqli_close($handler);
}
// этот код не будет выполнен, если произойдёт исключение в коде выше
echo "Ok";
} catch (Exception $e) {
// ловим исключение, и выводим текст
echo $e->getMessage();
echo "<br/>";
// выводим информацию о первоначальном исключении
echo $e->getPrevious()->getMessage();
}
Т.е. запомните — блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).
Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код 
Задание
Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира — посмотрите как это круто выглядит у whoops.
PHP7 — всё не так, как было раньше
Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы будете сталкиваться работая над современным PHP проектом. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот — в PHP7 это решили исправить, но? как обычно? завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:
- при возникновении фатальных ошибок типа
E_ERRORили фатальных ошибок с возможностью обработкиE_RECOVERABLE_ERRORPHP выбрасывает исключение - эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
- эти исключения наследуют класс Error
- оба класса Exception и Error реализуют интерфейс Throwable
- вы не можете реализовать интерфейс Throwable в своём коде
Интерфейс Throwable практически полностью повторяет нам Exception:
interface Throwable
{
public function getMessage(): string;
public function getCode(): int;
public function getFile(): string;
public function getLine(): int;
public function getTrace(): array;
public function getTraceAsString(): string;
public function getPrevious(): Throwable;
public function __toString(): string;
}
Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:
try {
// файл, который вызывает ошибку парсера
include 'e_parse_include.php';
} catch (Error $e) {
var_dump($e);
}
В результате ошибку поймаем и выведем:
object(ParseError)#1 (7) {
["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)"
["string":"Error":private] => string(0) ""
["code":protected] => int(0)
["file":protected] => string(49) "/www/education/error/e_parse_include.php"
["line":protected] => int(4)
["trace":"Error":private] => array(0) { }
["previous":"Error":private] => NULL
}
Как видите — поймали исключение ParseError, которое является наследником исключения Error, который реализует интерфейс Throwable, в доме который построил Джек. Ещё есть множество других исключений, но не буду мучать — для наглядности приведу иерархию исключений:
interface Throwable
|- Exception implements Throwable
| |- ErrorException extends Exception
| |- ... extends Exception
| `- ... extends Exception
`- Error implements Throwable
|- TypeError extends Error
|- ParseError extends Error
|- ArithmeticError extends Error
| `- DivisionByZeroError extends ArithmeticError
`- AssertionError extends Error
И чуть-чуть деталей:
TypeError — для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:
try {
(function(int $one, int $two) {
return;
})('one', 'two');
} catch (TypeError $e) {
echo $e->getMessage();
}
ArithmeticError — могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:
try {
1 << -1;
} catch (ArithmeticError $e) {
echo $e->getMessage();
}
DivisionByZeroError — ошибка деления на ноль:
try {
1 / 0;
} catch (ArithmeticError $e) {
echo $e->getMessage();
}
AssertionError — редкий зверь, появляется когда условие заданное в assert() не выполняется:
ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);
try {
assert(1 === 0);
} catch (AssertionError $e) {
echo $e->getMessage();
}
При настройках production-серверов, директивы
zend.assertionsиassert.exceptionотключают, и это правильно
Полный список предопределённых исключений вы найдёте в официальном мануале, там же иерархия SPL исключений.
Задание
Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.
При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7.
Единообразие
— Там ошибки, тут исключения, а можно это всё как-то до кучи сгрести?
Да запросто, у нас же есть set_error_handler() и никто нам не запретит внутри оного обработчика бросить исключение:
// Бросаем исключение вместо ошибок
function errorHandler($severity, $message, $file = null, $line = null)
{
// Кроме случаев, когда мы подавляем ошибки с помощью @
if (error_reporting() === 0) {
return false;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
// Будем обрабатывать все-все ошибки
set_error_handler('errorHandler', E_ALL);
Но данный подход с PHP7 избыточен, со всем теперь справляется Throwable:
try {
/** ... **/
} catch (Throwable $e) {
// отображение любых ошибок и исключений
echo $e->getMessage();
}
Отладка
Иногда, для отладки кода, нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция 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. Собственно, этот кусочек можно рассматривать как мимикрию под контрактную методологию программирования, и дальше я расскажу вам как я никогда его не использовал 
Функция
assert()поменяла своё поведение при переходе от версии 5.6 к 7.0, и ещё сильней всё поменялось в версии 7.2, так что внимательней читайте changelog’и PHP![]()
Первый случай — это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:
// включаем asserts в php.ini
// zend.assertions=1
assert(false, "Remove it!");
В результате выполнения данного кода получим E_WARNING:
Warning: assert(): Remove it! failed
PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError:
// переключаем в режим «исключений»
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 $message;
}
// устанавливаем 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']);
Если вас заинтересовали контракты, то специально для вас у меня есть ссылочка на фреймворк PhpDeal.
Никогда не используйте
assert()для проверки входных параметров, ведь фактическиassert()интерпретирует первый параметр (ведёт себя какeval()), а это чревато PHP-инъекцией. И да, это правильное поведение, ведь если отключить assert’ы, то все передаваемые аргументы будут проигнорированы, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения. А, и это поменяли в PHP 7.2![]()
Если у вас есть живой опыт использования assert() — поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме — PHP Assertions, с таким же вопросом в конце 
В заключение
Я за вас напишу выводы из данной статьи:
- Ошибкам бой — их не должно быть в вашем коде
- Используйте исключения — работу с ними нужно правильно организовать и будет счастье
- Assert — узнали о них, и хорошо
P.S.
Это репост из серии статей «PHP для начинающих»:
- Сессия
- Подключение файлов
- Обработка ошибок
Если у вас есть замечания по материалу статьи, или возможно по форме, то описывайте в комментариях суть, и мы сделаем данный материал ещё лучше.
Спасибо Максиму Слесаренко за помощь в написании статьи.
Хотя PHP уже давно поддерживает обработку исключений, однако, по сравнению с Java эта поддержка была довольно слабой
Первоначальная поддержка обработки исключений была введена в язык с 5 версии PHP, с двумя простыми встроенными классами исключений — Exception и ErrorException, с поддержкой дополнительных классов через SPL. Идея этого поста состоит в том, чтобы представить читателям современные возможности обработки исключений PHP.
Новый интерфейс
Хотя PHP 7 предоставляет классы Error и Exception, давайте сначала затронем интерфейс Throwable . И Error и Exception классы реализуют Throwable интерфейс — это основа для любого объекта , который может быть брошен с помощью оператора throw. Единственное, что он не может быть реализован непосредственно в классах пользовательского пространства, только через расширение класса Exception. Кроме того, он обеспечивает единую точку для отлова обоих типов ошибок в одном выражении:
<?php
try {
// ваш код
} catch (Throwable $e) {
echo 'Очень хороший способ отловить исключения и ошибки';
}
Список доступных встроенных классов исключений начиная с PHP 7.4:
- Exception
- ErrorException
- Error
- ArgumentCountError
- ArithmeticError
- AssertionError
- DivisionByZeroError
- CompileError
- ParseError
- TypeError
Дополнительные классы исключений можно найти в стандартной библиотеке PHP . И наиболее заметным из расширений JSON является класс JsonException.
THROWABLE
Интерфейс Throwable PHP 7:
interface Throwable
{
public function getMessage(): string; // Error reason
public function getCode(): int; // Error code
public function getFile(): string; // Error begin file
public function getLine(): int; // Error begin line
public function getTrace(): array; // Return stack trace as array like debug_backtrace()
public function getTraceAsString(): string; // Return stack trace as string
public function getPrevious(): Throwable; // Return previous `Trowable`
public function __toString(): string; // Convert into string
}
Вот иерархия Throwable:
interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException
Ошибка, почему?
В предыдущих версиях PHP ошибки обрабатывались совершенно иначе, чем исключения. Если возникала ошибка, то пока она не была фатальной, она могла быть обработана пользовательской функцией.
Проблема заключалась в том, что было несколько фатальных ошибок, которые не могли быть обработаны определяемым пользователем обработчиком ошибок. Это означало, что вы не могли корректно обрабатывать фатальные ошибки в PHP. Было несколько побочных эффектов, которые были проблематичными, такие как потеря контекста времени выполнения, деструкторы не вызывались, да и вообще иметь дело с ними было неудобно. В PHP 7 фатальные ошибки теперь являются исключениями, и мы можем легко их обработать. Фатальные ошибки приводят к возникновению исключений. Вам необходимо обрабатывать нефатальные ошибки с помощью функции обработки ошибок.
Вот пример ловли фатальной ошибки в PHP 7.1. Обратите внимание, что нефатальная ошибка не обнаружена.
<?php
try {
// будет генерировать уведомление, которое не будет поймано
echo $someNotSetVariable;
// фатальная ошибка, которая сейчас на самом деле ловится
someNoneExistentFunction();
} catch (Error $e) {
echo "Error caught: " . $e->getMessage();
}
Этот скрипт выведет сообщение об ошибке при попытке доступа к недопустимой переменной. Попытка вызвать функцию, которая не существует, приведет к фатальной ошибке в более ранних версиях PHP, но в PHP 7.1 вы можете ее перехватить. Вот вывод для скрипта:
Notice: Undefined variable: someNotSetVariable on line 3
Error caught: Call to undefined function someNoneExistentFunction()
Константы ошибок
В PHP много констант, которые используются в отношении ошибок. Эти константы используются при настройке PHP для скрытия или отображения ошибок определенных классов.
Вот некоторые из наиболее часто встречающихся кодов ошибок:
- E_DEPRECATED — интерпретатор сгенерирует этот тип предупреждений, если вы используете устаревшую языковую функцию. Сценарий обязательно продолжит работать без ошибок.
- E_STRICT — аналогично E_DEPRECATED, — указывает на то, что вы используете языковую функцию, которая не является стандартной в настоящее время и может не работать в будущем. Сценарий будет продолжать работать без каких-либо ошибок.
- E_PARSE — ваш синтаксис не может быть проанализирован, поэтому ваш скрипт не запустится. Выполнение скрипта даже не запустится.
- E_NOTICE — движок просто выведет информационное сообщение. Выполнение скрипта не прервется, и ни одна из ошибок не будет выдана.
- E_ERROR — скрипт не может продолжить работу, и завершится. Выдает ошибки, а как они будут обрабатываться, зависит от обработчика ошибок.
- E_RECOVERABLE_ERROR — указывает на то, что, возможно, произошла опасная ошибка, и движок работает в нестабильном состоянии. Дальнейшее выполнение зависит от обработчика ошибок, и ошибка обязательно будет выдана.
Полный список констант можно найти в руководстве по PHP.
Функция обработчика ошибок
Функция set_error_handler() используется, чтобы сообщить PHP как обрабатывать стандартные ошибки, которые не являются экземплярами класса исключений Error. Вы не можете использовать функцию обработчика ошибок для фатальных ошибок. Исключения ошибок должны обрабатываться с помощью операторов try/catch. set_error_handler() принимает callback функцию в качестве своего параметра. Callback-функции в PHP могут быть заданы двумя способами: либо строкой, обозначающей имя функции, либо передачей массива, который содержит объект и имя метода (именно в этом порядке). Вы можете указать защищенные и приватные методы для callable в объекте. Вы также можете передать значение null, чтобы указать PHP вернуться к использованию стандартного механизма обработки ошибок. Если ваш обработчик ошибок не завершает программу и возвращает результат, ваш сценарий будет продолжать выполняться со строки, следующей за той, где произошла ошибка.
PHP передает параметры в вашу функцию обработчика ошибок. Вы можете опционально объявить их в сигнатуре функции, если хотите использовать их в своей функции.
Вот пример:
<?php
function myCustomErrorHandler(int $errNo, string $errMsg, string $file, int $line) {
echo "Ух ты, мой обработчик ошибок получил #[$errNo] в [$file] на [$line]: [$errMsg]";
}
set_error_handler('myCustomErrorHandler');
try {
why;
} catch (Throwable $e) {
echo 'И моя ошибка: ' . $e->getMessage();
}
Если вы запустите этот код в PHP-консоли php -a, вы должны получить похожий вывод:
Error #[2] occurred in [php shell code] at line [3]: [Use of undefined constant why - assumed 'why' (this will throw an Error in a future version of PHP)]
Самые известные PHP библиотеки , которые делают обширное использование РНР set_error_handler() и могут сделать хорошие представления исключений и ошибок являются whoops, и Symony Debug, ErrorHandler компоненты. Я смело рекомендую использовать один из них. Если не собираетесь их использовать в своем проекте, то вы всегда можете черпать вдохновение из их кода. В то время как компонент Debug широко используется в экосистеме Symfony, Whoops остается библиотекой выбора для фреймворка Laravel .
Для подробного и расширенного использования, пожалуйста, обратитесь к руководству по PHP для обработчика ошибок.
Отображение или подавление нефатальной ошибки
Когда ваше приложение выходит в продакшн, логично, что вы хотите скрыть все системные сообщения об ошибках во время работы, и ваш код должен работать без генерации предупреждений или сообщений. Если вы собираетесь показать сообщение об ошибке, убедитесь, что оно сгенерировано и не содержит информации, которая может помочь злоумышленнику проникнуть в вашу систему.
В вашей среде разработки вы хотите, чтобы все ошибки отображались, чтобы вы могли исправить все проблемы, с которыми они связаны, но в процессе работы вы хотите подавить любые системные сообщения, отправляемые пользователю.
Для этого вам нужно настроить PHP, используя следующие параметры в вашем файле php.ini:
- display_errors – может быть установлен в false для подавления сообщений
- log_errors – может использоваться для хранения сообщений об ошибках в файлах журнала
- error_reporting – можно настроить, какие ошибки вызывают отчет
Лучше всего корректно обрабатывать ошибки в вашем приложении. В производственном процессе вы должны скорее регистрировать необработанные ошибки, чем разрешать их отображение пользователю. Функция error_log() может использоваться для отправки сообщения одной из определенных процедур обработки ошибок. Вы также можете использовать функцию error_log() для отправки электронных писем, но лично вы бы предпочли использовать хорошее решение для регистрации ошибок и получения уведомлений при возникновении ошибок, например Sentry или Rollbar .
Существует вещь, называемая оператором контроля ошибок ( @ ), который по своей сути может игнорировать и подавлять ошибки. Использование очень простое — просто добавьте любое выражение PHP с символом «собаки», и сгенерированная ошибка будет проигнорирована. Хотя использование этого оператора может показаться интересным, я призываю вас не делать этого. Мне нравится называть это живым пережитком прошлого.
Больше информации для всех функций, связанных с ошибками PHP, можно найти в руководстве.
Исключения (Exceptions)
Исключения являются основной частью объектно-ориентированного программирования и впервые были представлены в PHP 5.0. Исключением является состояние программы, которое требует специальной обработки, поскольку оно не выполняется ожидаемым образом. Вы можете использовать исключение, чтобы изменить поток вашей программы, например, чтобы прекратить что-либо делать, если некоторые предварительные условия не выполняются.
Исключение будет возникать в стеке вызовов, если вы его не перехватите. Давайте посмотрим на простой пример:
try {
print "это наш блок попыток n";
throw new Exception();
} catch (Exception $e) {
print "что-то пошло не так, есть улов!";
} finally {
print "эта часть всегда выполняется";
}
PHP включает в себя несколько стандартных типов исключений, а стандартная библиотека PHP (SPL) включает в себя еще несколько. Хотя вам не нужно использовать эти исключения, это означает, что вы можете использовать более детальное обнаружение ошибок и отчеты. Классы Exception и Error реализуют интерфейс Throwable и, как и любые другие классы, могут быть расширены. Это позволяет вам создавать гибкие иерархии ошибок и адаптировать обработку исключений. Только класс, который реализует класс Throwable, может использоваться с ключевым словом throw. Другими словами, вы не можете объявить свой собственный базовый класс и затем выбросить его как исключение.
Надежный код может встретить ошибку и справиться с ней. Разумная обработка исключений повышает безопасность вашего приложения и облегчает ведение журнала и отладку. Управление ошибками в вашем приложении также позволит вам предложить своим пользователям лучший опыт. В этом разделе мы рассмотрим, как отлавливать и обрабатывать ошибки, возникающие в вашем коде.
Ловля исключений
Вы должны использовать try/catch структуру:
<?php
class MyCustomException extends Exception { }
function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так.');
}
try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
}
Как видите, есть два предложения catch. Исключения будут сопоставляться с предложениями сверху вниз, пока тип исключения не будет соответствовать предложению catch. Эта очень простая функция throwMyCustomException() генерирует исключение MyCustomException, и мы ожидаем, что оно будет перехвачено в первом блоке. Любые другие исключения, которые произойдут, будут перехвачены вторым блоком. Здесь мы вызываем метод getMessage() из базового класса Exception. Вы можете найти больше информации о дополнительном методе в Exception PHP docs.
Кроме того, можно указать несколько исключений, разделяя их трубой ( | ).
Давайте посмотрим на другой пример:
<?php
class MyCustomException extends Exception { }
class MyAnotherCustomException extends Exception { }
try {
throw new MyAnotherCustomException;
} catch (MyCustomException | MyAnotherCustomException $e) {
echo "Caught : " . get_class($e);
}
Этот очень простой блок catch будет перехватывать исключения типа MyCustomException и MyAnotherCustomException.
Немного более продвинутый сценарий:
// exceptions.php
use SymfonyComponentHttpKernelExceptionNotFoundHttpException;
try {
throw new NotFoundHttpException();
} catch (Exception $e) {
echo 1;
} catch (NotFoundHttpException $e) {
echo 2;
} catch (Exception $e) {
echo 3;
} finally {
echo 4;
}
Это ваш окончательный ответ?
В PHP 5.5 и более поздних, блок finally также может быть указан после или вместо блоков catch. Код внутри блока finally всегда будет выполняться после блоков try и catch независимо от того, было ли выброшено исключение, и до возобновления нормального выполнения. Одним из распространенных применений блока finally является закрытие соединения с базой данных, но, наконец, его можно использовать везде, где вы хотите, чтобы код всегда выполнялся.
<?php
class MyCustomException extends Exception { }
function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так');
}
try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
} finally {
echo "Я всегда тут";
}
Вот хороший пример того, как работают операторы PHP catch/finally:
<?php
try {
try {
echo 'a-';
throw new exception();
echo 'b-';
} catch (Exception $e) {
echo 'пойманный-';
throw $e;
} finally {
echo 'завершенный-';
}
} catch (Exception $e) {
echo 'конец-';
}
Функция обработчика исключений
Любое исключение, которое не было обнаружено, приводит к фатальной ошибке. Если вы хотите изящно реагировать на исключения, которые не перехватываются в блоках перехвата, вам нужно установить функцию в качестве обработчика исключений по умолчанию.
Для этого вы используете функцию set_exception_handler() , которая принимает вызываемый элемент в качестве параметра. Ваш сценарий завершится после того, как вызов будет выполнен.
Функция restore_exception_handler() вернет обработчик исключений к его предыдущему значению.
<?php
class MyCustomException extends Exception { }
function exception_handler($exception) {
echo "Uncaught exception: " , $exception->getMessage(), "n";
}
set_exception_handler('exception_handler');
try {
throw new Exception('Uncaught Exception');
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} finally {
echo "Я всегда тут";
}
print "Не выполнено";
Здесь простая функция exception_handler будет выполняться после блока finally, когда ни один из типов исключений не был сопоставлен. Последний вывод никогда не будет выполнен.
Для получения дополнительной информации обратитесь к документации PHP.
Старый добрый «T_PAAMAYIM_NEKUDOTAYIM»
Вероятно, это было самое известное сообщение об ошибке PHP. В последние годы было много споров по этому поводу. Вы можете прочитать больше в отличном сообщении в блоге от Фила Осетра .
Сегодня я с гордостью могу сказать, что если вы запустите этот код с PHP 7, то сообщение о T_PAAMAYIM_NEKUDOTAYIM больше не будет:
<?php
class foo
{
static $bar = 'baz';
}
var_dump('foo'::$bar);
// Output PHP < 7.0:
// PHP Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM) in php shell code on line 1
// Output PHP > 7.0:
// string(3) "baz"
?>
В заключении
Со времени первого внедрения обработки исключений в PHP прошло много лет, пока мы не получили гораздо более надежную и зрелую обработку исключений, чем в Java. Обработка ошибок в PHP 7 получила много внимания, что делает его хорошим, открывая пространство для будущих улучшений, если мы действительно с сегодняшней точки зрения нуждаемся в них.
Nice solution found in Zend Framework 2:
/**
* ErrorHandler that can be used to catch internal PHP errors
* and convert to an ErrorException instance.
*/
abstract class ErrorHandler
{
/**
* Active stack
*
* @var array
*/
protected static $stack = array();
/**
* Check if this error handler is active
*
* @return bool
*/
public static function started()
{
return (bool) static::getNestedLevel();
}
/**
* Get the current nested level
*
* @return int
*/
public static function getNestedLevel()
{
return count(static::$stack);
}
/**
* Starting the error handler
*
* @param int $errorLevel
*/
public static function start($errorLevel = E_WARNING)
{
if (!static::$stack) {
set_error_handler(array(get_called_class(), 'addError'), $errorLevel);
}
static::$stack[] = null;
}
/**
* Stopping the error handler
*
* @param bool $throw Throw the ErrorException if any
* @return null|ErrorException
* @throws ErrorException If an error has been catched and $throw is true
*/
public static function stop($throw = false)
{
$errorException = null;
if (static::$stack) {
$errorException = array_pop(static::$stack);
if (!static::$stack) {
restore_error_handler();
}
if ($errorException && $throw) {
throw $errorException;
}
}
return $errorException;
}
/**
* Stop all active handler
*
* @return void
*/
public static function clean()
{
if (static::$stack) {
restore_error_handler();
}
static::$stack = array();
}
/**
* Add an error to the stack
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return void
*/
public static function addError($errno, $errstr = '', $errfile = '', $errline = 0)
{
$stack = & static::$stack[count(static::$stack) - 1];
$stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack);
}
}
This class allows you to start the specific ErrorHandler sometimes if you need it. And then you can also stop the Handler.
Use this class e.g. like this:
ErrorHandler::start(E_WARNING);
$return = call_function_raises_E_WARNING();
if ($innerException = ErrorHandler::stop()) {
throw new Exception('Special Exception Text', 0, $innerException);
}
// or
ErrorHandler::stop(true); // directly throws an Exception;
Link to the full class code:
https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php
A maybe better solution is that one from Monolog:
Link to the full class code:
https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php
It can also handle FATAL_ERRORS using the register_shutdown_function function. According to this class a FATAL_ERROR is one of the following array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR).
class ErrorHandler
{
// [...]
public function registerExceptionHandler($level = null, $callPrevious = true)
{
$prev = set_exception_handler(array($this, 'handleException'));
$this->uncaughtExceptionLevel = $level;
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
}
}
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1)
{
$prev = set_error_handler(array($this, 'handleError'), $errorTypes);
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
if ($callPrevious) {
$this->previousErrorHandler = $prev ?: true;
}
}
public function registerFatalHandler($level = null, $reservedMemorySize = 20)
{
register_shutdown_function(array($this, 'handleFatalError'));
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
$this->fatalLevel = $level;
}
// [...]
}
Всем известная истина звучит, что не ошибается только тот, кто ничего не делает. В этой статье мы рассмотрим, какие ошибки бывают, а также кратко рассмотрим функцию error reporting, используемую для контроля отображения errors. Но для начала (in first) изучим каждый вид дефектов отдельно, обратив внимание на наиболее распространенные.
Error в PHP: разновидности
Ошибки в PHP бывают:
— фатальные (fatal);
— не фатальные;
— пользовательские.
Фатальные
Fatal error в PHP — одна из наиболее серьезных проблем. Такие дефекты появляются и при компиляции, и во время работы парсера либо PHP-скрипта. Основной нюанс заключается в том, что происходит прерывание исполнения скрипта.
Ниже рассмотрим основные разновидности фатальных ошибок:
- E_PARSE. Грубый недостаток в синтаксисе. PHP-интерпретатор не понимает, что вы вообще от него хотите. Пример — разработчик забыл закрыть (поставил лишнюю) фигурную либо круглую скобку либо написал код на непонятном интерпретатору языке. Здесь важно понимать следующее: код файла с parse error выполнен не будет, поэтому, если вы захотите включить отображение ошибок в этом же файле, где появилась parse error, такое не сработает.
- E_ERROR. Интерпретатор PHP понимает, что хочет разработчик, но выполнить это не может по разным причинам. Выполнение скрипта будет прервано, однако произойдет именно в месте возникновения проблемы, то есть код сработает до того места, где находится ошибка. Примеры:
— не удалось обнаружить подключаемый файл PHP;
— было выброшено, но не было обработано исключение;
— разработчик пытался вызвать метод класса, причем данный метод не существует;
— отсутствует свободная память (превышен лимит директивы memory_limit).
Нередко проблема возникает во время чтения либо загрузки больших файлов, поэтому надо быть особенно внимательным в вопросах потребляемой памяти.
Не фатальные
В данном случае выполнение скрипта не прерывается, однако именно эти дефекты часто находят тестировщики программного обеспечения. И именно эти недоработки доставляют наибольшие хлопоты начинающим программистам на PHP.
Разновидности:
- E_WARNING. Нередко встречаются, если разработчик подключает файл с использованием include, а данного файла или нет на сервере, или была допущена ошибка при указании пути. Другая причина E_WARNING — использование неправильного типа аргументов при вызове функций. Но вообще причин много — все не перечислишь;
- E_NOTICE. Распространены наиболее широко. Вдобавок к этому, существуют любители, которые отключают вывод ошибок, в результате чего клепают E_NOTICE просто пачками. Эти errors сами по себе тривиальны:
— обращение к неопределенной переменной;
— обращение к элементу массива, когда элемент не существует;
— обращение к несуществующей константе;
— проблема, возникающая, если не конвертируются типы данных и т. п.
Чтобы избежать таких недоработок, надо быть внимательным, особенно к тому, что подсказывает IDE — игнорировать подсказки точно не стоит;
- E_DEPRECATED. Язык программирования PHP станет ругаться при использовании устаревших функций (т. е. функций, которые помечены в качестве deprecated);
- E_STRICT. Это тоже история про то, что нужно писать код правильно и обращать внимание на подсказки со стороны IDE, дабы потом не было мучительно больно и стыдно. К примеру, если вы вызовете нестатический метод как статику, код, отображенный ниже, функционировать будет, но ведь это как-то неправильно. Почему? Потому что в дальнейшем возможно появление существенных ошибок, если метод класса изменится, и появится обращение к $this:
class Strict {
public function test() {
echo «It’s test for me. It is not fatal error»;
}
}
Strict::test();
Но вообще тип E_STRICT больше актуален для PHP 5.6, поэтому он практически выпилен из 7-й версии языка.
Пользовательские
Этот «балаган» разводится самим разработчиком. Злоупотреблять такими errors не рекомендуется:
- E_USER_WARNING — некритическая ошибка;
- E_USER_ERROR — критическая;
- E_USER_NOTICE — речь идет о сообщениях, которые ошибками не являются.
Отдельно надо сказать про E_USER_DEPRECATED — напоминает о том, что метод либо функция устарели, то есть пришло время переписать код. Чтобы создать эту и подобные ошибки, применяется функция trigger_error:
Раз основные разновидности проблем уже были рассмотрены, пришло время дать пояснение относительно работы директивы display_errors:
- когда если display_errors = on, в случае ошибки веб-браузер получит html c кодом 200 и текстом ошибки;
- когда display_errors = off, для фатальных ошибок код реквеста будет 500, причем результат не вернется пользователю. Для остальных ошибок программный код будет работать неверно, однако он «никому про это не расскажет».
Error reporting
Для того чтобы ошибки в PHP не остались незамеченными, их нужно отслеживать с помощью отчетов (reports). Такой report можно получить посредством функции error_reporting(), а включить отображение ошибок можно, используя директиву display_errors:
<?php
error_reporting(E_ALL);
ini_set(‘display_errors’, 1);
Функция error reporting является встроенной. Она позволяет контролировать, какие именно errors станут отображаться и сообщаться (reported) разработчику. Не стоит забывать и о том, что в PHP ini существует директива error_reporting, причем во время выполнения функция error_reporting() задает значение этой директивы.
Полезные ссылки на тематические материалы:
- https://www.php.net/manual/ru/function.error-reporting.php;
- https://www.netangels.pro/article/php-errors/;
- https://habr.com/ru/post/440744/;
- https://www.karashchuk.com/PHP/error_reporting-display_errors-display_startup_errors/.








