Эта «небольшая» статейка является развитием темы затронутой в этой статье.
Как известно, PHP зародился довольно давно и уже тогда возник вопрос, что делать с возникающими ошибками. Perl, который является несомненным прародителем PHP по умолчанию не имел какой-либо системы обработки ошибок. При возникновении любой ошибки сервер выбрасывал 500-ю ошибку и на этом все заканчивалось. Поэтому Warnings, Fatal Errors и Notices были настоящим прорывом в облегчении и без того нелегкого труда программиста. Однако время шло, механизмы PHP не менялись, а технологии, как известно, на месте стоять не любят.
И вот в PHP 5.0, наконец-то, в арсенале программистов появилось такое мощное средство как исключение или Exception. Достоинств у Exception много, опишу лишь некоторые (возможно, я выражаюсь неточно или даже безграмотно, но мне было просто лень выискивать научные термины для описания преимуществ, потому описаны они «своими словами»):
Возможности обработки стандартных ошибок PHP крайне ограничены:
Ясно, что стандартный механизм обработки ошибок устарел и присутствует в языке только из соображений совместимости.
В этой небольшой статье я попробую осветить, как можно сделать обработку ошибок универсальной, переведя ее на использование механизма исключений.
Основная идея: ставим свой обработчик для стандартных ошибок и «бросаем» исключение в нем:
Этот код необходимо вынести в отдельный файл и подключать его только один раз. Класс MyException расширяет стандартный класс Exception добавлением двух дополнительных параметров в конструктор: файла и номера строки с ошибкой.
Функция set_error_handler устанавливает в качестве обработчика ошибок динамически созданную lambda-функцию (callback), которая и генерирует исключение в случае возникновения ошибки. Особенно прошу обратить внимание на второй параметр функции set_error_handler. Этот параметр очень важен, так как он определяет для каких типов ошибок будет вызываться пользовательский (то есть наш) обработчик, а для каких стандартный. В данном примере, я установил значение E_ALL, что означает, что обработчик будет вызываться для всех типов ошибок.
Если мы не хотим обрабатывать некоторые типы ошибок, например, Notice, то мы можем запросто указать это:
Однако идеальным подходом, как мне кажется будет все таки обработка всех ошибок, но для некоторых типов, в частности, notice, было бы целесообразно не выкидывать exception, а просто выводить информацию на экран:
Теперь рассмотрим приближенный к жизни пример. Задача:
Есть форма регистрации на сайте, необходимо реализовать при помощи исключений обработку ошибок валидации и выдачу соответствующих предупреждений для пользователя.
Сложности здесь собственно две:
Решение:
Главной сложностью здесь для нас будет то самое пресловутое преимущество Exception, которое заключается в том, что при «бросании» исключения происходит выход из управляющих конструкций до первого блока catch (или до конца скрипта). Для того, чтобы обойти этот подводный камень определим новый класс-потомок FormFieldsListException, в котором реализуем механизм накопления ошибок, а «бросать» исключение будем только после валидации всех полей. В классе FormFieldsListException определяем защищенный (protected) член $_list, в котором будем хранить данные. Для упрощения работы с массивом $_list указываем, что класс будет реализовывать два интерфейса: ArrayAccess для доступа к элементам массива и Iterator для работы в цикле. При инициализации метода проверки валидации создаем объект FormFieldsListException, а затем по мере определения ошибок добавляем их в объект FormFieldsListException, как в обычный массив.
После окончания процедуры валидации проверяем были ли занесены какие-то сообщения об ошибках. Если да то «бросаем» подготовленный объект исключения.
Для отлова исключения используем два блока catch: для FormFieldsListException и для всех остальных исключений. Это позволяет задать различные виды действий при возникновении различных типов исключений.
Вот так вот! :)
Правильно спроектированная система исключений способна серьезно упростить жизнь программиста, особенно при разработке приложений с использованием шаблона MVC. Как показало это небольшое исследование система обработки исключений в PHP5 таит в себе немалые резервы для модернизации и использования в специфических ситуациях.
P.S.: Некоторые из программистов, которым я показывал данную статью, считают использование исключений для валидации форм, мягко говоря, не самым лучшим вариантом (кстати, я бы попросил читателей, которые «в теме» высказаться по этому поводу), поэтому прошу считать приведенный пример всего лишь учебным примером, а не руководством к действию.
P.P.S.: Огромное спасибо товарищу ashofthedream, спор с которым и натолкнул меня на мысль изучить исключения поподробнее.
UPD: Перенесено в блог PHP
Как известно, PHP зародился довольно давно и уже тогда возник вопрос, что делать с возникающими ошибками. Perl, который является несомненным прародителем PHP по умолчанию не имел какой-либо системы обработки ошибок. При возникновении любой ошибки сервер выбрасывал 500-ю ошибку и на этом все заканчивалось. Поэтому Warnings, Fatal Errors и Notices были настоящим прорывом в облегчении и без того нелегкого труда программиста. Однако время шло, механизмы PHP не менялись, а технологии, как известно, на месте стоять не любят.
И вот в PHP 5.0, наконец-то, в арсенале программистов появилось такое мощное средство как исключение или Exception. Достоинств у Exception много, опишу лишь некоторые (возможно, я выражаюсь неточно или даже безграмотно, но мне было просто лень выискивать научные термины для описания преимуществ, потому описаны они «своими словами»):
- Сквозная генерация. Это означает, что возникновение исключения где либо в коде будет приводит к последовательному выходу из управляющих конструкций и функций до первого блока catch либо до функции main (с выдачей соответствующей ошибки в поток) основного скрипта
- Возможность переопределения основного класса Exception через наследование
- Возможность обработки нескольких типов исключений одновременно
Возможности обработки стандартных ошибок PHP крайне ограничены:
- Можно заблокировать при помощи @
- Можно установить свой обработчик при помощи set_error_handler
- Можно сгенерировать свою ошибку при помощи trigger_error
Ясно, что стандартный механизм обработки ошибок устарел и присутствует в языке только из соображений совместимости.
В этой небольшой статье я попробую осветить, как можно сделать обработку ошибок универсальной, переведя ее на использование механизма исключений.
Основная идея: ставим свой обработчик для стандартных ошибок и «бросаем» исключение в нем:
* This source code was highlighted with Source Code Highlighter.
- <?php
- class MyException extends Exception {
- public function __construct($message, $errorLevel = 0, $errorFile = '', $errorLine = 0) {
- parent::__construct($message, $errorLevel);
- $this->file = $errorFile;
- $this->line = $errorLine;
- }
- }
- set_error_handler(create_function('$c, $m, $f, $l', 'throw new MyException($m, $c, $f, $l);'), E_ALL);
- ?>
Этот код необходимо вынести в отдельный файл и подключать его только один раз. Класс MyException расширяет стандартный класс Exception добавлением двух дополнительных параметров в конструктор: файла и номера строки с ошибкой.
Функция set_error_handler устанавливает в качестве обработчика ошибок динамически созданную lambda-функцию (callback), которая и генерирует исключение в случае возникновения ошибки. Особенно прошу обратить внимание на второй параметр функции set_error_handler. Этот параметр очень важен, так как он определяет для каких типов ошибок будет вызываться пользовательский (то есть наш) обработчик, а для каких стандартный. В данном примере, я установил значение E_ALL, что означает, что обработчик будет вызываться для всех типов ошибок.
Если мы не хотим обрабатывать некоторые типы ошибок, например, Notice, то мы можем запросто указать это:
set_error_handler(create_function('$c, $m, $f, $l', 'throw new MyException($m, $c, $f, $l);'), E_ALL & ~E_NOTICE);* This source code was highlighted with Source Code Highlighter.
Однако идеальным подходом, как мне кажется будет все таки обработка всех ошибок, но для некоторых типов, в частности, notice, было бы целесообразно не выкидывать exception, а просто выводить информацию на экран:
set_error_handler(create_function('$c, $m, $f, $l', 'if ($c === E_NOTICE) {echo 'This is notice: '.$m} else {throw new MyException($m, $c, $f, $l);}'), E_ALL);* This source code was highlighted with Source Code Highlighter.
Теперь рассмотрим приближенный к жизни пример. Задача:
Есть форма регистрации на сайте, необходимо реализовать при помощи исключений обработку ошибок валидации и выдачу соответствующих предупреждений для пользователя.
Сложности здесь собственно две:
- Выводить все ошибки одновременно, а не по одной
- Отделить обработку ошибок валидации от обработки прочих исключений
Решение:
Главной сложностью здесь для нас будет то самое пресловутое преимущество Exception, которое заключается в том, что при «бросании» исключения происходит выход из управляющих конструкций до первого блока catch (или до конца скрипта). Для того, чтобы обойти этот подводный камень определим новый класс-потомок FormFieldsListException, в котором реализуем механизм накопления ошибок, а «бросать» исключение будем только после валидации всех полей. В классе FormFieldsListException определяем защищенный (protected) член $_list, в котором будем хранить данные. Для упрощения работы с массивом $_list указываем, что класс будет реализовывать два интерфейса: ArrayAccess для доступа к элементам массива и Iterator для работы в цикле. При инициализации метода проверки валидации создаем объект FormFieldsListException, а затем по мере определения ошибок добавляем их в объект FormFieldsListException, как в обычный массив.
* This source code was highlighted with Source Code Highlighter.
- <?php
- class FormFieldsListException extends Exception implements ArrayAccess, Iterator {
- protected $_list = array();
- public function __construct() {
- }
- public function offsetExists($index) {
- return isset($this->_list[$index]);
- }
- public function offsetGet($index) {
- return $this->_list[$index];
- }
- public function offsetSet($index, $value) {
- if (isset($index)) {
- $this->_list[$index] = $value;
- }
- else {
- $this->_list[] = $value;
- }
- }
- public function offsetUnset($index) {
- unset($this->_list[$index]);
- }
- public function current() {
- return current($this->_list);
- }
- public function key() {
- return key($this->_list);
- }
- public function next() {
- return next($this->_list);
- }
- public function rewind() {
- return reset($this->_list);
- }
- public function valid() {
- return (bool) $this->current();
- }
- }
- ?>
После окончания процедуры валидации проверяем были ли занесены какие-то сообщения об ошибках. Если да то «бросаем» подготовленный объект исключения.
Для отлова исключения используем два блока catch: для FormFieldsListException и для всех остальных исключений. Это позволяет задать различные виды действий при возникновении различных типов исключений.
* This source code was highlighted with Source Code Highlighter.
- <?php
- function validateForm() {
- $e = new FormFieldsListException();
- if ($errorInFirstField) {
- $e[] = 'Error in first field';
- }
- if ($errorInSecondField) {
- $e[] = 'Error in second field';
- }
- if ((bool)$e->current()) {
- throw $e;
- }
- }
- try {
- validateForm();
- }
- catch (FormFieldsListException $error) {
- echo '<b>Errors in the fields</b>:<br />';
- foreach ($error as $e) {
- echo $e.'<br />';
- }
- }
- catch (Exception $error) {
- echo 'Not validation error! '.$error->getMessage();
- }
- ?>
Вот так вот! :)
Правильно спроектированная система исключений способна серьезно упростить жизнь программиста, особенно при разработке приложений с использованием шаблона MVC. Как показало это небольшое исследование система обработки исключений в PHP5 таит в себе немалые резервы для модернизации и использования в специфических ситуациях.
P.S.: Некоторые из программистов, которым я показывал данную статью, считают использование исключений для валидации форм, мягко говоря, не самым лучшим вариантом (кстати, я бы попросил читателей, которые «в теме» высказаться по этому поводу), поэтому прошу считать приведенный пример всего лишь учебным примером, а не руководством к действию.
P.P.S.: Огромное спасибо товарищу ashofthedream, спор с которым и натолкнул меня на мысль изучить исключения поподробнее.
UPD: Перенесено в блог PHP