(PHP 4, PHP 5, PHP 7, PHP
exit — Вывести сообщение и прекратить выполнение текущего скрипта
Описание
exit(string $status
= ?): void
exit(int $status
): void
exit
— это конструкция языка, и она может быть вызвана без круглых скобок, если не передаётся параметр status
.
Список параметров
-
status
-
Если
status
задан в виде строки, то эта
конструкция выведет содержимоеstatus
перед выходом.Если
status
задан в виде целого числа (int),
то это значение будет использовано как статус выхода и не будет выведено.
Статусы выхода должны быть в диапазоне от 0 до 254, статус выхода 255 зарезервирован
PHP и не должен использоваться. Статус выхода 0 используется для успешного
завершения программы.
Возвращаемые значения
Функция не возвращает значения после выполнения.
Примеры
Пример #1 Пример использования exit
<?php
$filename
= '/path/to/data-file';
$file = fopen($filename, 'r')
or exit("Невозможно открыть файл ($filename)");?>
Пример #2 Пример использования exit
со статусом выхода
<?php//обычный выход из программы
exit;
exit();
exit(0);//выход с кодом ошибки
exit(1);
exit(0376); //восьмеричный?>
Пример #3 Функции выключения и деструкторы выполняются независимо
<?php
class Foo
{
public function __destruct()
{
echo 'Деинициализировать: ' . __METHOD__ . '()' . PHP_EOL;
}
}
function
shutdown()
{
echo 'Завершить: ' . __FUNCTION__ . '()' . PHP_EOL;
}$foo = new Foo();
register_shutdown_function('shutdown');
exit();
echo
'Эта строка не будет выведена.';
?>
Результат выполнения данного примера:
Завершить: shutdown() Деинициализировать: Foo::__destruct()
dexen dot devries at gmail dot com ¶
12 years ago
If you want to avoid calling exit() in FastCGI as per the comments below, but really, positively want to exit cleanly from nested function call or include, consider doing it the Python way:
define an exception named `SystemExit', throw it instead of calling exit() and catch it in index.php with an empty handler to finish script execution cleanly.
<?php// file: index.php
class SystemExit extends Exception {}
try {
/* code code */
}
catch (SystemExit $e) { /* do nothing */ }
// end of file: index.php
// some deeply nested function or .php file
if (SOME_EXIT_CONDITION)
throw new SystemExit(); // instead of exit()?>
albert at removethis dot peschar dot net ¶
14 years ago
jbezorg at gmail proposed the following:
<?phpif($_SERVER['SCRIPT_FILENAME'] == __FILE__ )
header('Location: /');?>
After sending the `Location:' header PHP _will_ continue parsing, and all code below the header() call will still be executed. So instead use:
<?phpif($_SERVER['SCRIPT_FILENAME'] == __FILE__)
{
header('Location: /');
exit;
}?>
theonenkl at gmail dot com ¶
8 years ago
A side-note for the use of exit with finally: if you exit somewhere in a try block, the finally won't be executed. Could not sound obvious: for instance in Java you never issue an exit, at least a return in your controller; in PHP instead you could find yourself exiting from a controller method (e.g. in case you issue a redirect).
Here follows the POC:
<?php
echo "testing finally wit exitn";
try {
echo
"In try, exitingn";
exit;
} catch(
Exception $e) {
echo "catchedn";
} finally {
echo "in finallyn";
}
echo
"In the endn";
?>
This will print:
testing finally wit exit
In try, exiting
vincent dot laag at gmail dot com ¶
12 years ago
Don't use the exit() function in the auto prepend file with fastcgi (linux/bsd os).
It has the effect of leaving opened files with for result at least a nice "Too many open files ..." error.
void a t informance d o t info ¶
14 years ago
To rich dot lovely at klikzltd dot co dot uk:
Using a "@" before header() to suppress its error, and relying on the "headers already sent" error seems to me a very bad idea while building any serious website.
This is *not* a clean way to prevent a file from being called directly. At least this is not a secure method, as you rely on the presence of an exception sent by the parser at runtime.
I recommend using a more common way as defining a constant or assigning a variable with any value, and checking for its presence in the included script, like:
in index.php:
<?php
define ('INDEX', true);
?>
in your included file:
<?php
if (!defined('INDEX')) {
die('You cannot call this script directly !');
}
?>
BR.
Ninj
emils at tvnet dot lv ¶
19 years ago
Note, that using exit() will explicitly cause Roxen webserver to die, if PHP is used as Roxen SAPI module. There is no known workaround for that, except not to use exit(). CGI versions of PHP are not affected.
alexyam at live dot com ¶
11 years ago
When using php-fpm, fastcgi_finish_request() should be used instead of register_shutdown_function() and exit()
For example, under nginx and php-fpm 5.3+, this will make browsers wait 10 seconds to show output:
<?php
echo "You have to wait 10 seconds to see this.<br>";
register_shutdown_function('shutdown');
exit;
function shutdown(){
sleep(10);
echo "Because exit() doesn't terminate php-fpm calls immediately.<br>";
}
?>
This doesn't:
<?php
echo "You can see this from the browser immediately.<br>";
fastcgi_finish_request();
sleep(10);
echo "You can't see this form the browser.";
?>
m dot libergolis at gmail dot com ¶
7 years ago
In addition to "void a t informance d o t info", here's a one-liner that requires no constant:
<?php basename($_SERVER['PHP_SELF']) == basename(__FILE__) && die('Thou shall not pass!'); ?>
Placing it at the beginning of a PHP file will prevent direct access to the script.
To redirect to / instead of dying:
<?php
if (basename($_SERVER['PHP_SELF']) == basename(__FILE__)) {
if (ob_get_contents()) ob_clean(); // ob_get_contents() even works without active output buffering
header('Location: /');
die;
}
?>
Doing the same in a one-liner:
<?php basename($_SERVER['PHP_SELF']) == basename(__FILE__) && (!ob_get_contents() || ob_clean()) && header('Location: /') && die; ?>
A note to security: Even though $_SERVER['PHP_SELF'] comes from the user, it's safe to assume its validity, as the "manipulation" takes place _before_ the actual file execution, meaning that the string _must_ have been valid enough to execute the file. Also, basename() is binary safe, so you can safely rely on this function.
devinemke at devinemke dot com ¶
21 years ago
If you are using templates with numerous includes then exit() will end you script and your template will not complete (no </table>, </body>, </html> etc...). Rather than having complex nested conditional logic within your content, just create a "footer.php" file that closes all of your HTML and if you want to exit out of a script just include() the footer before you exit().
for example:
include ('header.php');
blah blah blah
if (!$mysql_connect) {
echo "unable to connect";
include ('footer.php');
exit;
}
blah blah blah
include ('footer.php');
nicoladinh at gmail dot com ¶
12 years ago
Calling to exit() will flush all buffers started by ob_start() to default output.
tianyiw at vip dot qq dot com ¶
6 months ago
These are the standard error codes in Linux or UNIX.
1 - Catchall for general errors
2 - Misuse of shell builtins (according to Bash documentation)
126 - Command invoked cannot execute
127 - “command not found”
128 - Invalid argument to exit
128+n - Fatal error signal “n”
130 - Script terminated by Control-C
255* - Exit status out of range
powtac at gmx de ¶
4 years ago
When a object is passed as $status and it consists of a __toString() magic method the string value of this method will be used as $status. If the object does not contain a __toString method, exit will throw a catchable fatal error.
sunfundev at gmail dot com ¶
5 years ago
>> Shutdown functions and object destructors will always be executed even if exit is called.
It is false if you call exit into desctructor.
Normal exit:
<?php
class A
{
public function __destruct()
{
echo "bye An";
}
}
class
B
{
public function __destruct()
{
echo "bye Bn";
}
}$a = new A;
$b = new B;
exit;// Output:
// bye B
// bye A
?>
// Exit into desctructor:
<?php
class A
{
public function __destruct()
{
echo "bye An";
}
}
class
B
{
public function __destruct()
{
echo "bye Bn";
exit;
}
}$a = new A;
$b = new B;// Output:
// bye B
?>
chris at ocproducts dot com ¶
5 years ago
Calling 'exit' will bypass the auto_append_file option.
On some free hosting this risks you getting removed, as they may be using for ads and analytics.
So be a bit careful if using this on the most common output branch.
shaun at NOshatSPAM dot net ¶
20 years ago
return may be preferable to exit in certain situations, especially when dealing with the PHP binary and the shell.
I have a script which is the recipient of a mail alias, i.e. mail sent to that alias is piped to the script instead of being delivered to a mailbox. Using exit in this script resulted in the sender of the email getting a delivery failure notice. This was not the desired behavior, I wanted to silently discard messages which did not satisfy the script's requirements.
After several hours of trying to figure out what integer value I should pass to exit() to satisfy sendmail, I tried using return instead of exit. Worked like a charm. Sendmail didn't like exit but it was perfectly happy with return. So, if you're running into trouble with exit and other system binaries, try using return instead.
matt at serverboy dot net ¶
13 years ago
It should be noted that if building a site that runs on FastCGI, calling exit will generate an error in the server's log file. This can quickly fill up.
Also, using exit will diminish the performance benefit gained on FastCGI setups. Instead, consider using code like this:
<?phpif( /* error case */ )
echo "Invalid request";
else {
/* The rest of your application */
}
?>
I've also seen developers get around this issue with FastCGI by wrapping their code in a switch statement and using breaks:
index.php:
<?phpswitch(true) {
case true:
require('application.php');
}?>
application.php:
<?phpif($x > $y) {
echo "Sorry, that didn't work.";
break;
}// ...?>
It does carry some overhead, but compared to the alternative, it does the job well.
marco dot marsala at live dot it ¶
5 years ago
Please note $status is printed to stdout, not stderr.
kehaovista at qq dot com ¶
7 years ago
<?phpclass Foo
{
public function __construct()
{
register_shutdown_function([$this, 'shutdown']);
}
public function
__destruct()
{
echo 'Destruct: ' . __METHOD__ . '()' . PHP_EOL;
}
function
shutdown()
{
echo 'Shutdown: ' . __FUNCTION__ . '()' . PHP_EOL;
}
}$foo = new Foo();
exit();// output is
//Shutdown: shutdown()
//Destruct: Foo::__destruct()
Alexander Behling ¶
2 years ago
it is also possible to include function calls e.g. logging function to write the error in a log file.
e.g.
function logerror($error){
file_put_contents(__DIR__.'/error_log', 'Error occured: '.$error');
}
die(logerror('error msg here');
This will terminate the script and write "error msg here" to error.log
You could use this for example when query the database to log when the query fails.
Антон Шевчук // Web-разработчик
Не совершает ошибок только тот, кто ничего не делает, и мы тому пример – трудимся не покладая рук над созданием рабочих мест для тестировщиков
О да, в этой статье я поведу свой рассказа об ошибках в PHP, и том как их обуздать.
Ошибки
Разновидности в семействе ошибок
Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.
Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:
<?php error_reporting(E_ALL); ini_set('display_errors', 1);
Фатальные ошибки
Самый грозный вид ошибок – фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.
E_PARSE
Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:
<?php /** Parse error: syntax error, unexpected end of file */ {
Или написали на непонятном языке:
<?php /** Parse error: syntax error, unexpected '...' (T_STRING) */ Тут будет ошибка парсера
Лишние скобочки тоже встречаются, и не важно круглые либо фигурные:
<?php /** Parse error: syntax error, unexpected '}' */ }
Отмечу один важный момент – код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:
<?php // этот код не сработает error_reporting(E_ALL); ini_set('display_errors', 1); // т.к. вот тут ошибка парсера
E_ERROR
Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин, так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:
Не был найден подключаемый файл:
/** Fatal error: require_once(): Failed opening required 'not-exists.php' (include_path='.:/usr/share/php:/usr/share/pear') */ require_once 'not-exists.php';
Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:
/** Fatal error: Uncaught exception 'Exception' */ throw new Exception();
При попытке вызвать несуществующий метод класса:
/** Fatal error: Call to undefined method stdClass::notExists() */ $stdClass = new stdClass(); $stdClass->notExists();
Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:
/** Fatal Error: Allowed Memory Size */ $arr = array(); while (true) { $arr[] = str_pad(' ', 1024); }
Очень часто происходит при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти
Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug:
/** Fatal error: Maximum function nesting level of '256' reached, aborting! */ function deep() { deep(); } deep();
Не фатальные
Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик, и именно они доставляют больше всего хлопот у начинающих разработчиков.
E_WARNING
Частенько встречается, когда подключаешь файл с использованием include
, а его не оказывается на сервере или ошиблись указывая путь к файлу:
/** Warning: include_once(): Failed opening 'not-exists.php' for inclusion */ include_once 'not-exists.php';
Бывает, если используешь неправильный тип аргументов при вызове функций:
/** Warning: join(): Invalid arguments passed */ join('string', 'string');
Их очень много, и перечислять все не имеет смысла…
E_NOTICE
Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.
Когда обращаются к неопределенной переменной:
/** Notice: Undefined variable: a */ echo $a;
Когда обращаются к несуществующему элементу массива:
<?php /** Notice: Undefined index: a */ $b = array(); $b['a'];
Когда обращаются к несуществующей константе:
/** Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT' */ echo UNKNOWN_CONSTANT;
Когда не конвертируют типы данных:
/** Notice: Array to string conversion */ echo array();
Для избежания подобных ошибок – будьте внимательней, и если вам IDE подсказывает о чём-то – не игнорируйте её:
E_STRICT
Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывают. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this
:
/** Strict standards: Non-static method Strict::test() should not be called statically */ class Strict { public function test() { echo 'Test'; } } Strict::test();
E_DEPRECATED
Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):
/** Deprecated: Function split() is deprecated */ // популярная функция, всё никак не удалят из PHP // deprecated since 5.3 split(',', 'a,b');
В моём редакторе подобные функции будут зачёркнуты:
Обрабатываемые
Этот вид, которые разводит сам разработчик кода, я их уже давно не встречал, не рекомендую их вам заводить:
E_USER_ERROR
– критическая ошибкаE_USER_WARNING
– не критическая ошибкаE_USER_NOTICE
– сообщения которые не являются ошибками
Отдельно стоит отметить E_USER_DEPRECATED
– этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():
/** * @deprecated Deprecated since version 1.2, to be removed in 2.0 */ function generateToken() { trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED); // ... // code ... // ... }
Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы
display_errors
:
- если
display_errors = on
, то в случае ошибки браузер получит html c текстом ошибки и кодом 200- если же
display_errors = off
, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок – код будет работать неправильно, но никому об этом не расскажет
Приручение
Для работы с ошибками в PHP существует 3 функции:
- set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
- error_get_last() — получает информацию о последней ошибке
- register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого
Теперь немного подробностей об обработке ошибок с использованием set_error_handler()
, в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:
$errno
– первый аргумент содержит тип ошибки в виде целого числа$errstr
– второй аргумент содержит сообщение об ошибке$errfile
– необязательный третий аргумент содержит имя файла, в котором произошла ошибка$errline
– необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка$errcontext
– необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка
В случае если обработчик вернул true
, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:
<?php // включаем отображение всех ошибок, кроме E_NOTICE error_reporting(E_ALL & ~E_NOTICE); ini_set('display_errors', 1); // наш обработчик ошибок function myHandler($level, $message, $file, $line, $context) { // в зависимости от типа ошибки формируем заголовок сообщения switch ($level) { case E_WARNING: $type = 'Warning'; break; case E_NOTICE: $type = 'Notice'; break; default; // это не E_WARNING и не E_NOTICE // значит мы прекращаем обработку ошибки // далее обработка ложится на сам PHP return false; } // выводим текст ошибки echo "<h2>$type: $message</h2>"; echo "<p><strong>File</strong>: $file:$line</p>"; echo "<p><strong>Context</strong>: $". join(', $', array_keys($context))."</p>"; // сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется return true; } // регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок set_error_handler('myHandler', E_ALL);
У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет – пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём
С обработчиком, который написан выше есть одна существенная проблема – он не ловит фатальные ошибки, и вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:
function shutdown() { echo 'Этот текст будет всегда отображаться'; } register_shutdown_function('shutdown');
Данная функция будет срабатывать всегда!
Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль “последних”:
function shutdown() { $error = error_get_last(); if ( // если в коде была допущена ошибка is_array($error) && // и это одна из фатальных ошибок in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR]) ) { // очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях) while (ob_get_level()) { ob_end_clean(); } // выводим описание проблемы echo 'Сервер находится на техническом обслуживании, зайдите позже'; } } register_shutdown_function('shutdown');
Задание
Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.
О прожорливости
Проведём простой тест, и выясним – сколько драгоценных ресурсов кушает самая тривиальная ошибка:
/** * Этот код не вызывает ошибок */ // сохраняем параметры памяти и времени выполнения скрипта $memory = memory_get_usage(); $time= microtime(true); $a = ''; $arr = []; for ($i = 0; $i < 10000; $i++) { $arr[$a] = $i; } printf('%f seconds <br/>', microtime(true) - $time); echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
В результате запуска данного скрипта у меня получился вот такой результат:
0.002867 seconds 984 bytes
Теперь добавим ошибку в цикле:
/** * Этот код содержит ошибку */ // сохраняем параметры памяти и времени выполнения скрипта $memory = memory_get_usage(); $time= microtime(true); $a = ''; $arr = []; for ($i = 0; $i < 10000; $i++) { $arr[$b] = $i; // тут ошиблись с именем переменной } printf('%f seconds <br/>', microtime(true) - $time); echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
Результат ожидаемо хуже, и на порядок (даже на два порядка!):
0.263645 seconds 992 bytes
Вывод однозначен – ошибки в коде приводят к лишней прожорливости скриптов – так что во время разработки и тестирования приложения включайте отображение всех ошибок!
Тестирование проводил на PHP версии 5.6, в седьмой версии результат лучше – 0.0004 секунды против 0.0050 – разница только на один порядок, но в любом случае результат стоит прикладываемых усилий по исправлению ошибок
Где собака зарыта
В PHP есть спец символ «@» – оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:
<?php echo @UNKNOWN_CONSTANT;
При этом обработчик ошибок указанный в set_error_handler()
всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting()
внутри обработчика, в этом случае она вернёт 0
.
Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло
Исключения
В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Думаю да, ведь это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений
Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.
К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение – сохранить в другое место или сообщить пользователю о проблеме.
Исключение – это объект который наследуется от класса Exception
, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, “бросить”) при помощи оператора throw
, и можно перехватить (“поймать”) оператором catch
. Код генерирующий исключение, должен быть окружен блоком try
, для того чтобы можно было перехватить исключение. Каждый блок try
должен иметь как минимум один соответствующий ему блок catch
или finally
:
try { // код который может выбросить исключение if (rand(0, 1)) { throw new Exception('One') } else { echo 'Zero'; } } catch (Exception $e) { // код который может обработать исключение echo $e->getMessage(); }
В каких случаях стоит применять исключения:
- если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
- если используемый вами фреймверк или библиотека декларируют их использование
Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл – помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:
$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs'; // директории может не быть if (!is_dir($directory)) { throw new Exception('Directory `logs` is not exists'); } // может не быть прав на запись в директорию if (!is_writable($directory)) { throw new Exception('Directory `logs` is not writable'); } // возможно кто-то уже создал файл, и закрыл к нему доступ if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) { throw new Exception('System can't create log file'); } fputs($file, date('[H:i:s]') . " donen"); fclose($file);
Соответственно ловить данные исключения будем примерно так:
try { // код который пишет в файл // ... } catch (Exception $e) { // выводим текст ошибки echo 'Не получилось: '. $e->getMessage(); }
В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую – различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:
// исключения файловой системы class FileSystemException extends Exception {} // исключения связанные с директориями class DirectoryException extends FileSystemException { // коды исключений const DIRECTORY_NOT_EXISTS = 1; const DIRECTORY_NOT_WRITABLE = 2; } // исключения связанные с файлами class FileException extends FileSystemException {}
Теперь, если использовать эти исключения то можно получить следующий код:
try { // код который пишет в файл if (!is_dir($directory)) { throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS); } if (!is_writable($directory)) { throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE); } if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) { throw new FileException('System can't open log file'); } fputs($file, date('[H:i:s]'') . " donen"); fclose($file); } catch (DirectoryException $e) { echo 'С директорией возникла проблема: '. $e->getMessage(); } catch (FileException $e) { echo 'С файлом возникла проблема: '. $e->getMessage(); } catch (FileSystemException $e) { echo 'Ошибка файловой системы: '. $e->getMessage(); } catch (Exception $e) { echo 'Ошибка сервера: '. $e->getMessage(); }
Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.
Так, а что будет если не поймать исключение? Вы получите “Fatal Error: Uncaught exception …”. Неприятно.
Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:
// в качестве обработчика событий // будем использовать анонимную функцию set_exception_handler(function($exception) { /** @var Exception $exception */ echo $exception->getMessage(), "<br/>n"; echo $exception->getFile(), ':', $exception->getLine(), "<br/>n"; echo $exception->getTraceAsString(), "<br/>n"; });
Ещё расскажу про конструкцию с использованием блока finally
– этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:
try { // код который может выбросить исключение } catch (Exception $e) { // код который может обработать исключение // если конечно оно появится } finally { // код, который будет выполнен при любом раскладе }
Для понимания того, что это нам даёт приведу следующий пример использования блока finally
:
try { // где-то глубоко внутри кода // соединение с базой данных $handler = mysqli_connect('localhost', 'root', '', 'test'); try { // при работе с БД возникла исключительная ситуация // ... throw new Exception('DB error'); } catch (Exception $e) { // исключение поймали, обработали на своём уровне // и должны его пробросить вверх, для дальнейшей обработки throw new Exception('Catch exception', 0, $e); } finally { // но, соединение с БД необходимо закрыть // будем делать это в блоке finally mysqli_close($handler); } // этот код не будет выполнен, если произойдёт исключение в коде выше echo "Ok"; } catch (Exception $e) { // ловим исключение, и выводим текст echo $e->getMessage(); echo "<br/>"; // выводим информацию о первоначальном исключении echo $e->getPrevious()->getMessage(); }
Т.е. запомните – блок finally
будет выполнен даже в том случае, если вы в блоке catch
пробрасываете исключение выше (собственно именно так он и задумывался).
Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код
Задание
Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира – посмотрите как это круто выглядит у whoops.
PHP7 – всё не так, как было раньше
Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы столкнётесь через год работы PHP разработчиком. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот – в PHP7 это решили исправить, но как обычно завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:
- при возникновении фатальных ошибок типа
E_ERROR
или фатальных ошибок с возможностью обработкиE_RECOVERABLE_ERROR
PHP выбрасывает исключение - эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
- эти исключения наследуют класс Error
- оба класса Exception и Error реализуют интерфейс Throwable
- вы не можете реализовать интерфейс Throwable в своём коде
Интерфейс Throwable
практически полностью повторяет нам Exception
:
interface Throwable { public function getMessage(): string; public function getCode(): int; public function getFile(): string; public function getLine(): int; public function getTrace(): array; public function getTraceAsString(): string; public function getPrevious(): Throwable; public function __toString(): string; }
Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:
try { // файл, который вызывает ошибку парсера include 'e_parse_include.php'; } catch (Error $e) { var_dump($e); }
В результате ошибку поймаем и выведем:
object(ParseError)#1 (7) { ["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)" ["string":"Error":private] => string(0) "" ["code":protected] => int(0) ["file":protected] => string(49) "/www/education/error/e_parse_include.php" ["line":protected] => int(4) ["trace":"Error":private] => array(0) { } ["previous":"Error":private] => NULL }
Как видите – поймали исключение ParseError, которое является наследником исключения Error
, который реализует интерфейс Throwable
, в доме который построил Джек. Ещё есть другие, но не буду мучать – для наглядности приведу иерархию исключений:
interface Throwable |- Exception implements Throwable | |- ErrorException extends Exception | |- ... extends Exception | `- ... extends Exception `- Error implements Throwable |- TypeError extends Error |- ParseError extends Error |- ArithmeticError extends Error | `- DivisionByZeroError extends ArithmeticError `- AssertionError extends Error
TypeError – для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:
try { (function(int $one, int $two) { return; })('one', 'two'); } catch (TypeError $e) { echo $e->getMessage(); }
ArithmeticError – могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:
try { 1 << -1; } catch (ArithmeticError $e) { echo $e->getMessage(); }
DivisionByZeroError – ошибка деления на ноль:
try { 1 / 0; } catch (ArithmeticError $e) { echo $e->getMessage(); }
AssertionError – редкий зверь, появляется когда условие заданное в assert() не выполняется:
ini_set('zend.assertions', 1); ini_set('assert.exception', 1); try { assert(1 === 0); } catch (AssertionError $e) { echo $e->getMessage(); }
При настройках production-серверов, директивы
zend.assertions
иassert.exception
отключают, и это правильно
Задание
Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.
При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7
Отладка
Иногда для отладки кода нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:
<?php function example() { echo '<pre>'; debug_print_backtrace(); echo '</pre>'; } class ExampleClass { public static function method () { example(); } } ExampleClass::method();
В результате выполнения функции debug_print_backtrace()
будет выведен список вызовов приведших нас к данной точке:
#0 example() called at [/www/education/error/backtrace.php:10] #1 ExampleClass::method() called at [/www/education/error/backtrace.php:14]
Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу]
, но я не встречал использования оных.
Assert
Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP, собственно это кусочек контрактной методологии программирования, и дальше я расскажу вам как я никогда его не использовал
Первый случай – это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:
// включаем вывод ошибок error_reporting(E_ALL); ini_set('display_errors', 1); // включаем asserts ini_set('zend.assertions', 1); ini_set('assert.active', 1); assert(false, "Remove it!");
В результате выполнения данного кода получим E_WARNING
:
Warning: assert(): Remove it! failed
PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError
:
// включаем asserts ini_set('zend.assertions', 1); ini_set('assert.active', 1); // переключаем на исключения ini_set('assert.exception', 1); assert(false, "Remove it!");
В результате ожидаемо получаем не пойманный AssertionError
. При необходимости, можно выбрасывать произвольное исключение:
assert(false, new Exception("Remove it!"));
Но я бы рекомендовал использовать метки
@TODO
, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними
Второй вариант использования – это создание некоего подобия TDD, но помните – это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:
// callback-функция для вывода информации в браузер function backlog($script, $line, $code, $message) { echo "<h3>$message</h3>"; highlight_string ($code); } // устанавливаем callback-функцию assert_options(ASSERT_CALLBACK, 'backlog'); // отключаем вывод предупреждений assert_options(ASSERT_WARNING, false); // пишем проверку и её описание assert("sqr(4) == 16", "When I send integer, function should return square of it"); // функция, которую проверяем function sqr($a) { return; // она не работает }
Третий теоретический вариант – это непосредственно контрактное программирование – когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):
/** * Настройки соединения должны передаваться в следующем виде * * [ * 'host' => 'localhost', * 'port' => 3306, * 'name' => 'dbname', * 'user' => 'root', * 'pass' => '' * ] * * @param $settings */ function setupDb ($settings) { // проверяем настройки assert(isset($settings['host']), 'Db `host` is required'); assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer'); assert(isset($settings['name']), 'Db `name` is required, should be integer'); // соединяем с БД // ... } setupDb(['host' => 'localhost']);
Никогда не используйте
assert()
для проверки входных параметров, ведь фактическиassert()
интерпретирует строковую переменную (ведёт себя какeval()
), а это чревато PHP-инъекцией. И да, это правильное поведение, т.к. просто отключив assert’ы всё что передаётся внутрь будет проигнорировано, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения
Если у вас есть живой опыт использования assert()
– поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме – PHP Assertions, с таким же вопросом в конце
В заключение
Я за вас напишу выводы из данной статьи:
- Ошибкам бой – их не должно быть в вашем коде
- Используйте исключения – работу с ними нужно правильно организовать и будет счастье
- Assert – узнали о них, и хорошо
P.S. Спасибо Максиму Слесаренко за помощь в написании статьи
I have a PHP script that stops processing (it seems to be) on a PHP warning. The error is general «PHP Warning: fopen» — «failed to open stream: No such file or directory».
Should PHP stop here? I thought only on fatal errors?
Is there a way to get it to continue?
asked Nov 26, 2009 at 9:47
3
A warning is emitted but the script execution continues. The fact that your script stops is more likely related to processing you try to do afterward but not to the warning itself.
The previous suggestion to use file_exists and is_readable is a good one.
answered Nov 26, 2009 at 9:57
ArnaudArnaud
1015 bronze badges
1
Yes, it should if error_reporting() level is low enough.
Yes, there is. Add «@» before fopen
which causes the waring, like this: @fopen(...)
Toby Allen
11k11 gold badges73 silver badges124 bronze badges
answered Nov 26, 2009 at 9:51
Ivan KrechetovIvan Krechetov
18.7k8 gold badges48 silver badges60 bronze badges
In addition to what Conrad Meyer has mentioned from the PHP manual:
$fres = @fopen('file.ext','w+');
if($fres){
// so anything you want with the file
}
fopen
returns false on error. When there’s an error suppressed on fopen and you do not use the if($fres)
, the subsequent file operation functions will throw error saying that $fres
is not a valid file handle.
answered Nov 26, 2009 at 9:56
maurismauris
42.8k15 gold badges97 silver badges131 bronze badges
Even if it continued, the program would, most probably, not work the way it was meant to. Anyway, try handling the exception:
try {
# code that may cause an error
}
catch( Exception $e ) {
# do error handling mechanism
}
answered Nov 26, 2009 at 9:52
Alan Haggai AlaviAlan Haggai Alavi
72.4k19 gold badges101 silver badges127 bronze badges
1
За последние 24 часа нас посетили 11258 программистов и 595 роботов. Сейчас ищет 651 программист …
-
machetero
Активный пользователь- С нами с:
- 25 окт 2014
- Сообщения:
- 499
- Симпатии:
- 21
При подключении файла в php7 с синтаксической ошибкой, сделанной намерено, выполнение скрипта прекращается. Почему ? Блока try catch нету. Должно вылетать исключение и выполнение должно продолжаться.
Вызываемый файл:-
include __DIR__.‘/new.php’;
Подключаемый файл:
-
public function throwex(){
-
echo ‘now ex will be thrown’;
-
echo ‘throwing was done’;
Строка «after error» не выводиться.
Неужели теперь при любой ошибке в php7 выполнение скрипта будет прекращаться ? -
Команда форума
Модератор- С нами с:
- 25 июл 2013
- Сообщения:
- 12.162
- Симпатии:
- 1.770
- Адрес:
- :сердА
1) Деление на ноль — это не синтаксическая ошибка. Это ошибка деления на ноль.
2) Я не вижу у тебя нигде кода вызова throwex();
3) Я не вижу у тебя в классе конструктора, а он, если не ошибаюсь, обязан присутствовать в php7. Может, ошибка из-за этого?
4) Вообще, отображение ошибок не забыл включить? -
machetero
Активный пользователь- С нами с:
- 25 окт 2014
- Сообщения:
- 499
- Симпатии:
- 21
Там знак бакса пропущен в присвоении. Деление на ноль даёт ворнинг.
Отображение включено. Выводится parse error. Хотя в мане написано что не перехваченые исключения-ошибки должны превращаться в fatal error.
Единственное что работает, это перехват try catch’ем этой ошибки.
-
Команда форума
Модератор- С нами с:
- 25 июл 2013
- Сообщения:
- 12.162
- Симпатии:
- 1.770
- Адрес:
- :сердА
Пардоньте, не заметил. Последние месяца два пишу исключительно на JS, и глаз привык к переменным без доллира.Ну дык Parse error — это тебе для понимания, а по коду ошибки она может проходить как Fatal.
Далее. Читаем:
В PHP 7 при возникновении фатальных ошибок (E_ERROR) и фатальных ошибок с возможностью обработки (E_RECOVERABLE_ERROR) будет выброшен exception, а не произойдет завершение скрипта.
У тебя это и происходит. Это нормальное поведение исключений — если их не перехватывать, приложение аварийно завершается. Просто раньше оно падало в любом случае, а теперь можно обернуть это дело в try/catch, ловить ошибку там, и не бояться, что у тебя все обрушится.
— Добавлено —
Вот тебе из доков: -
machetero
Активный пользователь- С нами с:
- 25 окт 2014
- Сообщения:
- 499
- Симпатии:
- 21
Раньше было лучше тем, что когда случалась не фатальная ошибка, код после неё продолжал выполняться.
-
Команда форума
Модератор- С нами с:
- 25 июл 2013
- Сообщения:
- 12.162
- Симпатии:
- 1.770
- Адрес:
- :сердА
Протестую. Когда код работает после фатальной ошибки, это нифига не лучше. Вся система в разнос пойти может.
-
machetero
Активный пользователь- С нами с:
- 25 окт 2014
- Сообщения:
- 499
- Симпатии:
- 21
Раньше было лучше тем, что когда случалась не фатальная ошибка, код после неё продолжал выполняться.
-
Команда форума
Модератор- С нами с:
- 20 июн 2012
- Сообщения:
- 8.522
- Симпатии:
- 1.744
Исключение бросается, ты его не ловишь. А непойманное исключение — фатальная ошибка. Вот попробуй так:
-
include __DIR__ . «/new.php»;
-
echo $e->getMessage() . «<br>»;
Я уже попробовал, всё как задумано
-
syntax error, unexpected ‘=’
— Добавлено —
А такая ошибка синтаксиса всегда вызывала остановку выполнения скрипта раньше. -
machetero
Активный пользователь- С нами с:
- 25 окт 2014
- Сообщения:
- 499
- Симпатии:
- 21
Просто обычно в try catch не заворачивают какие то мелкие куски кода(а в них может произойти ошибка), а заворачивают целиком весь код, так что после catch уже никаких вызовов нет.
-
Команда форума
Модератор- С нами с:
- 20 июн 2012
- Сообщения:
- 8.522
- Симпатии:
- 1.744
А смысл заворачивать весь код? Я как раз-таки относительно небольшие куски кода в try { } catch() {} оборачиваю, особенно там, где ошибка не предполагается, но если произойдёт — её возможно обработать. А ловлей синтаксических ошибок через catch как не занимался, так и не буду заниматься. Их не должно быть в production, а при разработке я хочу их сразу же видеть, так что пусть себе прерывается
-
machetero
Активный пользователь- С нами с:
- 25 окт 2014
- Сообщения:
- 499
- Симпатии:
- 21
-
Команда форума
Модератор- С нами с:
- 25 июл 2013
- Сообщения:
- 12.162
- Симпатии:
- 1.770
- Адрес:
- :сердА
Ты ошибки парсинга ловить собрался чтоль трай-кэчем? По-моему ты решаешь проблему, которую сам себе придумал.
-
Команда форума
Модератор- С нами с:
- 20 июн 2012
- Сообщения:
- 8.522
- Симпатии:
- 1.744
Ну вероятно может быть ситуация, когда это может пригодиться, иначе не ввели бы в язык. Например, пригодилось бы в WordPress. Сейчас, если допустить в файле functions.php активной темы синтаксическую ошибку, валится весь сайт вместе с админкой. А если бы подключение functions.php темы было реализовано через новый механизм php 7, можно было бы в админке красивое сообщение показать «Файл functions.php вашей темы содержит синтаксическую ошибку»
-
Команда форума
Модератор- С нами с:
- 25 июл 2013
- Сообщения:
- 12.162
- Симпатии:
- 1.770
- Адрес:
- :сердА
Я тебе отвечу твоими же словами:
-
Команда форума
Модератор- С нами с:
- 20 июн 2012
- Сообщения:
- 8.522
- Симпатии:
- 1.744
Так проблема в том, что темки зачастую пишет пользователь wordpress, а не авторы движка.
-
Команда форума
Модератор- С нами с:
- 25 июл 2013
- Сообщения:
- 12.162
- Симпатии:
- 1.770
- Адрес:
- :сердА
Ну дык пишешь ты тему для вордпресса — увидел, что все обвалилось к чертовой матери, поправил, заработало.
Или ты имеешь ввиду случай, когда какой-то мудак выложил тему или модуль с ошибкой, и они вместе, весело и с брызгами положили сайт незадачливому блоггеру?
-
Команда форума
Модератор- С нами с:
- 20 июн 2012
- Сообщения:
- 8.522
- Симпатии:
- 1.744
Ну да. И куча пользователей wordPress, которые правят тему методом копипасты с кривых сайтов. Вот недавно я участвовал в написании плагина для wordPress, который массово используется. И имею счастье наблюдать, какие вопросы приходят от пользователей.
Russian (Pусский) translation by Anna Goorikova (you can also view the original English article)
В этом посте вы узнаете, как использовать обработку исключений в PHP. Начиная с PHP 5, мы можем использовать блоки try catch для обработки ошибок — это лучший способ обработки исключений и управления потоком вашего приложения. В этой статье мы рассмотрим основы обработки исключений вместе с несколькими примерами из реального мира.
Что такое исключение?
В PHP 5 появилась новая модель ошибок, которая позволяет вам кидать и ловить исключения в вашем приложении — это лучший способ обработки ошибок, чем то, что мы имели в более старых версиях PHP. Все исключения являются экземплярами базового класса Exception
, который мы можем расширить, чтобы ввести наши собственные пользовательские исключения.
Здесь важно отметить, что обработка исключений отличается от обработки ошибок. При обработке ошибок мы можем использовать функцию set_error_handler
для установки нашей настраиваемой функции обработки ошибок, чтобы всякий раз, когда срабатывает ошибка, она вызывала нашу функцию обработки ошибок. Таким образом, вы можете управлять ошибками. Однако, как правило, некоторые виды ошибок не восстанавливаются и прекращают выполнение программы.
С другой стороны, исключения — это что-то, что умышленно вызывает код, и ожидается, что он будет пойман в какой-то момент вашего приложения. Таким образом, мы можем сказать, что исключения восстанавливаются, а не определенные ошибки, которые не подлежат восстановлению. Если исключение, которое выбрасывается, попадает где-то в ваше приложение, выполнение программы продолжается с момента, когда исключение было поймано. А исключение, которое не попадает нигде в ваше приложение, приводит к ошибке, которое останавливает выполнение программы.
Поток управления обработкой исключений
Давайте рассмотрим следующую диаграмму, которая показывает общий поток управления обработкой исключений.
Исключения могут быть выброшены и пойманы с помощью блоков try
и catch
. Вы несете ответственность за выброс исключений, если что-то произойдет, чего не ожидается. Давайте быстро рассмотрим основной поток обработки исключений, как показано в следующем псевдокоде.
1 |
// code before the try-catch block
|
2 |
|
3 |
try { |
4 |
// code
|
5 |
|
6 |
// if something is not as expected
|
7 |
// throw exception using the "throw" keyword
|
8 |
|
9 |
// code, it won't be executed if the above exception is thrown
|
10 |
} catch (Exception $e) { |
11 |
// exception is raised and it'll be handled here
|
12 |
// $e->getMessage() contains the error message
|
13 |
}
|
14 |
|
15 |
// code after the try-catch block, will always be executed
|
В большинстве случаев, когда вы имеете дело с исключениями, вы в конечном итоге используете шаблон, как показано в приведенном выше фрагменте. Вы также можете использовать блок finally
вместе с блоками try
и catch
, но мы вернемся к этому позже в этой статье.
Блок try
— тот, который используется, когда вы подозреваете, что ваш код может генерировать исключение. Вы всегда должны обертывать такой код, используя try
и catch
.
Выброс исключения
Исключение может быть вызвано функцией, которую вы вызываете, или вы можете использовать ключевое слово throw
для выбрасывания исключения вручную. Например, вы можете проверить некоторый ввод перед выполнением любой операции и выбросить исключение, если данные недействительны.
Здесь важно отметить, что если вы выбросите исключение, но вы не определили блок catch
, который должен обрабатывать это исключение, это приведет к фатальной ошибке. Поэтому вам нужно убедиться, что вы всегда определяете блок catch
, если вы бросаете исключения в своем приложении.
Когда исключение попадает в блок catch
, объект Exception
содержит сообщение об ошибке, которое было выбрано с использованием ключевого слова throw
. Переменная $e
в приведенном выше примере является экземпляром класса Exception
, поэтому она имеет доступ ко всем методам этого класса. В этом блоке вы должны определить свою собственную логику обработки исключений — что именно вы хотите сделать с ошибкой, которую вы поймаете.
В следующем разделе мы рассмотрим пример из реального мира, чтобы понять, как работает обработка исключений.
Пример из реального мира
В этом разделе мы построим реальный пример для демонстрации обработки исключений в PHP.
Предположим, что вы создали приложение, которое загружает конфигурацию приложения из файла config.php. Теперь важно, чтобы файл config.php присутствовал, когда ваше приложение загружается. Таким образом, ваше приложение не может работать, если файл config.php отсутствует. Так что это идеальный случай, чтобы выбросить исключение и сообщить пользователю, что им необходимо исправить проблему.
1 |
<?php
|
2 |
try { |
3 |
// init bootstrapping phase
|
4 |
|
5 |
$config_file_path = "config.php"; |
6 |
|
7 |
if (!file_exists($config_file_path)) |
8 |
{
|
9 |
throw new Exception("Configuration file not found."); |
10 |
}
|
11 |
|
12 |
// continue execution of the bootstrapping phase
|
13 |
} catch (Exception $e) { |
14 |
echo $e->getMessage(); |
15 |
die(); |
16 |
}
|
17 |
?>
|
Как вы можете видеть в приведенном выше примере, мы проверяем наличие файла config.php в начале фазы начальной загрузки. Если файл config.php найден, выполнение продолжается в обычном режиме. С другой стороны, мы выбросим исключение, если файл config.php не существует. Кроме того, мы хотели бы прекратить выполнение, если есть исключение!
Вот как вы можете использовать исключения в своих приложениях. Вы должны выбрасывать исключения для исключительных случаев использования — вы не должны излишне бросать исключения для ошибок, таких как недопустимые учетные данные пользователя, неправильные разрешения на доступ к каталогам и т. д., которые вы часто ожидаете. Они лучше обрабатываются общими сообщениями об ошибках в потоке выполнения обычного приложения.
Таким образом, это был пример обработки исключений с использованием класса Exception
по умолчанию. В следующем разделе мы рассмотрим, как вы можете расширить основной класс Exception
и создать свои собственные пользовательские исключения в своем приложении.
Как создавать пользовательские исключения
В этом разделе мы обсудим, как вы можете создавать пользовательские исключения в своих приложениях. Фактически, мы расширим пример, который мы только что обсуждали в предыдущем разделе, чтобы продемонстрировать пользовательские исключения.
В предыдущем примере мы выбрали исключение конфигурации, используя класс Exception
по умолчанию. Это прекрасно, если вы просто хотите иметь дело с сообщением об ошибке. Однако иногда вы хотите сделать немного больше в зависимости от типа исключения, которое бросается. Вот почему пользовательские исключения полезны.
Перейдем к предыдущему примеру, как показано в следующем фрагменте.
1 |
<?php
|
2 |
class ConfigFileNotFoundException extends Exception {} |
3 |
|
4 |
try { |
5 |
// init bootstrapping phase
|
6 |
|
7 |
$config_file_path = "config.php"; |
8 |
|
9 |
if (!file_exists($config_file_path)) |
10 |
{
|
11 |
throw new ConfigFileNotFoundException("Configuration file not found."); |
12 |
}
|
13 |
|
14 |
// continue execution of the bootstrapping phase
|
15 |
} catch (ConfigFileNotFoundException $e) { |
16 |
echo "ConfigFileNotFoundException: ".$e->getMessage(); |
17 |
// other additional actions that you want to carry out for this exception
|
18 |
die(); |
19 |
} catch (Exception $e) { |
20 |
echo $e->getMessage(); |
21 |
die(); |
22 |
}
|
23 |
?>
|
Во-первых, мы определили класс ConfigFileNotFoundException
, который расширяет основной класс Exception
. Теперь он становится нашим настраиваемым классом исключений, и мы можем использовать его, когда хотим выбросить исключение ConfigFileNotFoundException
в нашем приложении.
Затем мы использовали ключевое слово throw
для исключения исключений ConfigFileNotFoundException
в случае, если файл config.php не существует. Однако важное различие находится в блоке catch
. Как вы можете видеть, мы определили два блока catch
, и каждый блок используется для обнаружения различного типа исключения.
Первый получает исключения типа ConfigFileNotFoundException
. Итак, если генерируемое исключение относится к типу ConfigFileNotFoundException
, этот блок будет выполнен. Если тип исключения не соответствует какому-либо конкретному блоку catch
, он будет соответствовать последнему, который должен поймать все генерические сообщения об исключениях.
Блок Finally
В этом разделе мы рассмотрим, как вы можете использовать ключевое слово finally
вместе с блоками try
и catch
. Иногда вы хотите выполнить часть кода независимо от того, было ли исключено исключение. Вот где вы можете использовать блок finally
, поскольку код, который вы размещаете в блоке finally, всегда будет выполняться после выполнения блоков try и catch независимо от того, было ли выбрано исключение.
Попробуем понять это, используя следующий пример.
1 |
try { |
2 |
// code
|
3 |
|
4 |
// if something is not as expected
|
5 |
// throw exception using the "throw" keyword
|
6 |
|
7 |
// code, it won't be executed if the above exception is thrown
|
8 |
} catch (Exception $e) { |
9 |
// exception is raised and it'll be handled here
|
10 |
// $e->getMessage() contains the error message
|
11 |
} finally { |
12 |
// code, it'll always be executed
|
13 |
}
|
Код в приведенном выше примере почти то же самое с единственным исключением, что мы добавили блок finally
после блока catch
. И, как мы обсуждали, код в этом блоке всегда будет выполняться.
Типичные прецеденты, которые мы могли бы придумать для использования блока finally, обычно связаны с очисткой ресурсов. Например, если вы открыли соединение с базой данных или файл на диске в блоке try
, вы можете выполнять задачи очистки, такие как закрытие соединения в блоке finally
, так как это гарантировано.
Обработка исключений — это ключевой навык кодирования, и вы должны подумать над тем, как будут обрабатываться исключения при разработке ваших приложений. Это поможет вам обнаружить и восстановить непредвиденные ошибки в вашем приложении. Надеюсь, что этот пост вдохновит вас написать лучший код обработки ошибок!
Заключение
Сегодня мы обсудили тему обработки исключений в PHP. В первой половине статьи мы обсудили основы исключений в PHP и создали реальный пример, чтобы продемонстрировать, как они работают. В конце мы рассмотрели, как вы можете создавать пользовательские исключения, расширяя основной класс Exception
.