Обработка php ошибок. Часть 2
Продолжу тему обработки ошибок, начатую ранее статьей Обработка php ошибок. Часть1. В конце той статьи я написал, что не рассмотрел там некоторые вещи. Теперь, как я и обещал, мы их наверстаем. Опять замечу только, что все написанное ниже справедливо только для php версии 5. |
Пользовательские типы исключений
Мы знаем, что есть общий класс Exception, который отвечает за абсолютно все исключения. Но до этого момента многие из нас не знали, что можно самим определять свои типы исключений. Механизм этот основан на наследовании классов. С наследованием, я надеюсь, вы уже ознакомились в одной из моих статей на этом блоге.
Итак, попробуем создать собственный тип исключений. Назовем его MyException:
class MyException extends Exception {}
Теперь мы можем выкинуть исключение нашего типа:
…
throw new MyException();
…
И в итоге можем обработать собственное исключение:
try {
…
throw new MyException();
…
} catch (MyException $me) {
echo ‘Поймали исключение MyException’;
} catch (Exception $e) {
echo ‘Поймали какое-то исключение’;
}
?>
То есть здесь мы определили два идущих подряд блока catch, чтобы поймать все исключения, которые могут возникнуть. Если бы мы не определили последний catch, то при возникновении исключения не типа MyException, скрипт бы не обработал остальные исключения (если мы не воспользовались функцией set_exception_handler) и скорее всего завершился бы с ошибкой Uncaught Exception… (если бы конечно на уровне выше не был определен еще какой-то обработчик, но об этом скажем чуть позже).
Наше исключение является не чем иным, как обыкновенным классом. А значит, оно может содержать и конструктор/деструктор, и свойства, и собственные методы. Класс Exception вообще-то выглядит следующим образом:
class Exception {
// сообшение
protected $message = 'Unknown exception';
// код исключения, определяемый пользователем
protected $code = 0;
// файл, в котором было выброшено исключение
protected $file;
// строка, в которой было выброшено исключение
protected $line;
// конструктор
function __construct($message = null, $code = 0);
// возвращает сообщение исключения
final function getMessage();
// возвращает код исключения
final function getCode();
// возвращает путь к файлу, где выброшено исключение
final function getFile();
// возвращает номер строки, выбросившей исключение
final function getLine();
// массив обратной трассировки
final function getTrace();
// обратная трассировка как строка
final function getTraceAsString();
// должен вернуть форматированную строку, для отображения
function __toString();
}
?>
Как видим, встроенный в PHP класс Exception обладает исчерпывающей информацией, достаточной для анализа произошедшей ошибки. Кроме этого, здесь мы видим, что переопределить в производном классе можно только конструктор и метод __toString, т.к. остальные методы объявлены с ключевым словом final. Но мы можем не только переопределять их, но и создавать собственные методы, ведь MyException является классом.
Попробуем теперь расширить наш MyException:
class MyException extends Exception {
private $myerror;
public function __construct($myerror) {
$this->myerror = $myerror;
parent::__construct(‘’, 0);
}
public function getMyError() {
return $this->myerror;
}
public function __toString() {
return ‘Моя ошибка: ’.$this->myerror.’; Сообщение: ’.$this->getMessage();
}
}
?>
После этого можем сгенерировать наше исключение следующим образом:
…
throw new MyException(‘мое описание ошибки’);
…
Многоуровневая обработка исключений и разматывание стека функций
Смысла в обработке исключительных ситуаций почти не было бы, если бы не возможность многоуровневой обработки:
// какая-то функция, в которой происходит ошибка
function fn() {
…
throw new Exception(‘ошибочка вышла!’);
…
}
…
try {
fn();
} catch (Exception $e) {
echo ‘Ошибка: ’. $e->getMessage();
}
…
?>
Заметим, что здесь в функции нет блоков try/catch, а есть только выкидывание исключения. Это выкидывание написано там с надеждой (или скорее уверенностью :)), что это исключение будет обработано на уровне выше. Ведь функция или метод класса не должны беспокоиться об обработке исключений в принципе. Произошла ошибка - выкидываем исключение и завершаемся, а тот, кто нас вызвал - пусть разбирается.
Это и есть так называемое разматывание стека функций. Можно рассмотреть пример посложнее:
// какая-то функция, в которой происходит ошибка
function fn2() {
…
throw new Exception(‘ошибочка вышла!’);
…
}
// эта функция вызывает fn2
function fn() {
…
fn2();
echo 'test';
…
}
…
try {
fn();
} catch (Exception $e) {
echo ‘Ошибка: ’. $e->getMessage();
}
…
?>
В этом случае исключение также будет поймано, т.к. выброшенное перейдет на тот уровень, где его наконец смогут поймать При этом сообщения test из функции fn мы не увидим, т.к. управление прервется на вызове fn2() из-за исключения в функции fn2.
Вот и все, что я хотел рассказать про исключительные ситуации. Теперь у вас есть полная необходимая информация для того, чтобы обрабатывать ошибки в своих скриптах правильно и удобно.
Не нужная эта балалайкак. set_error_handler() + trigger_error() = TRUE.