За последние 24 часа нас посетили 18847 программистов и 1644 робота. Сейчас ищут 1145 программистов ...

Теория: Исключения и ошибки

Тема в разделе "Решения, алгоритмы", создана пользователем Ti, 24 мар 2007.

?

ну, как?

  1. Афтар жжот, песчы исчо

    0 голосов
    0,0%
  2. Понял ничего

    0 голосов
    0,0%
  3. -1, а нафега?

    0 голосов
    0,0%
  4. НЕРАБОТАЕТ!!!

    0 голосов
    0,0%
  5. Неасилил, патамушта многа букаф

    0 голосов
    0,0%
  1. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    PHP:
    1. <?php
    2. set_error_handler('onError');
    3. echo '<h1>Работа с исключениями (ошибками)</h1>';
    4.  
    5. /*
    6. Здесь будет описано как работать с исключениями и ошибками.
    7. Сразу хочу отметить что все написанное здесь работает, минимум, в PHP5
    8. Да, да, прямо сейчас вы можете вставить весь этот код в ваш файл test.php и разобраться, как это работает.
    9.  
    10. Если вам знакомы исключения, этот материал МОЖЕТ оказаться полезным.
    11.  
    12. Для тех кто не в курсе, что за зверь эти исключения:
    13. В вкратце, исключения - это, по сути, ошибки, или, как сказано в википедии, «возможные проблемы, которые могут возникнуть при выполнении программы».
    14. Например, при регистрации пользователя могут происходить такие исключения:
    15. * неверный e-mail
    16. * такой e-mail уже используется
    17. * такой логин уже зарегистрирован
    18. и т.д.
    19. Иногда, исключениями считают ошибки, предусмотренные программистом.
    20. Мы будем использовать любую не фатальную ошибку в качестве исключения.
    21. Также, предлагаю прочитать:
    22. * [url=http://ru.wikipedia.org/wiki/]http://ru.wikipedia.org/wiki/[/url]Обработка_исключений
    23. * [url=https://php.ru/manual/language.exceptions.php]http://php.net/manual/en/language.exceptions.html[/url]
    24.  
    25. Для полного погружения полезно изучить ООП и наследование
    26. * [url=http://ru.wikipedia.org/wiki/]http://ru.wikipedia.org/wiki/[/url]Объектно-ориентированное_программирование
    27. * [url=http://ru.wikipedia.org/wiki/]http://ru.wikipedia.org/wiki/[/url]Наследование_(программирование)
    28.  
    29. После прохождения этого квеста вы сможете наслаждаться такой конструкцией:
    30. */
    31. echo '<h2>Пример исключения</h2>';
    32. try {
    33.     $handle = fopen('file.txt', 'r');
    34.     $line = fgets($handle, $text);
    35.     fclose($handle);
    36.     echo 'Строка из файла успешно прочитана: ',htmlspecialchars($line);
    37. }
    38. catch (ErrorException $e) {
    39.     // обработка исключения работы с файлом
    40.     echo '<h3>Ошибка при работе с файлом:</h3>',$e->getMessage();
    41. }
    42.  
    43. /*
    44. Ошибки и исключения в PHP это разные вещи.
    45. Объединим их - ошибки будут вызвать исключения
    46.  
    47. Вначале кода стоит строка - set_error_handler('onError');
    48. Функция set_error_handler регистрирует функцию, которая будет обрабатывать все ошибки (кроме фатальных).
    49.  
    50. Наша функция onError будет ловить ошибку и вызывать исключение ErrorException:
    51. */
    52. function onError($code, $message, $file, $line) {
    53.     throw new ErrorException($message, $code, E_ALL, $file, $line);
    54. }
    55.  
    56. /*
    57. С обычного объекта исключения вы можете получить такую информацию:
    58. $e->getMessage();                - Сообщение исключения
    59. $e->getCode();                  - Номер ошибки
    60. $e->getFile();                  - Файл, в котором произошло исключение
    61. $e->getLine();                  - Строка
    62. $e->getTrace();                  - обратная трассировка (массив)
    63. $e->getTraceAsString();          - тоже самое ввиде строки
    64. Все эти функции показали себя очень полезными.
    65. Предлагаю добавить еще одну - $e->getContext()
    66. В функцию которая зарегистрирована в set_error_handler PHP передает ссылку на таблицу переменных, того места, где произошла ошибка.
    67. Что бы добавить эту переменную в исключение - расширим класс Exception:
    68. */
    69. set_error_handler('onErrorEx');
    70. function onErrorEx($code, $message, $file, $line, $context) {
    71.     throw new ExErrorException($message, $code, $file, $line, $context);
    72. }
    73. class ExErrorException extends Exception {
    74.     protected $context;
    75.     function __construct($message = '', $code = 0, $file = null, $line = null, $context = null) {
    76.         $this->message = $message;
    77.         $this->code = $code;
    78.         $this->file = $file;
    79.         $this->line = $line;
    80.         $this->context = $context;
    81.     }
    82.     public function getContext() {
    83.         return $this->context;
    84.     }
    85. }
    86.  
    87. echo '<h2>Расширенный обьект исключений</h2>';
    88.  
    89. function testContext() {
    90.     $var1 = 1;
    91.     $var2 = 2;
    92.     // Создаем исключение, для этого, например выводим $var3, которая не определена: ошибка PHP -> onErrorEx() -> throw new ExErrorException()
    93.     echo $var3;
    94. }
    95. try {
    96.     testContext();
    97. }
    98. catch (ExErrorException $e) {
    99.     echo '<h3>Наше исключение:</h3>';
    100.     echo $e->getMessage();
    101.     echo '<h3>Таблица переменных, где произошло исключение</h3>';
    102.     echo '<pre>';
    103.     $context = $e->getContext();
    104.     print_r($context);
    105.     echo '</pre>';
    106. }
    107.  
    108. /*
    109. Можно отлавливать исключения, одного блока try, разных типов:
    110. */
    111. class a extends Exception {}
    112. class b extends Exception {}
    113. echo '<h2>Обработка исключений разных типов</h2>';
    114. if (isset($e)) unset($e);
    115. try {
    116.     $rand = rand(0, 1);
    117.     $message = "Rand вернул $rand";
    118.     if ($rand) {
    119.         throw new a($message);
    120.     }
    121.     else {
    122.         throw new b($message);
    123.     }
    124. }
    125. catch (a $e) {
    126.     // особый код для исключения a
    127. }
    128. catch (b $e) {
    129.     // особый код для исключения b
    130. }
    131. if (isset($e)) { // общий код для исключений типа a или b
    132.     echo 'Произошла ошибка типа ',get_class($e),'.<br />Сообщение исключения: ',$e->getMessage();
    133. }
    134.  
    135.  /*
    136.  Любой класс «родитель» может отлавливать исключения «детей».
    137.  Исключение Exception - родитель всех исключений, им можно отловить ЛЮБОЕ исключение.
    138.  Имеет значение порядок блоков catch.
    139.  */
    140.  
    141.  echo '<h2>Наследование в исключениях</h2>';
    142.  try {
    143.      throw new a;
    144.  }
    145.  catch (Exception $e) {
    146.      echo '<h3>Отловили исключение класса ',get_class($e),' при помощи родителя Exception</h3>';
    147.  }
    148.  catch (a $e) {
    149.      echo '<h3>Этот блок не отработает, т.к. его нужно было разместить раньше</h3>';
    150.  }
    151.  
    152. /*
    153. можно отлавливать исключения по интерфейсу
    154. */
    155. interface MyExceptionInterface {}
    156. class MyException extends Exception implements MyExceptionInterface {}
    157. try {
    158.     throw new MyException;
    159. }
    160. catch (MyExceptionInterface $e) {
    161.     echo "Поймали исключение с интефейсом MyExceptionInterface";
    162. }
    163.  
    164. /*
    165. При использовании set_error_handler приготовьтесь к:
    166.  
    167. 1. У вас начнут появляться ошибки, которых раньше не было даже с error_reporting(E_ALL);
    168. Например, при обращении к статической функции, которая не объявлена как статическая, - произойдет исключение
    169.  
    170. 2. Исключение подавить символом @ не получится - его следует только отлавливать.
    171.  
    172. 3. Мне не встречалась библиотека, написанная с учетом пункта 1, а потому при подключении/использовании «сторонних» классов/функций/framework'ов/... готовьтесь ловить исключение ;)
    173. Как выход, исправляйте в них баги или, на время, отключайте set_error_handler, для этого есть функция restore_error_handler()
    174.  
    175.  
    176. *** В данной статье, для наглядности, я сначала использовал функцию, а позже ее определял - это особенность PHP: функции определяются до выполнения кода
    177. например, в самом начале в строке set_error_handler('onError'); используется функция onError, которая определяется позже
    178.  
    179. *** Версия и дата сборки cтатьи написана ниже ;)
    180.  
    181. Релевантно:
    182. - [url=http://phpclub.ru/faq/PHP5/Exception]http://phpclub.ru/faq/PHP5/Exception[/url]
    183. - [url=http://habrahabr.ru/blogs/php/30399/]http://habrahabr.ru/blogs/php/30399/[/url] (Обработка ошибок и исключений в PHP)
    184. - [url=http://www.pyha.ru/forum/topic/103.0]http://www.pyha.ru/forum/topic/103.0[/url] (Обработка исключений в PHP)
    185.  
    186. Ti, version RC3, 2007-2010, специально для php.ru/forum
    187. */
    188. ?>
     
  2. Anonymous

    Anonymous Guest

    +1. Еще бы текстовую теорию приложил, цены бы не было...
     
  3. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    Присоединяюсь, все очень здраво и читабельно :)
     
  4. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    а может поделитесь примером, где бы реально была бы необходимость обрабатывать исключения?
     
  5. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    Простой пример реальной необходимости в исключениях наверное не приведешь - это как с рекурсией, все при желании можно реализовать без нее. :) Исключения полезны в сложных проектах, когда разные части пишутся разными людьми и нельзя заранее предусмотреть, как эти части будут взаимодействовать друг с другом.
     
  6. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    обработка исключений - это более удобный способ обработки ошибок, а ошибки могут возникать ВЕЗДЕ, а посему, обрабатывать исключения можна везде, вплоть до паранои
     
  7. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Только вот исключения страдают большой тормознутостью...
    Сделайте тестовый скрипт (я тестировал на регистрации пользователя, где в форме ~15 полей, все надо проверить) - exceptions vs if {} else {}... Результат удручающий, поэтому отказался от них в основной части кода. Использую иногда, где это приносит действительно удобство и не требует скорости (к примеру скрипты регистрации, которые запускаються весьма редко относительно других скриптов).
    Так что я считаю выше описанный подход большой глупостью в плане потери производительности - писать всё на исключениях в PHP пока очень рано - if {} else работает в десятки раз быстрее да и грамотно написанная система обойдёться минимальным их кол-вом.
     
  8. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    вот пример того как исключения могут облегчить жизнь:

    Кусок кода получает реферер и сверяет его хост с текущим.


    ДО исключений.
    Код запутан и слишком излишен, в стиле "две страници ассамблера, что делает не знаю, но трогать баюсь" (С) БОР
    PHP:
    1. <?php
    2. $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false;
    3. $our = false;
    4. if ($referer) {
    5.     $arr = @parse_url($referer);
    6.     if ($arr) {
    7.         if (isset($arr['host']) and strcasecmp($arr['host'], $_SERVER['HTTP_HOST']) == 0) $our = 1;
    8.     }
    9.     else {
    10.         $referer = false;
    11.     }
    12. }
    ПОСЛЕ исключений
    PHP:
    1. <?php
    2. try {
    3.     $referer = $_SERVER['HTTP_REFERER'];
    4.     $arr = parse_url(referer);
    5.     $our = strcasecmp($arr['host'], $_SERVER['HTTP_HOST']) == 0;
    6. }
    7. catch (ErrorExceptionEx $e) {
    8.     $our = $referer = false;
    9. }
     
  9. vb

    vb Активный пользователь

    С нами с:
    6 июн 2006
    Сообщения:
    911
    Симпатии:
    0
    Адрес:
    Saint-Petersburg
    Mr. Ti, +1 :)
     
  10. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    PHP:
    1. <?php
    2. $referrer= @$_SERVER['HTTP_REFERER'];
    3. $our= preg_match('/^'.preg_quote('http://'.@$_SERVER['HTTP_HOST'],'/').'/',$referrer);
    4. ?>
     
  11. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    +1, с оговоркой, что после закрывающего ограничителя в preg_match добавлено "i" :)
     
  12. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    Dagdamor, если в реферере неправильный регистр, то он явно поддельный...
     
  13. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    Возможно, но в примере Ti сравнение идет без учета регистра - примеры должны работать аналогично (по идее).

    EDIT: Хотя ХЗ. Возможно, ты и прав. :) Пример без исключений мне нравится больше - даже не потому, что он проще, читабельнее - для меня, по крайней мере, и всяко быстрее работает (причем работает и под PHP4) но сколько потому, что для подобных примеров, имхо, привлекать исключения не нужно. Дело в том, что несовпадение реферера - вполне штатная ситуация. Это не ошибка, просто один из вполне легальных вариантов работы. Исключения же созданы для обработки ситуаций исключительных. В примере с реферером использование исключений - стрельба из пушки по воробьям.
     
  14. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    dark-demon
    скромный вопрос: как Вы узнаете об ошибках на работающих проектах?
     
  15. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    всё в логах
     
  16. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    и ошибки, возникающие при использовании @
     
  17. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    Ti, я этой штукой редко пользуюсь :)

    попутно: есть ли какие-либо способы отлова фатальных ошибок? если конкретно, то нужно в логи писать дополнительную информацию.
     
  18. Anonymous

    Anonymous Guest

    dark-demon, увы =(
    Единственный вариант, который заработал — это регать ob_handler — он исполняется после скрипта даже в случае фатальных ошибок.
     
  19. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    пробовал. в нём нельзя писать в логи - только в браузер :(
    пока удалось отловить только ошибки парсинга: http://dark-demon.jino-net.ru/samples/d ... /index.php c помощью error_get_last. можно, конечно, при следующем запуске скрипта проверять не завершился ли предыдущий фатальной ошибкой, но дополнительную инфу (ради чего это собственно и затевалось) взять уже просто неоткуда :(
     
  20. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    Существующий ключ в середине массива
    0.000124931335449 // if .. else
    0.000010013580322 // @
    0.000005960464478 // ErrorExceptionEx

    Не существующий ключ в середине массива
    0.000012874603271 // if .. else
    0.000005960464478 // @
    0.000005006790161 // ErrorExceptionEx

    PHP:
    1. <?
    2. class ErrorExceptionEx extends Exception {
    3.  
    4.  
    5.     protected $context;
    6.  
    7.  
    8.     function __construct($message = '', $code = 0, $file = null, $line = null, $context = null) {
    9.         $this->message = $message;
    10.         $this->code = $code;
    11.         $this->file = $file;
    12.         $this->line = $line;
    13.         $this->context = $context;
    14.     }
    15.  
    16.  
    17.     public function getContext() {
    18.         return $this->context;
    19.     }
    20.  
    21.  
    22. }
    23.  
    24. function onErrorEx($code, $message, $file, $line, $context) { throw new ErrorExceptionEx($message, $code, $file, $line, $context); }
    25. function setExErrorHandler() { set_error_handler('onErrorEx'); }
    26. setExErrorHandler();
    27.  
    28. // большой массив
    29. $a = range(1, 1024*32);
    30.  
    31. echo '<h2>Существующий ключ в середине массива</h2>';
    32. $key = floor(count($a)/2);
    33. test($key);
    34.  
    35. echo '<h2>Не существующий ключ в середине массива</h2>';
    36. $key++;
    37. unset($a[$key]);
    38. test($key);
    39.  
    40. function test($i) {
    41.     global $a;
    42.  
    43.     // @
    44.     $t = microtime(true);
    45.  
    46.     @$a[$i]++;
    47.  
    48.     echo number_format(microtime(true)-$t, 15), '<br />';
    49.     setExErrorHandler();
    50.  
    51.     // if
    52.     $t = microtime(true);
    53.  
    54.     if (isset($a[$i])) $a[$i]++;
    55.     else $a[$i] = 1;
    56.  
    57.     echo number_format(microtime(true)-$t, 15), '<br />';
    58.  
    59.     // Exception
    60.     $t = microtime(true);
    61.  
    62.     try { $a[$i]++; }
    63.     catch (ErrorExceptionEx $e) { $a[$i] = 1; }
    64.  
    65.     echo number_format(microtime(true)-$t, 15), '<br />';
    66. }
     
  21. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    Тоже самое, только с переменными:

    Cуществующая переменная
    0.000088930130005 // if .. else
    0.000009059906006 // @
    0.000005006790161 // ErrorExceptionEx

    Не существующая переменная
    0.000010967254639 // if .. else
    0.000006198883057 // @
    0.000051975250244 // ErrorExceptionEx
     
  22. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Вы часто используете их в таком контексте? Я - нет.
    Сделайте проверку формы на правильность и валидность введённых данных и при ошибке кидайте исключение, и выводите юзеру сообщение об ошибке.

    Сделайте более-менее большую форму (3-5 полей) и посмотрите результат.
    Вряд ли многие используют исключения в таком контексте как у вас.
     
  23. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    Psih, убивал бы за такие вещи.
    случай из жизни: заполнил длиннющую форму, отослал, выдалавь ошибка, исправил, отослал, выдалась ещё одна, исправил, ...
     
  24. Ti

    Ti Активный пользователь

    С нами с:
    3 июл 2006
    Сообщения:
    2.378
    Симпатии:
    1
    Адрес:
    d1.ru, Екатеринбург
    Такая схема, это прямая разруха юзабилити.

    Исключение вызывается в месте, которое не знает как его обработать.
    Обычно это класс.
    Класс посетителя, в нем метод регистрации, этот метод не знает как решать проблему неверных данных – он создает исключение.
    Исключение может содержать информацию о нескольких ошибках.
     
  25. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    Ti
    С этого момента поподробнее, пожалуйста...