Исключения
Общие сведения
PHP4 не имел никакой обработки исключений. В PHP5 появилась модель исключений аналогичная таковой в других языках программирования. Следует отметить, что имеется поддержка catch all, но не finally. Исключения бросаются оператором throw, ловятся в блоках try и обрабатываются в блоках catch.
Пример:
<?php
try {
// здесь производим опасные действия, т.е. те действия которые могут бросить исключение
} catch (Exception $e) {
// здесь мы оказываемся если поймали исключение с типом Exception, которое помещается в переменную $e
}
?>
Заметим, что на один
try блок может быть несколько
catch блоков, и в этом случае при исключении они просматриваются сверху вниз до первого совпадения между классом исключения и классом, указанным в блоке
catch. Сравнение производится оператором
instanceof.
Блоки
try/
catch также могут быть вложенными. И если при исключении в текущем блоке не найден его обработчик, то поиск продолжается в родительском. Так происходит, пока исключение не достигнет самого внешнего блока, а если и там не будет найдено соответствующего блока
catch, то исключение станет не пойманным, а выполнение программы прервется.
Исключение можно перебросить в родительский блок
try/
catch, даже если оно было поймано в текущем. Для этого достаточно просто выполнить
throw $e; в блоке
catch.
Пример:
<?php
class MyException extends Exception {
function display() {
echo 'MyException: ' . $this->getMessage() . "\n";
}
}
class MyExceptionFoo extends MyException {
function display() {
echo 'MyExceptionFoo: ' . $this->getMessage() . "\n";
}
}
try {
throw new MyExceptionFoo('Исключение');
} catch (MyException $exception) {
$exception->Display();
} catch (Exception $exception) {
echo $exception;
}
?>
Результат:
Любой пользовательский класс-исключение должен быть потомком встроенного класса
Exception. Это сделано для того, чтобы
catch (Exception $e) всегда ловил исключение и таким образом реализовывал catch all. Встроенный класс исключений также позволяет собрать много полезной информации не доступной никаким другим способом.
Встроенный класс
Exception можно представить следующим PHP-кодом. Комментарии показывают назначение каждого свойства и соответствующих методов-получателей. Однако, т.к. некоторые методы используются внутренне, они помечены как
финальные. В итоге класс очень ограничен, т.к. ему нужно быть уверенным, что все, что используется внутренне, работает, так как ожидается.
<?php
class Exception {
function __construct(/*string*/ $message = NULL, /*int*/ $code = 0) {
if (func_num_args()) {
$this->message = $message;
}
$this->code = $code;
$this->file = __FILE__; // места с ключевым словом throw
$this->line = __LINE__; // места с ключевым словом throw
$this->trace = debug_backtrace();
$this->string = StringFormat($this);
}
protected $message = 'Unknown exception'; // сообщение исключения
protected $code = 0; // определенный пользователем код исключения
protected $file; // имя файла, где произошло исключение
protected $line; // номер строки, где произошло исключение
private $trace; // бэктрейс исключения
private $string; // строковое представление исключения
final function getMessage() {
return $this->message;
}
final function getCode() {
return $this->code;
}
final function getFile() {
return $this->file;
}
final function getTrace() {
return $this->trace;
}
final function getTraceAsString() {
return self::TraceFormat($this);
}
function __toString() {
return $this->string;
}
static private function StringFormat(Exception $exception) {
// функция не доступная в виде php-скрипта, которая
// возвращает всю информацию в виде строки.
}
static private function TraceFormat(Exception $exception) {
// функция не доступная в виде php-скрипта, которая
// возвращает бэктрейс в виде строки.
}
}
?>
Благодаря этому ваше не пойманное исключение, будет хорошо выглядеть в консоли, но, к сожалению, не в браузере.
Проблемы с обратной совместимостью
Cтарый код без функций, методов или классов с именами throw, try и catch должен работать без изменений.
Дополнительные сведения
Описание в оригинальной документации:
http://www.php.net/exceptions
Преимущества и недостатки
Преимущества:
- Единый механизм обработки ошибок.
- Гарантированный выход из опасного блока. Т.е. нет необходимости анализировать код возврата функции и предпринимать какие-то действия, кроме кидания исключения.
- Все встроенные классы исключений, будь то DOMException, SQLLiteException, com_exception и др. наследованы от стандартного класса Exception и, следовательно, имеют стандартный интерфейс.
Недостатки:
- Все встроенные функции не бросают исключений, но это можно легко исправить.
Использование исключений для обработки ошибок
Лучше всего исключения подходят для обработки ошибок ваших собственных скриптов и библиотек, т.к. вы сами определяете каким образом и с какими параметрами бросать исключение. Однако многим хотелось бы обрабатывать через исключения и обычные ошибки времени выполнения PHP. Имея некоторые знания, мы легко можем это сделать.
Исходя из того, что стандартные функции PHP не бросают исключений, а делают обработку ошибок «по старинке», с использованием стандартных способов PHP, можно этим воспользоваться. Также принимая во внимание, что для E_NOTICE будет странным бросать исключение, которое, будучи не пойманным, остановит работу скрипта, можно сконструировать конвертер из сообщений в исключения. Однако для особых параноиков можно второй параметр функции set_error_handler() поменять на E_ALL | E_STRICT.
<?php
class phpException extends exception {
public function __construct($errno, $errstr, $errfile, $errline) {
parent::__construct();
$this->code = $errno;
$this->message = $errstr;
$this->file = $errfile;
$this->line = $errline;
}
}
function err2exc($errno, $errstr, $errfile, $errline) {
throw new phpException($errno, $errstr, $errfile, $errline);
}
set_error_handler('err2exc', E_ALL & ~E_NOTICE &~ E_USER_NOTICE | E_STRICT);
error_reporting(E_ALL | E_STRICT);
?>
Таким образом, мы имеем возможность ловить
phpException для ошибок PHP и любой другой класс исключений для своих собственных исключений.
Приведем пример для большей наглядности. Предположим, что вышеозначенный код лежит в файле
class.phpException.php
<?php
require_once 'class.phpException.php';
class calc {
// ...
static public function div($num1, $num2) {
$num1 = (float) $num1;
$num2 = (float) $num2;
if ($num2 == 0) {
throw new Exception('Деление на ноль');
}
return $num1 / $num2;
}
// ...
}
try {
intval($not_exists_var);
echo calc::div(1, 0);
fopen('non_exists_file');
} catch (exception $e) {
echo $e;
}
?>
Таким образом, все исключения и критические ошибки ловятся внутри блока
try и не приводят к засорению экрана и позволяют произвести некоторую работу по освобождению ресурсов.