mirror of
https://github.com/php/doc-ru.git
synced 2025-08-16 18:22:04 +00:00
585 lines
22 KiB
XML
585 lines
22 KiB
XML
<?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
|
||
-->
|