Files
php-doc-ru/language/exceptions.xml
2025-04-28 11:37:39 +03:00

585 lines
22 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 402a72776804fbb74ec5c4cad72d4c7b6cdd5b2d Maintainer: sergey Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="language.exceptions" xmlns="http://docbook.org/ns/docbook">
<title>Исключения</title>
<para>
Модель исключений PHP напоминает модель исключений других языков программирования.
PHP умеет выбрасывать — &throw; — и ловить — &catch; — исключения.
Код заключают в блок &try;, чтобы упростить обработку вероятных исключений.
Каждому блоку &try; указывают как минимум один блок &catch; или &finally;.
</para>
<para>
Исключение будет «всплывать» по стеку вызовов функций, пока не найдёт
блок &catch;, если функция выбросила исключение, а в текущей области
видимости функции, которая её вызвала, нет блока &catch;. PHP выполнит
каждый блок &finally;, который встретит по пути. Программа завершается
фатальной ошибкой, когда стек вызовов не встречает блок &catch;
и разворачивается до глобальной области видимости, если разработчик не установил
глобальный обработчик исключений.
</para>
<para>
В коде допустимо выбрасывать только объект исключения, тип которого при проверке
оператором &instanceof; соответствует интерфейсу <interfacename>Throwable</interfacename>.
Попытка выбросить объект, который не выполняет это условие, приведёт к фатальной ошибке PHP.
</para>
<para>
С PHP 8.0.0 ключевое слово &throw; стало выражением, которое разрешили записывать
в контексте выражения. В предыдущих версиях это слово было инструкцией
и её записывали в отдельной строке.
</para>
<sect1 annotations="chunk:false" xml:id="language.exceptions.catch">
<title><literal>catch</literal></title>
<para>
Блок &catch; определяет, как реагировать на исключение, которое выбросил код.
Блок &catch; определяет один или больше типов исключений или ошибок, которые он обрабатывает,
и необязательную переменную, которой блок присвоит исключение.
До PHP 8.0.0 переменную требовалось указывать.
Объект исключения обработает первый блок &catch;, с которым столкнутся
исключение или ошибка того типа или подтипа, который ожидает блок.
</para>
<para>
Блоки &catch; записывают один за другим, чтобы перехватывать исключения разных классов.
Нормальное выполнение, когда блок &try; не выбросил исключение,
продолжится после последнего блока &catch;, который определили в последовательности.
Внутри блока &catch; допустимо выбрасывать, а точнее — повторно выбрасывать исключения
через ключевое слово &throw;. PHP продолжит выполнение кода после блока &catch;,
который сработал, если внутри блока не выбросили исключение.
</para>
<para>
При появлении исключения PHP не выполнит код, который идёт за инструкцией,
а попытается найти первый подходящий блок &catch;. PHP выдаст фатальную ошибку
с сообщением <literal>Uncaught Exception ...</literal>, если исключение не поймали
и через функцию <function>set_exception_handler</function> не определили обработчик исключений.
</para>
<para>
С PHP 7.1.0 в блоке &catch; допустимо указывать исключения
через символ вертикальной черты <literal>|</literal>. Это полезно, когда разные исключения
из разных иерархий классов обрабатывают одинаково.
</para>
<para>
С PHP 8.0.0 имя переменной для исключения, которое поймал блок, необязательно. PHP выполнит
блок &catch;, но у блока не будет доступа к объекту, который выбросил код, если переменную не указали.
</para>
</sect1>
<sect1 annotations="chunk:false" xml:id="language.exceptions.finally">
<title><literal>finally</literal></title>
<para>
Блок &finally; также допустимо указывать после или вместо блоков &catch;.
PHP выполнит код в блоке &finally; после блоков &try; и &catch;,
независимо от того, выбросил ли код исключение, и до возобновления нормального выполнения.
</para>
<para>
Заслуживает внимания взаимодействие между блоком &finally; и инструкцией &return;.
PHP выполнит блок &finally;, даже если встретит внутри блоков &try; или &catch;
инструкцию &return;. Больше того, когда PHP встречает инструкцию &return;,
он вычисляет её, но вернёт результат после выполнения блока &finally;.
Кроме того, PHP вернёт значение из блока &finally;, если блок &finally; тоже
содержит инструкцию &return;.
</para>
</sect1>
<sect1 annotations="chunk:false" xml:id="language.exceptions.exception-handler">
<title>Глобальный обработчик исключений</title>
<para>
Глобальный обработчик исключений, если обработчик установили, перехватит исключение,
если исключению разрешили всплывать до глобальной области видимости.
Функция <function>set_exception_handler</function> устанавливает функцию,
которую PHP вызовет вместо блока &catch;, если в коде не вызвали другие блоки.
Эффект по существу такой же, как если бы всю программу обернули в блок &try;-&catch;
с этой функцией в качестве &catch;.
</para>
</sect1>
<sect1 annotations="chunk:false" xml:id="language.exceptions.notes">
&reftitle.notes;
<note>
<para>
Внутренние функции PHP чаще сообщают об ошибках через
<link linkend="ini.error-reporting">отчёт об ошибках</link>,
только современные <link linkend="language.oop5">объектно-ориентированные</link>
модули работают с исключениями. При этом ошибки легко переводятся в исключения
через класс <link linkend="class.errorexception">ErrorException</link>.
Эта техника, однако, работает только с нефатальными ошибками.
</para>
<example>
<title>Преобразование отчётов об ошибках в исключения</title>
<programlisting role="php">
<![CDATA[
<?php
function exceptions_error_handler($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
}
set_error_handler('exceptions_error_handler');
?>
]]>
</programlisting>
</example>
</note>
<tip>
<para>
<link linkend="intro.spl">Стандартная библиотека PHP (SPL)</link>
предлагает много <link linkend="spl.exceptions">встроенных исключений</link>.
</para>
</tip>
</sect1>
<sect1 annotations="chunk:false" xml:id="language.exceptions.examples">
&reftitle.examples;
<example>
<title>Выброс исключения</title>
<programlisting role="php">
<![CDATA[
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Деление на ноль.');
}
return 1 / $x;
}
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'PHP перехватил исключение: ', $e->getMessage(), "\n";
}
// Продолжить выполнение
echo "Привет, мир\n";
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
0.2
PHP перехватил исключение: Деление на ноль.
Привет, мир
]]>
</screen>
</example>
<example>
<title>Обработка исключений в блоке &finally;</title>
<programlisting role="php">
<![CDATA[
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Деление на ноль.');
}
return 1 / $x;
}
try {
echo inverse(5) . "\n";
} catch (Exception $e) {
echo 'PHP перехватил исключение: ', $e->getMessage(), "\n";
} finally {
echo "Первый блок finally.\n";
}
try {
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'PHP перехватил исключение: ', $e->getMessage(), "\n";
} finally {
echo "Второй блок finally.\n";
}
// Продолжить выполнение
echo "Привет, мир\n";
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
0.2
Первый блок finally.
PHP перехватил исключение: Деление на ноль.
Второй блок finally.
Привет, мир
]]>
</screen>
</example>
<example>
<title>Взаимодействие между блоками &finally; и &return;</title>
<programlisting role="php">
<![CDATA[
<?php
function test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo test();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
finally
]]>
</screen>
</example>
<example>
<title>Вложенные исключения</title>
<programlisting role="php">
<![CDATA[
<?php
class MyException extends Exception { }
class Test {
public function testing() {
try {
try {
throw new MyException('foo!');
} catch (MyException $e) {
// Повторный выброс исключения
throw $e;
}
} catch (Exception $e) {
var_dump($e->getMessage());
}
}
}
$foo = new Test;
$foo->testing();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
string(4) "foo!"
]]>
</screen>
</example>
<example>
<title>Обработка нескольких исключений в одном блоке catch</title>
<programlisting role="php">
<![CDATA[
<?php
class 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();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
string(11) "MyException"
]]>
</screen>
</example>
<example>
<title>Пример блока &catch; без переменной</title>
<para>
Разрешено только в PHP 8.0.0 и более поздних версиях.
</para>
<programlisting role="php">
<![CDATA[
<?php
class SpecificException extends Exception {}
function test() {
throw new SpecificException('Ой!');
}
try {
test();
} catch (SpecificException) {
print "Функция выбросила исключение SpecificException, но детали исключения неважны.";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
A SpecificException was thrown, but we don't care about the details.
]]>
</screen>
</example>
<example>
<title>Throw как выражение</title>
<para>
Разрешено только в PHP 8.0.0 и более поздних версиях.
</para>
<programlisting role="php">
<![CDATA[
<?php
function test() {
do_something_risky() or throw new Exception('Функция завершилась ошибкой');
}
function do_something_risky() {
return false; // Имитация сбоя
}
try {
test();
} catch (Exception $e) {
print $e->getMessage();
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Функция завершилась ошибкой
]]>
</screen>
</example>
</sect1>
<sect1 xml:id="language.exceptions.extending">
<title>Наследование исключений</title>
<para>
Пользовательский класс исключения определяют путём расширения
встроенного класса Exception. Ниже показаны методы и свойства класса Exception,
которые доступны дочерним классам.
</para>
<example>
<title>Встроенный класс Exception</title>
<programlisting role="php">
<![CDATA[
<?php
class Exception implements Throwable
{
protected $message = 'Unknown exception'; // Сообщение исключения
private $string; // Кеш метода __toString
protected $code = 0; // Пользовательский код исключения
protected $file; // Файл, в котором код выбросил исключение
protected $line; // Строка, в которой код выбросил исключение
private $trace; // Трассировка вызовов методов и функций
private $previous; // Предыдущее исключение, если исключение вложенное
public function __construct($message = '', $code = 0, ?Throwable $previous = null);
final private function __clone(); // Запрещает клонирования исключения
final public function getMessage(); // Сообщение исключения
final public function getCode(); // Код исключения
final public function getFile(); // Файл, в котором код выбросил исключение
final public function getLine(); // Строка, на которой код выбросил исключение
final public function getTrace(); // Массив функции backtrace()
final public function getPrevious(); // Предыдущее исключение
final public function getTraceAsString(); // Отформатированная строка трассировки
// Переопределяемый
public function __toString(); // Отформатированная строка для отображения
}
?>
]]>
</programlisting>
</example>
<para>
В конструкторе класса-наследника нужно также вызвать конструктор родительского класса —
<link linkend="language.oop5.paamayim-nekudotayim">parent::__construct()</link>,
когда класс расширяет класс Exception и переопределяет <link
linkend="language.oop5.decon">конструктор</link>,
чтобы гарантировать, что родительский класс правильно присвоил значения доступным данным.
Метод <link linkend="language.oop5.magic">__toString()</link> допустимо переопределять,
чтобы настроить пользовательский вывод, когда с объектом исключения работают как со строкой.
</para>
<note>
<para>
Исключения нельзя клонировать.
Попытка <link linkend="language.oop5.cloning">клонировать</link>
исключение приведёт к фатальной ошибке <constant>E_ERROR</constant>.
</para>
</note>
<example>
<title>Наследование класса Exception</title>
<programlisting role="php">
<![CDATA[
<?php
/**
* Определим класс исключения
*/
class MyException extends Exception
{
// Переопределим исключение так, что параметр message станет обязательным
public function __construct($message, $code = 0, ?Throwable $previous = null)
{
// Какой-то код
// Убедимся, что родительский класс правильно присвоил значения
parent::__construct($message, $code, $previous);
}
// Переопределим строковое представление объекта
public function __toString()
{
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
public function customFunction()
{
echo "Пользовательская функция для этого типа исключения\n";
}
}
/**
* Создадим класс для тестирования исключения
*/
class TestException
{
public $var;
const THROW_NONE = 0;
const THROW_CUSTOM = 1;
const THROW_DEFAULT = 2;
function __construct($avalue = self::THROW_NONE)
{
switch ($avalue) {
case self::THROW_CUSTOM:
// Выбрасываем собственное исключение
throw new MyException('1 — неправильный параметр', 5);
break;
case self::THROW_DEFAULT:
// Выбрасываем встроеное исключение
throw new Exception('2 — недопустимый параметр', 6);
break;
default:
// Без исключения, PHP создаст объект
$this->var = $avalue;
break;
}
}
}
// Пример 1
try {
$o = new TestException(TestException::THROW_CUSTOM);
} catch (MyException $e) { // Перехватится
echo "Блок catch перехватил пользовательское переопределённое исключение\n", $e;
$e->customFunction();
} catch (Exception $e) { // Пропустится
echo "Поймано встроенное исключение\n", $e;
}
// Продолжить выполнение
var_dump($o); // Null
echo "\n\n";
// Пример 2
try {
$o = new TestException(TestException::THROW_DEFAULT);
} catch (MyException $e) { // Тип исключения не совпадёт
echo "Блок catch перехватил пользовательское переопределённое исключение\n", $e;
$e->customFunction();
} catch (Exception $e) { // Перехватится
echo "Блок catch перехватил встроенное исключение\n", $e;
}
// Продолжить выполнение
var_dump($o); // Null
echo "\n\n";
// Пример 3
try {
$o = new TestException(TestException::THROW_CUSTOM);
} catch (Exception $e) { // Перехватится
echo "Блок catch перехватил встроенное исключение\n", $e;
}
// Продолжить выполнение
var_dump($o); // Null
echo "\n\n";
// Пример 4
try {
$o = new TestException();
} catch (Exception $e) { // Пропустится, поскольку исключение не выбрасывается
echo "Блок catch перехватил встроенное исключение\n", $e;
}
// Продолжить выполнение
var_dump($o); // TestException
echo "\n\n";
?>
]]>
</programlisting>
</example>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->