PHP: <?php set_error_handler('onError'); echo '<h1>Работа с исключениями (ошибками)</h1>'; /* Здесь будет описано как работать с исключениями и ошибками. Сразу хочу отметить что все написанное здесь работает, минимум, в PHP5 Да, да, прямо сейчас вы можете вставить весь этот код в ваш файл test.php и разобраться, как это работает. Если вам знакомы исключения, этот материал МОЖЕТ оказаться полезным. Для тех кто не в курсе, что за зверь эти исключения: В вкратце, исключения - это, по сути, ошибки, или, как сказано в википедии, «возможные проблемы, которые могут возникнуть при выполнении программы». Например, при регистрации пользователя могут происходить такие исключения: * неверный e-mail * такой e-mail уже используется * такой логин уже зарегистрирован и т.д. Иногда, исключениями считают ошибки, предусмотренные программистом. Мы будем использовать любую не фатальную ошибку в качестве исключения. Также, предлагаю прочитать: * [url=http://ru.wikipedia.org/wiki/]http://ru.wikipedia.org/wiki/[/url]Обработка_исключений * [url=https://php.ru/manual/language.exceptions.php]http://php.net/manual/en/language.exceptions.html[/url] Для полного погружения полезно изучить ООП и наследование * [url=http://ru.wikipedia.org/wiki/]http://ru.wikipedia.org/wiki/[/url]Объектно-ориентированное_программирование * [url=http://ru.wikipedia.org/wiki/]http://ru.wikipedia.org/wiki/[/url]Наследование_(программирование) После прохождения этого квеста вы сможете наслаждаться такой конструкцией: */ echo '<h2>Пример исключения</h2>'; try { $handle = fopen('file.txt', 'r'); $line = fgets($handle, $text); fclose($handle); echo 'Строка из файла успешно прочитана: ',htmlspecialchars($line); } catch (ErrorException $e) { // обработка исключения работы с файлом echo '<h3>Ошибка при работе с файлом:</h3>',$e->getMessage(); } /* Ошибки и исключения в PHP это разные вещи. Объединим их - ошибки будут вызвать исключения Вначале кода стоит строка - set_error_handler('onError'); Функция set_error_handler регистрирует функцию, которая будет обрабатывать все ошибки (кроме фатальных). Наша функция onError будет ловить ошибку и вызывать исключение ErrorException: */ function onError($code, $message, $file, $line) { throw new ErrorException($message, $code, E_ALL, $file, $line); } /* С обычного объекта исключения вы можете получить такую информацию: $e->getMessage(); - Сообщение исключения $e->getCode(); - Номер ошибки $e->getFile(); - Файл, в котором произошло исключение $e->getLine(); - Строка $e->getTrace(); - обратная трассировка (массив) $e->getTraceAsString(); - тоже самое ввиде строки Все эти функции показали себя очень полезными. Предлагаю добавить еще одну - $e->getContext() В функцию которая зарегистрирована в set_error_handler PHP передает ссылку на таблицу переменных, того места, где произошла ошибка. Что бы добавить эту переменную в исключение - расширим класс Exception: */ set_error_handler('onErrorEx'); function onErrorEx($code, $message, $file, $line, $context) { throw new ExErrorException($message, $code, $file, $line, $context); } class ExErrorException extends Exception { protected $context; function __construct($message = '', $code = 0, $file = null, $line = null, $context = null) { $this->message = $message; $this->code = $code; $this->file = $file; $this->line = $line; $this->context = $context; } public function getContext() { return $this->context; } } echo '<h2>Расширенный обьект исключений</h2>'; function testContext() { $var1 = 1; $var2 = 2; // Создаем исключение, для этого, например выводим $var3, которая не определена: ошибка PHP -> onErrorEx() -> throw new ExErrorException() echo $var3; } try { testContext(); } catch (ExErrorException $e) { echo '<h3>Наше исключение:</h3>'; echo $e->getMessage(); echo '<h3>Таблица переменных, где произошло исключение</h3>'; echo '<pre>'; $context = $e->getContext(); print_r($context); echo '</pre>'; } /* Можно отлавливать исключения, одного блока try, разных типов: */ class a extends Exception {} class b extends Exception {} echo '<h2>Обработка исключений разных типов</h2>'; if (isset($e)) unset($e); try { $rand = rand(0, 1); $message = "Rand вернул $rand"; if ($rand) { throw new a($message); } else { throw new b($message); } } catch (a $e) { // особый код для исключения a } catch (b $e) { // особый код для исключения b } if (isset($e)) { // общий код для исключений типа a или b echo 'Произошла ошибка типа ',get_class($e),'.<br />Сообщение исключения: ',$e->getMessage(); } /* Любой класс «родитель» может отлавливать исключения «детей». Исключение Exception - родитель всех исключений, им можно отловить ЛЮБОЕ исключение. Имеет значение порядок блоков catch. */ echo '<h2>Наследование в исключениях</h2>'; try { throw new a; } catch (Exception $e) { echo '<h3>Отловили исключение класса ',get_class($e),' при помощи родителя Exception</h3>'; } catch (a $e) { echo '<h3>Этот блок не отработает, т.к. его нужно было разместить раньше</h3>'; } /* можно отлавливать исключения по интерфейсу */ interface MyExceptionInterface {} class MyException extends Exception implements MyExceptionInterface {} try { throw new MyException; } catch (MyExceptionInterface $e) { echo "Поймали исключение с интефейсом MyExceptionInterface"; } /* При использовании set_error_handler приготовьтесь к: 1. У вас начнут появляться ошибки, которых раньше не было даже с error_reporting(E_ALL); Например, при обращении к статической функции, которая не объявлена как статическая, - произойдет исключение 2. Исключение подавить символом @ не получится - его следует только отлавливать. 3. Мне не встречалась библиотека, написанная с учетом пункта 1, а потому при подключении/использовании «сторонних» классов/функций/framework'ов/... готовьтесь ловить исключение ;) Как выход, исправляйте в них баги или, на время, отключайте set_error_handler, для этого есть функция restore_error_handler() *** В данной статье, для наглядности, я сначала использовал функцию, а позже ее определял - это особенность PHP: функции определяются до выполнения кода например, в самом начале в строке set_error_handler('onError'); используется функция onError, которая определяется позже *** Версия и дата сборки cтатьи написана ниже ;) Релевантно: - [url=http://phpclub.ru/faq/PHP5/Exception]http://phpclub.ru/faq/PHP5/Exception[/url] - [url=http://habrahabr.ru/blogs/php/30399/]http://habrahabr.ru/blogs/php/30399/[/url] (Обработка ошибок и исключений в PHP) - [url=http://www.pyha.ru/forum/topic/103.0]http://www.pyha.ru/forum/topic/103.0[/url] (Обработка исключений в PHP) Ti, version RC3, 2007-2010, специально для php.ru/forum */ ?>
dark-demon Простой пример реальной необходимости в исключениях наверное не приведешь - это как с рекурсией, все при желании можно реализовать без нее. Исключения полезны в сложных проектах, когда разные части пишутся разными людьми и нельзя заранее предусмотреть, как эти части будут взаимодействовать друг с другом.
обработка исключений - это более удобный способ обработки ошибок, а ошибки могут возникать ВЕЗДЕ, а посему, обрабатывать исключения можна везде, вплоть до паранои
Только вот исключения страдают большой тормознутостью... Сделайте тестовый скрипт (я тестировал на регистрации пользователя, где в форме ~15 полей, все надо проверить) - exceptions vs if {} else {}... Результат удручающий, поэтому отказался от них в основной части кода. Использую иногда, где это приносит действительно удобство и не требует скорости (к примеру скрипты регистрации, которые запускаються весьма редко относительно других скриптов). Так что я считаю выше описанный подход большой глупостью в плане потери производительности - писать всё на исключениях в PHP пока очень рано - if {} else работает в десятки раз быстрее да и грамотно написанная система обойдёться минимальным их кол-вом.
вот пример того как исключения могут облегчить жизнь: Кусок кода получает реферер и сверяет его хост с текущим. ДО исключений. Код запутан и слишком излишен, в стиле "две страници ассамблера, что делает не знаю, но трогать баюсь" (С) БОР PHP: <?php $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false; $our = false; if ($referer) { $arr = @parse_url($referer); if ($arr) { if (isset($arr['host']) and strcasecmp($arr['host'], $_SERVER['HTTP_HOST']) == 0) $our = 1; } else { $referer = false; } } ПОСЛЕ исключений PHP: <?php try { $referer = $_SERVER['HTTP_REFERER']; $arr = parse_url(referer); $our = strcasecmp($arr['host'], $_SERVER['HTTP_HOST']) == 0; } catch (ErrorExceptionEx $e) { $our = $referer = false; }
PHP: <?php $referrer= @$_SERVER['HTTP_REFERER']; $our= preg_match('/^'.preg_quote('http://'.@$_SERVER['HTTP_HOST'],'/').'/',$referrer); ?>
dark-demon Возможно, но в примере Ti сравнение идет без учета регистра - примеры должны работать аналогично (по идее). EDIT: Хотя ХЗ. Возможно, ты и прав. Пример без исключений мне нравится больше - даже не потому, что он проще, читабельнее - для меня, по крайней мере, и всяко быстрее работает (причем работает и под PHP4) но сколько потому, что для подобных примеров, имхо, привлекать исключения не нужно. Дело в том, что несовпадение реферера - вполне штатная ситуация. Это не ошибка, просто один из вполне легальных вариантов работы. Исключения же созданы для обработки ситуаций исключительных. В примере с реферером использование исключений - стрельба из пушки по воробьям.
Ti, я этой штукой редко пользуюсь попутно: есть ли какие-либо способы отлова фатальных ошибок? если конкретно, то нужно в логи писать дополнительную информацию.
dark-demon, увы =( Единственный вариант, который заработал — это регать ob_handler — он исполняется после скрипта даже в случае фатальных ошибок.
пробовал. в нём нельзя писать в логи - только в браузер пока удалось отловить только ошибки парсинга: http://dark-demon.jino-net.ru/samples/d ... /index.php c помощью error_get_last. можно, конечно, при следующем запуске скрипта проверять не завершился ли предыдущий фатальной ошибкой, но дополнительную инфу (ради чего это собственно и затевалось) взять уже просто неоткуда
Существующий ключ в середине массива 0.000124931335449 // if .. else 0.000010013580322 // @ 0.000005960464478 // ErrorExceptionEx Не существующий ключ в середине массива 0.000012874603271 // if .. else 0.000005960464478 // @ 0.000005006790161 // ErrorExceptionEx PHP: <? class ErrorExceptionEx extends Exception { protected $context; function __construct($message = '', $code = 0, $file = null, $line = null, $context = null) { $this->message = $message; $this->code = $code; $this->file = $file; $this->line = $line; $this->context = $context; } public function getContext() { return $this->context; } } function onErrorEx($code, $message, $file, $line, $context) { throw new ErrorExceptionEx($message, $code, $file, $line, $context); } function setExErrorHandler() { set_error_handler('onErrorEx'); } setExErrorHandler(); // большой массив $a = range(1, 1024*32); echo '<h2>Существующий ключ в середине массива</h2>'; $key = floor(count($a)/2); test($key); echo '<h2>Не существующий ключ в середине массива</h2>'; $key++; unset($a[$key]); test($key); function test($i) { global $a; // @ restore_error_handler(); $t = microtime(true); @$a[$i]++; echo number_format(microtime(true)-$t, 15), '<br />'; setExErrorHandler(); // if $t = microtime(true); if (isset($a[$i])) $a[$i]++; else $a[$i] = 1; echo number_format(microtime(true)-$t, 15), '<br />'; // Exception $t = microtime(true); try { $a[$i]++; } catch (ErrorExceptionEx $e) { $a[$i] = 1; } echo number_format(microtime(true)-$t, 15), '<br />'; }
Тоже самое, только с переменными: Cуществующая переменная 0.000088930130005 // if .. else 0.000009059906006 // @ 0.000005006790161 // ErrorExceptionEx Не существующая переменная 0.000010967254639 // if .. else 0.000006198883057 // @ 0.000051975250244 // ErrorExceptionEx
Вы часто используете их в таком контексте? Я - нет. Сделайте проверку формы на правильность и валидность введённых данных и при ошибке кидайте исключение, и выводите юзеру сообщение об ошибке. Сделайте более-менее большую форму (3-5 полей) и посмотрите результат. Вряд ли многие используют исключения в таком контексте как у вас.
Psih, убивал бы за такие вещи. случай из жизни: заполнил длиннющую форму, отослал, выдалавь ошибка, исправил, отослал, выдалась ещё одна, исправил, ...
Такая схема, это прямая разруха юзабилити. Исключение вызывается в месте, которое не знает как его обработать. Обычно это класс. Класс посетителя, в нем метод регистрации, этот метод не знает как решать проблему неверных данных – он создает исключение. Исключение может содержать информацию о нескольких ошибках.