Обробка помилок в коді програм РНР

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

МІНІСТЕРСТВО ОСВІТИ
РОСІЙСЬКИЙ ХІМІКО-ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
ім. Д.І. Менделєєва
НОВОМОСКОВСЬКИЙ ІНСТИТУТ
ОБРОБКА ПОМИЛОК У КОДІ ПРОГРАМ PHP
НАВЧАЛЬНИЙ ПОСІБНИК
Новомосковськ 2008
МІНІСТЕРСТВО ОСВІТИ
РОСІЙСЬКИЙ ХІМІКО-ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
ім. Д.І. Менделєєва
НОВОМОСКОВСЬКИЙ ІНСТИТУТ
ОБРОБКА ПОМИЛОК У КОДІ ПРОГРАМ PHP
НАВЧАЛЬНИЙ ПОСІБНИК
Укладач: В. С. Прохоров
Зміст
ВСТУП
1. КОНТРОЛЬ ПОМИЛОК
1.1 РОЛІ ПОМИЛОК
1.2 ВИДИ ПОМИЛОК
1.2.1 Несерйозно ПОМИЛКИ
1.2.2 СЕРЙОЗНІ ПОМИЛКИ
1.2.2.1 Припинення виконання програми
1.2.2.2Возврат неприпустимого значення
1.2.2.3 Ненормальне стан програми
1.2.2.4 Виклик функції-обробника
1.3 ДИРЕКТИВИ РНР КОНТРОЛЮ ПОМИЛОК
1.3.1 ДИРЕКТИВА error_reporting
1.3.2 ДИРЕКТИВА display_errors
1.3.3 ДИРЕКТИВА error_log
1.4 УСТАНОВКА РЕЖИМУ ВИВЕДЕННЯ ПОМИЛОК
1.5 ОПЕРАТОР ВІДКЛЮЧЕННЯ ПОМИЛОК
1.5.1 ПРИКЛАД ВИКОРИСТАННЯ ОПЕРАТОРА @
1.5.2 ПРЕДОСТЕРІЖЕНІЯ ЩОДО ВИКОРИСТАННЯ ОПЕРАТОРА ВІДКЛЮЧЕННЯ ПОМИЛОК @
2 перехоплення ПОМИЛОК. МЕТОД РЕЄСТРАЦІЇ обробника помилок
2.1 ФУНКЦІЯ set_error_handler
2.2 ФУНКЦІЯ restore_error_handler ()
2.3 ПРОБЛЕМИ З ОПЕРАТОРОМ @
2.4 ГЕНЕРАЦІЯ ПОМИЛОК
2.5 СТЕК ВИКЛИКІВ ФУНКЦІЙ
2.6 ПРИМУСОВЕ ЗАВЕРШЕННЯ ПРОГРАМИ
2.7 ФІНАЛІЗАТОРИ
3. Перехоплювати помилки. МЕТОД ВИКЛЮЧЕНЬ
3.1 БАЗОВИЙ СИНТАКСИС
3.2 ІНСТРУКЦІЯ throw
3.3 РОЗКРУЧУВАННЯ СТЕК
3.4 ВИКЛЮЧЕННЯ і деструктор
3.5 ВИКЛЮЧЕННЯ І set_error_handler ()
3.6 КЛАСИФІКАЦІЯ І СПАДКУВАННЯ
3.7 БАЗОВИЙ КЛАС Exception
3.8 ВИКОРИСТАННЯ ІНТЕРФЕЙСІВ
3.9 БЛОКИ-ФІНАЛІЗАТОРИ
3.9.1 Непідтримувана конструкція try ... finally
3.9.2 "Виділення ресурсу є ініціалізація"
3.9.3 Перехоплення всіх винятків
3.10 ТРАНСФОРМАЦІЯ ПОМИЛОК
3.10.1 Серйозність "несерйозних" помилок
3.10.2 Перетворення помилок у виключення
3.10.3 Код бібліотеки PHP_Exceptionizer
3.10.4 Ієрархія винятків
3.10.5 Фільтрація за типами помилок
3.10.6 Перспективи
ВИСНОВОК
ЛІТЕРАТУРА

ВСТУП
Є думка: "У будь-якій програмі є хоча б одна помилка". На практиці "хоча б одна" означає "багато" або навіть "дуже багато".
Фаза "позбавлення" програми від помилок (фаза налагодження) є найбільш тривалою і трудомісткою. Основне проведення часу програміста (і не тільки) - це боротьба з помилками.
Одна з найсильніших рис РНР - можливість відображення повідомлень про помилки прямо в браузері. Залежно від стану інтерпретатора повідомлення будуть виводитися в браузер або придушуватися.
Для успішної боротьби з помилками потрібно навчитися керувати налаштуваннями РНР, дізнатися про його тонких місцях і про можливості основних директив. Окрему увагу слід приділяти методикам налагодження скриптів, а точніше - обробці повідомлень про помилки та попереджень, які можуть виникнути під час роботи програми, а також висновку стека викликів процедур (подібного до того, що існує в мовах Java і Perl). Слід з обережністю використовувати оператор відключення попереджень про помилки.
Завдання обробки помилок у коді програми - одна з найважливіших і популярних при програмуванні. Для її успішного рішення потрібно уточнити поняття терміна "помилка" і визначити його роль у програмуванні, а також вивчити різні класифікації помилкових ситуацій. Це завдання може бути ефективно вирішена при використанні поняття "виключення" і способів застосування конструкції try ... catch. Використання механізму успадкування та класифікації винятків може сильно скоротити код програми і зробити його універсальним. Існують коди бібліотек, що дозволяють обробляти численні помилки та попередження, що генеруються функціями РНР, як звичайні винятку.
Грамотний перехоплення помилок з самого зародження програмування вважався важким завданням. Механізм обробки виключень, хоча і спрощує її, але все одно залишається дуже складним.

1. КОНТРОЛЬ ПОМИЛОК

Термін "помилка" має три різних значень:
1. Помилкова ситуація - факт наявності помилки в програмі. Це може бути, наприклад, синтаксична помилка (пропущена дужка), або ж помилка семантична - смислова (використання змінної, яка раніше не була визначена).
2. Внутрішнє повідомлення про помилку ("внутрішня помилка"), яку видає РНР у відповідь на різні невірні дії програми (наприклад, відкриття неіснуючого файлу).
У РНР можна встановлювати різні режими відображення помилок, тому факт наявності помилки в програмі в сенсі попереднього пункту далеко не завжди приводить до висновку повідомлення про неї.
3. Користувача повідомлення про помилку ("користувальницька помилка"), до якої зараховуються всі повідомлення або стану, що генеруються й оброблювані самою програмою. Наприклад, в скрипті авторизації ситуація "введено неправильний пароль" - помилка саме такого роду.

1.1 РОЛІ ПОМИЛОК

Внутрішнє повідомлення про помилку означає помилку, яку немає сенсу показувати в браузері користувача. Це необхідно робити на етапі налагодження скрипта, коли в ролі користувача виступає сам розробник. Таке повідомлення краще всього записувати у файли журналу для подальшого аналізу, а в браузер виводити стандартний текст, наприклад: "Сталася внутрішня помилка, інформація про неї буде доступна розробнику скрипта пізніше". Багато програмістів вважають за краще також у кінці сторінки видавати додаткові відомості про помилку, тобто записувати повідомлення і у файл журналу, і виводити на екран. Така практика в більшості випадків допомагає розробнику "на місці" з'ясувати, що ж сталося.
Для запису повідомлень про помилки в журнал в РНР існують спеціальні засоби: директиви log errors, error log, а також функція error log () (докладніше см.п.п. 1.3.2, 1.3.3).
Користувача повідомлення про помилку призначене для відображення користувачеві - звідси і його назва. При виникненні помилкової ситуації такого роду користувач повинен побачити осмислений текст в браузері, а також, можливо, поради, що ж йому тепер робити. Не рекомендується протиставляти користувальницькі помилки внутрішнім - часто вони можуть у якійсь мірі перекриватися. Наприклад, при неможливості з'єднання з SQL-сервером у програмі допустима генерація відразу двох видів повідомлень:
● внутрішнє повідомлення: відповідь SQL-сервера, дата і час помилки, номер рядка в програмі і т. д.;
● користувальницьке повідомлення: наприклад, текст "Помилка з'єднання з SQL-сервером, спробуйте зайти пізніше".

1.2 ВИДИ ПОМИЛОК

У найпростішому випадку інформація про помилку включає в себе текст діагностичного повідомлення, але можуть також уточнюватися і додаткові дані, наприклад, номер рядка та ім'я файлу, де виникла помилкова ситуація. Якщо в програмі виникла помилкова ситуація, необхідно прийняти рішення, що ж у цьому випадку робити. Код, який цим займається (якщо він присутній), називають кодом відновлення після помилки, а запуск цього коду - відновленням після помилки. Розглянемо, наприклад, такий код:
$ F = @ fopen ("spoon.txt", "r");
if (! $ f) return;
У цьому прикладі код відновлення - це інструкція if, яка явно обробляє ситуацію неможливості відкриття файлу. Зверніть увагу, що використовується оператор @ перед fopen (), щоб не отримувати діагностичне повідомлення від самого РНР - воно не потрібне, у нас же власний обробник помилкової ситуації (код відновлення).
У даній термінології діагностичні повідомлення, які видає РНР, також можна назвати кодом відновлення.
Помилки за своєю "серйозності" можна підрозділити на два великі класи:
● серйозні помилки з неможливістю автоматичного відновлення. Наприклад, якщо ви намагаєтеся відкрити неіснуючий файл, то далі обов'язково повинні вказати, що робити, якщо це не вдасться: адже записувати або зчитувати дані з невідкритого файлу не можна;
● несерйозні (нефатального) помилки, відновлення після яких не потрібно, наприклад, попередження (warnings), повідомлення (notices), а також налагодження повідомлення (debug notices). Звичайно у випадку виникнення такого роду помилкових ситуацій немає необхідності робити щось особливе і нестандартне, цілком достатньо просто зберегти де-небудь інформацію про помилку (наприклад, у файлі журналу).
Для серйозних помилок необхідно вручну писати код відновлення та переривати звичайний хід програми, в той час як для помилок несерйозних нічого особливого робити не потрібно.

1.2.1 Несерйозно ПОМИЛКИ

Для обробки нефатальних помилок, після яких не потрібно "персональне" відновлення, в РНР є інструмент, званий установкою обробника помилок (або перехопленням помилок; докладніше див. п. 2).
Метод полягає в тому, що в програмі пишеться спеціальна функція - обробник помилки, яка викликається РНР щоразу, коли настає та чи інша помилкова ситуація. Завдання обробника - зберегти де-небудь інформацію про помилку або ж просто вивести її в браузер, красиво оформивши.

1.2.2 СЕРЙОЗНІ ПОМИЛКИ

Серйозні помилки в загальному випадку неможливо обробити з використанням set_error_handler (), тому що в кожному конкретному випадку потрібно писати "персональний" код відновлення.
У функції-обробнику для відновлення після помилки можна виконати всього лише одну цілком осмислене дія - це завершити програму.
Головне питання при роботі з серйозними помилками - написання коду відновлення. Він повинен мати достатній контроль над ходом виконання програми (наприклад, міг виконувати інструкції return або break, а не тільки лише завершував програму з exit ()).

1.2.2.1 Припинення виконання програми

У разі виникнення серйозної помилки програма завершує роботу з exit () або die ().
Такий метод неприйнятний в коді бібліотек загального користування: адже викликає програма може не чекати, що її виконання може бути перервано через який-небудь дрібниці на зразок неможливість відкриття файлу журналу.

1.2.2.2 Повернення неприпустимого значення

Практично всі стандартні функції РНР у разі виникнення помилкової ситуації повертають false або NULL, а також викликають trigger_error () для фіксування діагностичного повідомлення (його можна потім перехопити за допомогою функції-обробника). Наприклад, функція fopen () при неможливості відкриття файлу повертає false, і ми в подальшому повинні перевірити результат на "істинність".
Цей метод має три недоліки.
● Головний недолік - програміст може забути перевірити повернене функцією значення, і продовжити виконання програми так, ніби нічого не сталося. У більшості випадків це породить лавиноподібне наростання кількості діагностичних повідомлень, що не мають ніякого відношення до суті проблеми.
● Будь-яке значення, що повертається функцією, може бути за змістом допустимим. Наприклад, стандартна функція unserialize () розпаковує деяку змінну з її строкового подання (згенерованого викликом serialize ()) і повертає її початкове значення. Що повинна повернути функція в разі помилки? Якщо, наприклад, NULL, то де гарантія, що дійсно відбулася помилка, а вихідна змінна не містила просто значення NULL до упаковки?
● Код відновлення доводиться постійно дублювати. Наприклад, якщо в програмі потрібно відкрити 10 різних файлів, ми будемо змушені 10 разів перевірити повертається функцією fopen () значення. Легко уявити, як сильно це "роздує" програму. Ще гірше ви себе відчуєте, якщо згадаєте, що в майбутньому може знадобитися модифікувати код відновлення: доведеться робити це в 10 місцях.

1.2.2.3 Ненормальне стан програми

Часто одного лише неприпустимого значення, що повертається виявляється недостатньо для зберігання всієї інформації. Тому на додаток до попереднього способу в РНР іноді застосовується запис інформації про помилку в деяку внутрішню змінну, яку можна буде надалі вважати і проаналізувати іншими функціями.
Так працюють, наприклад, інструменти для доступу до MySQL в PHP: mysql_connect (), mysql_query () і т. д. Ви можете перевірити, чи не повернули чи система "помилкове" значення, а потім використовувати mysql_error () або mysql_errno () для отримання додаткової інформації. Стан програми, в якому колись раніше сталася помилка, що вимагає додаткового аналізу, називають ненормальним. Головний недолік ненормального стану в тому, що якщо раптом відбудеться ще одна помилка, то інформація про неї "затрет" попереднє повідомлення. Таким чином, ми змушені періодично перевіряти, чи не знаходиться програма в ненормальному стані, і робити додаткові дії. Це сильно збільшує код, крім того, програміст може забути вставити в програму необхідні перевірки.

1.2.2.4 Виклик функції-обробника

Цей спосіб застосовують до обробки несерйозних помилок. Для перехоплення серйозних помилкових ситуацій він застосовний мало. Дійсно, функція-обробник має у своєму розпорядженні ті ж самі три альтернативи. Вона не отримує в своє розпорядження ні поточні локальні змінні програми, ні інформацію про те, що слід робити в кожному персональному випадку.

1.3 ДИРЕКТИВИ РНР КОНТРОЛЮ ПОМИЛОК

Рівнем деталізації повідомлень про помилки, а також іншими параметрами управляють директиви РНР, перераховані нижче.

1.3.1 ДИРЕКТИВА error_reporting

error_reporting
● Можливі значення: числова константа (за замовчуванням - E_ALL ~ E_NOTICE.
● Де встановлюється: php.ini,. Htaccess, ini_set ().
Встановлює "рівень строгості" для системи контролю помилок РНР. Значення цього параметра має бути цілим числом, яке інтерпретується як десяткове подання двійкової бітової маски. Встановлені в 1 біти задають, наскільки детальним має бути контроль. Можна також не возитися з битами, а використовувати константи. У табл. 1.1 наведені деякі константи, які на практиці застосовуються найчастіше.
Таблиця 1.1. Біти, керуючі контролем помилок
Біт
Константа PHP
Призначення
1
E_ERROR
Фатальні помилки
2
E_WARNING
Загальні попередження
4
E_PARSE
Помилки трансляції
8
E_NOTICE
Попередження
16
E_CORRE_ ERROR
Глобальні попередження (майже не використовується)
32
E_CORRE_STRING
Глобальні помилки (не використовується)
2048
E_STRICT
Різні "рекомендації" РНР по поліпшенню коду (наприклад, зауваження щодо виклику застарілих функцій)
2047
E_ALL
Всі перераховані прапори, за винятком E_STRICT
Найчастіше зустрічається значення 7 (1 +2 + 4), або, що те ж саме, E_ALL ~ E_NOTICE (тут оператор ~ означає побітове "виключає АБО"). Воно задає повний контроль, крім некритичних попереджень інтерпретатора (таких, наприклад, як звернення до неініціалізованої змінної). Часто це значення задається за замовчуванням при установці РНР.
Якщо ви розробляєте скрипти на РНР, перше, що вам варто зробити, - це встановлювати значення error_reporting рівним E_ALL або навіть E_ALL | E_STRICT, тобто включити абсолютно всі повідомлення про помилки.
Хоча у вже наявних сценаріях (включаючи популярні системи phpBB, phpNuke і т. д.) це, швидше за все, породить цілі легіони найрізноманітніших попереджень, не варто їх лякатися: вони свідчать лише про недостатньо хорошій якості коду.
Режим E_ALL дуже допомагає при налагодженні скриптів. Існують поширені помилки, над якими можна просидіти не одну годину, у той час як з включеним режимом E_ALL вони виявляються протягом декількох хвилин.
Краще за все тримати у файлі php.ini максимально можливий режим контролю помилок-E_ALL або навіть E_ALL E_STRICT, тобто включити абсолютно всі повідомлення про помилки, а у разі крайньої необхідності відключати його в скриптах в персональному порядку. Для цього існує три способи.
Способи відключення режиму контролю помилок:
● використовувати функцію error_reporting (E_ALL ~ E_NOTICE);
● запустити функцію ini_set ("error_reporting", E_ALL ~ E_NOTICE);
● використовувати оператор @ для локального відключення помилок.

1.3.2 ДИРЕКТИВА display_errors

display_errors
log_errors
● Можливі значення: on або off.
● Де встановлюється: php.ini,. Htaccess, iniseto.
Якщо директива display_errors встановлена ​​в значення on, всі повідомлення про помилки та попередження виводяться в браузер користувача, що запустив скрипт.
Якщо ж встановлено параметр log_errors, то повідомлення додатково потрапляють і в файл журналу (див. нижче директиву error_log).
При налагодженні скриптів рекомендується встановлювати display_errors в значення on, тому що це сильно спрощує роботу. І навпаки, якщо скрипт працює на хостингу, повідомлення про помилки небажані - краще їх вимкнути, а замість цього включити log_errors.

1.3.3 ДИРЕКТИВА error_log

error_log
● Можливі значення: абсолютний шлях до файлу (за умовчанням - не заданий).
● Де встановлюється: php.ini,. Htaccess, ini_set ().
У PHP існують два методи виведення повідомлень про помилки: друк помилок в браузер і запис їх у файл журналу (log-файл). Директива error_log задає шлях до журналу.

1.4 УСТАНОВКА РЕЖИМУ ВИВЕДЕННЯ ПОМИЛОК

Для установки режиму виведення помилок під час роботи програми служить функція error_reporting ().
int error_reporting ([int $ level])
Ця функція встановлює "рівень строгості" для системи контролю помилок РНР, тобто величину параметра error_reporting в конфігурації РНР.
Рекомендується першим рядком сценарію ставити виклик:
error_reporting (E_ALL);
При цьому можуть дратувати "дрібні" повідомлення типу "використання неініціалізованої змінної". Практика показує, що ці попередження свідчать (найчастіше) про можливу логічної помилку в програмі, і що при їх відключенні може виникнути ситуація, коли програму буде дуже важко налагодити.

1.5 ОПЕРАТОР ВІДКЛЮЧЕННЯ ПОМИЛОК

Є і ще один аргумент на користь того, щоб завжди включати повний контроль помилок. Це - існування в РНР оператора @. Якщо цей оператор поставити перед будь-яким виразом, то всі помилки, які там виникнуть, будуть проігноровані.
Якщо у виразі використовується результат роботи функції, з якої викликається інша функція і т. д., то попередження будуть заблоковані для кожної з них. Тому обережно застосовуйте @.
Наприклад:
if (! @ filemtime ("notextst.txt"))
echo "Файл не існує!";
Спробуйте прибрати оператор @ - тут же отримаєте повідомлення: "Файл не знайдено", а тільки після цього - висновок оператора echo. Однак з оператором @ попередження буде придушене, що і було потрібно.
У наведеному прикладі, можливо, кілька логічніше було б скористатися функцією file_exists (), яка якраз і призначена для визначення факту існування файлу, але в деяких ситуаціях це не підійде. Наприклад:
/ / Обновити файл, якщо він не існує або дуже старий
if (! file_exists ($ fname) | | filemtime ($ fname) <time () -60 * 60)
myFunctionForUpdateFile ($ fname);
Порівняйте з наступним фрагментом:
/ / Обновити файл, якщо він не існує або дуже старий
if (@ filemtime ($ fname) <time () -60 * 60)
myFunctionForUpdateFile ($ fname);
Завжди пам'ятайте про оператора @. Він зручний. Рекомендується не ризикувати, ставлячи слабкий контроль помилок за допомогою функції error_reporting (), якщо такий контроль і так можна локально встановити за допомогою оператора @?
Оператор відключення помилок @ цінний ще й тим, що він блокує не тільки виведення помилок в браузер, але також і в log-файл. Приклад з лістингу 1.1 ілюструє ситуацію.
Лістинг 1.1. Файл er.php
<? Php # # Відключення помилок: логи не модифікуються.
error_reporting (E_ALL);
ini_set ("error_log", "log.txt");
ini_set ("log_errors", true);
@ Filemtime ("spoon");
?>
Запустивши наведений скрипт, ви помітите, що файл журналу log.txt навіть не створився. Спробуйте тепер прибрати оператор @ - ви-отримаєте попередження "stat failed for spoon", і воно ж запишеться в log.txt.

1.5.1 ПРИКЛАД ВИКОРИСТАННЯ ОПЕРАТОРА @

Нехай є форма з submit-кнопкою, і потрібно в сценарії визначити, чи натиснута вона. Можна зробити це так:
<? Php
if (! empty ($ submit)) echo "Кнопка натиснута!";
...
?>
Погодьтеся, код лістингу 1.2 елегантніше.
Лістинг 1.2. Файл submit.php
<? Php # # Зручність оператора @.
if (@ $ _REQUEST ['submit']) echo "Кнопка натиснута!"
?>
<Form action ="<?=$_ SERVER ['SCRIPT_NAME']?>">
<input type="submit" name="submit" value="Go!">
</ Form>

1.5.2 ПРЕДОСТЕРІЖЕНІЯ ЩОДО ВИКОРИСТАННЯ ОПЕРАТОРА ВІДКЛЮЧЕННЯ ПОМИЛОК @

Оператор @ слід застосовувати з обережністю. Наприклад, наступний код нікуди не годиться - постарайтеся не повторювати його в своїх програмах.
/ / Не пригнічуйте повідомлення про помилки у включаються файлах - інакше
/ / Налагодження перетвориться на справжнє пекло!
@ Include "mistake.php";
/ / Не використовуйте оператор @ перед функціями, написаними на РНР,
/ / Якщо тільки немає 100%-й впевненості в тому, що вони працюють
/ / Коректно в будь-якій ситуації!
@ MyOwnBigFunction ();
Рекомендації, в яких випадках застосування оператора придушення помилок виправдано і відносно безпечно:
● в конструкціях if (@ $ _REQUEST ['key']) для перевірки існування (і ненульового значення) елемента масиву;
● перед стандартними функціями РНР начебто fopen (), filemtime (), mysql_connect () і т. д., якщо далі йде перевірка коду повернення та поява повідомлення про помилку;
● в HTML-файлах зі вставками PHP-коду, якщо дуже ліньки писати багато лапок: <?=@$ resuit [element] [field]?> (Такий виклик не породить помилок, незважаючи на відсутність лапок).
У всіх інших випадках краще кілька разів подумати, перш ніж застосовувати оператор @. Чим менше область коду, в якій він буде діяти, тим більш надійною виявиться програма. Тому не рекомендується використовувати @ перед include - це заблокує перевірку помилок для дуже великого фрагмента програми.

2. Перехоплювати помилки. МЕТОД РЕЄСТРАЦІЇ обробника помилок

У РНР версії 5 існують два методи перехоплення помилок під час виконання програми:
● реєстрація обробника помилки.
● винятків;
РНР підтримує засоби, що дозволяють "перехоплювати" момент виникнення тієї чи іншої помилки (або попередження) і викликати при цьому функцію, написану користувачем.
Метод перехоплення помилок за допомогою призначених для користувача функцій далекий від досконалості. Зробити з повідомленням що-небудь розумне, крім як записати його куди-небудь і завершити програму (при необхідності), не представляється можливим.
Метод виключень, який розглянуто в п. 3, позбавлений цього недоліку, він досить складний і практично не реалізований на рівні стандартних функцій РНР.
Приклад використання обробника помилок приведений в лістингу 2.1.
Лістинг 2.1. Файл handler1.php
<? Php # # Перехоплення помилок і попереджень.
/ / Визначаємо нову функцію-обробник.
function myErrorHandler ($ errno, $ msg, $ file, $ line) {
/ / Якщо використовується @, нічого не робити.
if (error_reporting () == 0) return;
/ / Інакше - виводимо повідомлення.
echo '<div style="border-style:inset; border-width:2">';
echo "Сталася помилка з кодом <b> $ errno </ b>! <br>";
echo "Файл: <tt> $ file </ tt>, рядок $ line. <br>";
echo "Текст помилки: <i> $ msg </ i>";
echo "</ div>";
}
/ / Реєструємо її для всіх типів помилок.
set_error_handler ("myErrorHandler", E_ALL);
/ / Викликаємо функцію для неіснуючого файлу, щоб
/ / Згенерувати попередження, яке буде перехоплено.
filemtime ("spoon");
?>
Що б не сталося, програма завжди продовжує свою роботу - просто в момент виникнення помилкової ситуації викликається функція-обробник, а потім виконання йде далі. Саме з цієї причини обробник не можна назвати кодом відновлення.

2.1 ФУНКЦІЯ set_error_handler

Функція
string set_error_handler (string $ runcName [, int $ errorTypes])
реєструє користувальницький обробник помилок - функцію, яка буде викликана при виникненні повідомлень, зазначених у $ errorTypes типів (бітова маска, наприклад, E_ALL ~ E_NOTICE).
Повідомлення, що не відповідають масці $ errorTypes, будуть у будь-якому випадку оброблятися вбудованими засобами РНР, а не попередньої встановленої функцією-перехоплювачем. Ім'я користувача функції передається в параметрі $ runcName. Якщо до цього був встановлений якийсь інший обробник, функція поверне його ім'я - з тим, щоб його можна було пізніше відновити. Користувальницький обробник повинен ставилося так, як показано в лістингу 2.2.
Лістинг 2.2. Файл handler0.php
<? Php # # Перехоплення помилок і попереджень.
/ / Визначаємо нову функцію-обробник.
function myErrorHandler ($ errno, $ msg, $ file, $ line) {
echo '<div style="border-style:inset; border-width:2">';
echo "Сталася помилка з кодом <b> $ errno </ b>! <br>";
echo "Файл: <tt> $ file </ tt>, рядок $ line. <br>";
echo "Текст помилки: <i> $ msg </ i>";
echo "</ div>";
}
/ / Реєструємо її для всіх типів помилок.
set_error_handler ("myErrorHandler", E_ALL);
/ / Викликаємо функцію для неіснуючого файлу, щоб
/ / Згенерувати попередження, яке буде перехоплено.
filemtime ("spoon");
?>
Тепер при виникненні будь-якої помилки або навіть попередження в програмі буде викликана функція myErrorHandier (). Її аргументи отримають значення, відповідно, номера яких, тексту помилки, імені файлу і номера рядка, в якій було створене повідомлення.
На жаль, не всі типи помилок можуть бути перехоплені таким чином. Наприклад, помилки трансляції у внутрішнє представлення E_PARSE, а також E_ERROR негайно завершують роботу програми. Виклики функції die () також не перехоплюються.
Призначити функцію реакції на E_PARSE і E_ERROR все ж можна. Справа в тому, що перехоплювач вихідного потоку скрипта, встановлюваний функцією ob__start (), обов'язково викликається при завершенні роботи програми, у тому числі в разі фатальної помилки. Звичайно, йому не передається повідомлення про помилку та її код; він повинен сам отримати ці дані з "хвоста" вихідного потоку (наприклад, використовуючи функції для роботи з регулярними виразами).
У разі якщо користувальницький обробник повертає значення false (і тільки його!), Вважається, що помилка не була оброблена, і управління передається стандартному оброблювачу РНР (зазвичай він виводить текст помилки в браузер). Всі інші повернені значення (включаючи навіть null або, що те ж саме, у разі, якщо оператора return взагалі немає), призводять до придушення запуску стандартної процедури обробки помилок.

2.2 ФУНКЦІЯ restore_error_handler ()

void restore_error_handler ()
Коли викликається функція set_error_handler (), попереднє ім'я користувача функції запам'ятовується в спеціальному внутрішньому стеку РНР. Щоб витягти це ім'я і тут же його встановити як обробника, застосовується функція restore_error_handler (). Приклад:
/ / Реєструємо обробник для всіх типів помилок.
set_error_handler ("myErrorHandler", E_ALL);
/ / Включаємо підозрілий файл.
include "suspicious_file.php";
/ / Відновлюємо попередній обробник.
restore_error_handler ();
Необхідно стежити, щоб кількість викликів restore_error_handler () було в точності дорівнює числу викликів set_error_handler (). Не можна відновити те, чого немає.

2.3 ПРОБЛЕМИ З ОПЕРАТОРОМ @

Користувацька функція перехоплення помилок викликається незалежно від того, чи був використаний оператор придушення помилок у момент генерації попередження. Це дуже незручно: поставивши оператор @ перед викликом filemtime (), ми побачимо, що результат роботи скрипта анітрохи не змінився: текст попередження як і раніше виводиться в браузер.
Для того щоб вирішити проблему, скористаємося корисним властивістю оператора @: на час свого виконання він встановлює error_reporting, рівним нулю. Значить, ми можемо визначити, чи був викликаний оператор @ у момент "спрацьовування" обробника, за нульовим значенням функції error_reporting () (при виклику без параметрів вона повертає поточний рівень помилок) - лістинг 2.3.
Лістинг 2.3. Файл handler.php
<? Php # # Перехоплення помилок і попереджень.
/ / Визначаємо нову функцію-обробник.
function myErrorHandler ($ errno, $ msg, $ file, $ line) {
/ / Якщо використовується @, нічого не робити.
if (error_reporting () == 0) return;
/ / Інакше - виводимо повідомлення.
echo '<div style="border-style:inset; border-width:2">';
echo "Сталася помилка з кодом <b> $ errno </ b>! <br>";
echo "Файл: <tt> $ file </ tt>, рядок $ line. <br>";
echo "Текст помилки: <i> $ msg </ i>";
echo "</ div>";
}
/ / Реєструємо її для всіх типів помилок.
set_error_handler ("myErrorHandler", E_ALL);
/ / Викликаємо функцію для неіснуючого файлу, щоб
/ / Згенерувати попередження, яке буде перехоплено.
@ Filemtime ("spoon");
?>
Тепер все працює коректно: попередження не відображається в браузері, оскільки застосований оператор @.

2.4 ГЕНЕРАЦІЯ ПОМИЛОК

Функція
void trigger_error (string $ message [, int $ type])
призначена для штучної генерації повідомлення про помилки з вказаним типом. У РНР існує кілька констант, чиї імена починаються з E_USER_, які можна використовувати нарівні з E_ERROR, E_WARNING і т. д., але тільки для персональних потреб. Саме з функцією trigger_error () вони найчастіше і застосовуються. Ось ці константи:
E_ERROR
E_WARNING
E_USER_NOTICE
Як їх використовувати - цілком залежить від програміста: ніяких обмежень не накладається.
int error_log (string $ msg [, int $ type = O] [, string $ dest] [, string $ extra_headers])
Вище ми розглядали директиви log_errors і error_log, які змушують РНР записувати діагностичні повідомлення в файл, а не тільки виводити їх в браузер. Функція error_log за своїм змістом схожа на trigger_error (), однак, вона змушує інтерпретатор записати деякий текст ($ msg) у файл журналу (при нульовому $ type і за замовчуванням), заданий в директиві error_log. Основні значення аргументу $ type, які може приймати функція, перераховані нижче:
● $ type = = 0
Записує повідомлення в системний файл журналу або у файл, заданий в директиві error_log.
● $ type = = 1
Відправляє повідомлення поштою адресату, чия адреса вказана в $ dest. При цьому $ extra_headers використовується в якості додаткових поштових заголовків.
● $ type == 3
Повідомлення додається в кінець файлу з ім'ям $ dest.

2.5 СТЕК ВИКЛИКІВ ФУНКЦІЙ

У РНР версії 4.3.0 і старше існує можливість простежити весь ланцюжок викликів функцій, починаючи від головної програми і закінчуючи поточної виконуваної процедурою.
Функція
list debug_backtrace ()
повертає великий список, в якому міститься інформація про "батьківських" функціях і їхніх аргументах. Результат роботи лістингу 2.4 говорить сам за себе.
Лістінг2.4. Файл trace.php
<? Php # # Висновок дерева викликів функції.
function inner ($ a) {
/ / Внутрішня функція.
echo "<div>"; print_r (debug_backtrace ()); echo "</ div>";
}
function outer ($ x) {
/ / Батьківська функція.
inner ($ x * $ x);
}
/ / Головна програма.
outer (3);
?>
Після запуску цього скрипта буде надрукований приблизно наступний результат (його трохи стиснули):
Array (
[0] => Array (
[File] => z: \ home \ book \ original \ src \ interdivter \ trace.php
[Line] => 6
[Function] => inner
[Args] => Array ([0] => 9)
)
[1] => Array (
[File] => z: \ home \ book \ original \ src \ interdivter \ trace.php
[Line] => 8
[Function] => outer
[Args] => Array ([0] => 3)
)
)
Як бачите, в масиві опинилася вся інформація про проміжні викликах функцій.
Функцію зручно застосовувати, наприклад, в призначеному для користувача обробнику помилок. Тоді можна відразу ж визначити, в якому місці було згенеровано повідомлення і які виклики до цього привели, У великих програмах рівень вкладеності функцій може досягати декількох десятків, тому, напевно, замість print_r () варто написати свій власний код для виведення дерева викликів у більш компактній формі.

2.6 ПРИМУСОВЕ ЗАВЕРШЕННЯ ПРОГРАМИ

Функція
void exit ()
негайно завершує роботу сценарію. З неї ніколи не відбувається повернення. Перед закінченням програми викликаються функції-фіналізатори.
void die (string $ message)
Функція робить майже те ж саме, що і exit (), тільки перед завершенням роботи виводить рядок, задану в параметрі $ message). Найчастіше її застосовують, якщо потрібно надрукувати повідомлення про помилку та аварійно завершити програму.
Корисним прикладом використання die () може служити такий код:
$ Filename = '/ path / to / data-file';
$ File = @ fopen ($ filename, 'r')
or die ("не можу відкрити файл $ filename!");
Тут ми орієнтуємося на специфіку оператора or - "виконувати" другий операнд тільки тоді, коли перший "хибна". Зауважте, що оператор | | тут застосовувати не можна - він має більш високий пріоритет, ніж =.
З використанням | | останній приклад потрібно було б переписати таким чином:
$ Filename = '/ path / to / data-file';
($ File = fopen ($ filename, 'r'))
| | Die ("не можу відкрити файл $ filename!");
Погодьтеся, громіздкість останнього прикладу практично повністю виключає можливість застосування | | в подібних конструкціях.

2.7 ФІНАЛІЗАТОРИ

Розробники РНР передбачили можливість вказати в програмі функцію-фіналізатор, яка буде автоматично викликана, як тільки робота сценарію завершиться - неважливо, з-за помилки або легально. У такій функції ми можемо, наприклад, записати інформацію в кеш або оновити який-небудь файл журналу роботи програми. Що ж потрібно для цього зробити?
По-перше, написати саму функцію і дати їй будь-яке ім'я. Бажано також, щоб вона була невеликою і в ній не було помилок, тому що сама функція, цілком можливо, буде викликатися перед завершенням сценарію через помилки.
По-друге, зареєструвати її як фіналізатор, передавши її ім'я стандартної функції register_shutdown_function ().
int register_shutdown_function (string $ func)
Реєструє функцію із зазначеним ім'ям з тією метою, щоб вона автоматично викликалася перед поверненням з сценарію. Функція буде викликана як при закінченні програми, так і при викликах exit () або die (), а також при фатальних помилках, що призводять до завершення сценарію - наприклад, при синтаксичній помилці.
Звичайно, можна зареєструвати кілька фінальних функцій, які будуть викликатися в тому ж порядку, в якому вони реєструвалися.
Правда, є одне "але". Фінальна функція викликається вже після закриття з'єднання з браузером клієнта. Тому всі дані, виведені в ній через echo, втрачаються (у всякому разі, так відбувається в Unix-версії РНР, а під Windows CGI-версія РНР і echo працюють чудово). Так що краще не виводити ніяких даних в такій функції, а обмежитися роботою з файлами та іншими викликами, які нічого не направляють в браузер.
Остання обставина, на жаль, обмежує функціональність фіналізаторов: їм не можна доручити, наприклад, висновок закінчення сторінки, якщо сценарій з якихось причин перервався через помилки. Взагалі кажучи, треба зауважити, що в РНР ніяк не можна в злучці помилки в деякому занедбаному коді виконати будь-які розумні дії (крім, зрозуміло, миттєвого виходу).

3. Перехоплювати помилки. МЕТОД ВИКЛЮЧЕНЬ

Механізм обробки виключень або просто "виключення" (exceptions) - це технологія, що дозволяє писати код відновлення після серйозної помилки в зручному для програміста вигляді. Із застосуванням винятків перехоплення і обробка помилок, найбільш слабка частина в більшості програмних систем, значно спрощується.
Концепція винятків базується на загальній ідеї об'єктно-орієнтованого програмування: дані повинні оброблятися в тій ділянці програми, який має максимум відомостей про те, як це робити. Якщо в деякому місці ще не до кінця відомо, яке саме перетворення повинно бути виконано, краще відкласти роботу "на потім". З використанням винятків код обробки помилки явно відділяється від коду, в якому помилка може виникнути.
Винятки також дозволяють зручно передавати інформацію про виниклу помилку вниз по дереву (стеку) викликів функцій. Таким чином, код відновлення може знаходитися навіть не в поточній процедурі, а в тій, що її викликає.

3.1 БАЗОВИЙ СИНТАКСИС

Виняток - це деяке повідомлення про помилку виду "серйозна". При своїй генерації воно автоматично передається в ділянку програми, який краще всього "обізнаний", що ж слід зробити в даній конкретній ситуації. Ця ділянка називається обробником винятку.
Будь-яке виключення в програмі представляє собою об'єкт деякого класу, створюваний, як зазвичай, оператором new. Цей об'єкт може містити різну інформацію, наприклад, текст діагностичного повідомлення, а також номер рядка та ім'я файлу, у яких відбулася генерація винятку. Припустимо додавати і будь-які інші параметри.
Перш ніж іти далі, давайте розглянемо найпростіший приклад виклику обробника (лістинг 3.1). Заодно одержимо уявлення про синтаксис винятків.
Лістінг.3.1. Файл simple.php
<? Php # # Простий приклад використання винятків.
echo "Початок програми. <br>";
try {
/ / Код, в якому перехоплюються винятку.
echo "Все, що має початок ... <br>";
/ / Генеруємо ("викидаємо") виняток.
throw new Exception ("Hello!");
echo "... має і кінець. <br>";
} Catch (Exception $ e) {
/ / Код обробника.
echo "Виключення: {$ e-> getMessage ()} <br>";
}
echo "Кінець програми. <br>";
?>
У лістингу 3.1 приведений приклад базового синтаксису конструкції try ... catch, що застосовується для роботи з винятками.
Розглянемо цю інструкцію докладніше:
● Код обробника виключення поміщається в блок інструкції catch (у перекладі з англійської - "ловити").
● Блок try (у перекладі з англійської - "спробувати") використовується для того, щоб вказати в програмі область перехоплення. Будь-які винятки, згенеровані всередині неї (і лише вони), будуть передані відповідним обробнику.
● Інструкція throw використовується для генерації виключення. Генерацію також називають порушенням або навіть викиданням (або "вкиданням") вилучення (від англ. Throw - кидати). Як було відмічено раніше, будь-який виняток представляє собою звичайний об'єкт РНР, який ми і створюємо в операторі new.
● Зверніть увагу на аргумент блоку catch. У ньому зазначено, в яку змінну повинен бути записаний "спійманий" об'єкт-виключення перед запуском коду обробника. Також обов'язково задається тип винятку - ім'я класу. Оброблювач буде викликаний тільки для тих об'єктів-винятків, які сумісні з вказаним типом (наприклад, для об'єктів даного типу).
Робота інструкції try ... catch полягає в тому, що одна частина програми "кидає" (throw) виключення, а інша - його "ловить" (catch).

3.2 ІНСТРУКЦІЯ throw

Інструкція throw не просто генерує об'єкт-виняток і передає його оброблювачу блоку catch. Вона також негайно завершує роботу поточного try-блоку. Саме тому результат роботи сценарію з лістингу 3.1 виглядає так:
Початок програми.
Все, що має початок ...
Виключення: Hello!
Кінець програми.
Як бачите, за рахунок особливості інструкції throw наша програма піддає серйозного скепсису тезу "Все, що має початок, має й кінець" - вона просто не виводить закінчення фрази.
У цьому відношенні інструкція throw дуже схожа на інструкції return, break і continue: вони теж приводять до негайного завершення роботи поточної функції чи ітерації циклу.

3.3 РОЗКРУЧУВАННЯ СТЕК

Найважливішою і корисною особливістю інструкції throw є те, що її можна використовувати не тільки безпосередньо в try-блоці, а й всередині будь-якої функції, яка звідти викликається. При цьому проводиться вихід не тільки з функції, що містить throw, але також і з усіх проміжних процедур. Приклад - в лістингу 3.2.
Лістінг3.2.Файл stack.php
<? Php # # Інструкція try у вкладених функціях.
echo "Початок програми. <br>";
try {
echo "Початок try-блоку. <br>";
outer ();
echo "Кінець try-блоку. <br>";
} Catch (Exception $ e) {
echo "Виключення: {$ e-> getMessage ()} <br>";
}
echo "Кінець програми. <br>";
function outer () {
echo "Увійшли у функцію". __METHOD__. "<br>";
inner ();
echo "Вийшли з функції". __METHOD__. "<br>";
}
function inner () {
echo "Увійшли у функцію". __METHOD__. "<br>";
throw new Exception ("Hello!");
echo "Вийшли з функції". __METHOD__. "<br>";
}
?>
Результат роботи даного коду виглядає так:
Початок програми.
Початок try-блоку.
Увійшли у функцію outer
Увійшли у функцію inner
Виключення: Hello!
Кінець програми.
Ми переконуємося, що жоден з операторів echo, що викликаються після інструкції throw, не "спрацював". По суті, програма навіть не дійшла до них: управління було миттєво передано в catch-блок, а після цього - в наступну за try ... catch рядок програми.
Дане поведінка інструкції throw називають розкручуванням стека викликів функцій, тому що об'єкт-виключення послідовно передається з однієї функції до іншої, щоразу приводячи до її завершення - як би "відмотуємо" стік.
Можна помітити, що інструкція throw дуже схожа на команду return, проте вона викликає "виліт" потоку виконання не тільки з поточної функції, але також і з тих, які її викликали (до найближчого відповідного catch-блоку).

3.4 ВИКЛЮЧЕННЯ і деструктор

Деструктор будь-якого об'єкту викликається кожного разу, коли остання посилання на цей об'єкт виявляється втраченою, наприклад, програма виходить за межу області видимості змінної. Стосовно до механізму обробки винятків це дає потужний інструмент - коректне знищення всіх об'єктів, створених до виклику throw. Лістинг 3.3 ілюструє ситуацію.
Лістинг 3.3. Файл destr.php
<? Php # # Деструктори і виключення.
/ / Клас, коментує операції зі своїм об'єктом.
class Orator {
private $ name;
function __construct ($ name) {
$ This-> name = $ name;
echo "Створено об'єкт {$ this-> name}. <br>";
}
function __destruct () {
echo "Знищений об'єкт {$ this-> name}. <br>";
}
}
function outer () {
$ Obj = new Orator (__METHOD__);
inner ();
}
function inner () {
$ Obj = new Orator (__METHOD__);
echo "Увага, вкидання! <br>";
throw new Exception ("Hello!");
}
/ / Основна програма.
echo "Початок програми. <br>";
try {
echo "Початок try-блоку. <br>";
outer ();
echo "Кінець try-блоку. <br>";
} Catch (Exception $ e) {
echo "Виключення: {$ e-> getMessage ()} <br>";
}
echo "Кінець програми. <br>";
?>
Створено спеціальний клас, який виводить на екран діагностичні повідомлення у своєму конструкторі і деструкції. Об'єкти цього класу створюються в першому рядку кожної функції.
Результат роботи програми виглядає так:
Початок програми.
Початок try-блоку.
Створений об'єкт outer.
Створений об'єкт inner.
Увага, вкидання!
Знищений об'єкт inner.
Знищений об'єкт outer.
Виключення: Hello!
Кінець програми.
При виклику throw спочатку стався коректний вихід із вкладених функцій (зі знищенням всіх локальних об'єктів і викликом деструкторів), і вже тільки після цього запустився catch-обробник. Дане поведінка також називають розкручуванням стека.

3.5 ВИКЛЮЧЕННЯ І set_error_handler ()

У п.2 розглядали підхід до обробки нефатальних помилок, а саме встановлення функції-обробника за допомогою виклику функції set_error_handler (). У РНР версії 4 він був єдино допустимим методом.
Функція-обробник має один величезний недолік: у ній невідомо точно, що ж слід зробити у разі виникнення помилки.
Порівняємо явно механізм обробки виключень і метод перехоплення помилок. Розглянемо приклад, схожий на скрипт з лістингу 3.1, ілюструє суть проблеми (лістинг 3.4).
Лістинг 3.4. Файл seh.php
<? Php # # Недоліки set_error_handler ().
echo "Початок програми. <br>";
set_error_handler ("handler");
{
/ / Код, в якому перехоплюються винятку.
echo "Все, що має початок ... <br>";
/ / Генеруємо ("викидаємо") виняток.
trigger_error ("Hello!");
echo "... має і кінець. <br>";
}
echo "Кінець програми. <br>";
/ / Функція-обробник.
function handler ($ num, $ str) {
/ / Код обробника.
echo "Помилка: $ str <br>";
/ / Exit ();
}
?>
Перше, що кидається в очі, - це зайва багатослівність коду. Але давайте підемо далі і подивимося, який результат видає дана програма:
Початок програми.
Все, що має початок ...
Помилка: Hello!
За рахунок використання exit () у функції handler () нова програма не тільки піддає сумніву відому тезу (див. оператори echo), але також і стверджує, що будь-яка, навіть найменша, помилка є фатальною.
Що ж, раз проблема в команді exit (), спробуємо її прибрати з скрипта і побачимо наступний результат:
Початок програми.
Все, що має початок ...
Помилка: Hello!
... Має і кінець.
Кінець програми.
І знову ми отримали не те, що потрібно: помилка тепер вже не є "занадто фатальною", як раніше, у неї протилежна проблема: вона, навпаки, недостатньо фатальна.
Ми-то хотіли руйнувати ідіому про кінцівки за все, що має початок, а отримали - просто боязке зауваження, вимовлене пошепки з-за лаштунків.

3.6 КЛАСИФІКАЦІЯ І СПАДКУВАННЯ

Зазвичай всі серйозні помилки в програмі (і внутрішні, і призначені для користувача) піддаються деякої класифікації. Механізм обробки винятків, крім основного свого достоїнства - можливість відокремлення коду обробки помилки від коду її генерації - має і ще один плюс. Він полягає в можливості перехоплення виключень по їх видової належності на основі ієрархії класів. При цьому кожен виняток може належати одночасно кільком видам, і перехоплюватися з урахуванням збіги свого (або батьківського) виду.
Лістинг 3.5 ілюструє той факт, що під час перехоплення виключень використовується інформація про спадкування класів-виключень.
Лістинг 3.5. Файл inherit.php
<? Php # # Успадкування винятків.
/ / Виняток - помилка файлових операцій.
class FilesystemException extends Exception {
private $ name;
public function __construct ($ name) {
parent:: __construct ($ name);
$ This-> name = $ name;
}
public function getName () {return $ this-> name;}
}
/ / Виняток - файл не знайдений.
class FileNotFoundException extends FilesystemException {}
/ / Виняток - Помилка запису у файл.
class FileWriteException extends FilesystemException {}
try {
/ / Генеруємо виняток типу FileNotFoundException.
if (! file_exists ("spoon"))
throw new FileNotFoundException ("spoon");
} Catch (FilesystemException $ e) {
/ / Ловимо БУДЬ файлове виняток!
echo "Помилка при роботі з файлом '{$ e-> getName ()}'.< br>";
} Catch (Exception $ e) {
/ / Ловимо всі інші винятки, які ще не спіймали.
echo "Інше виняток: {$ e-> getDirName ()}.< br>";
}
?>
У програмі ми генеруємо помилку типу FileNotFoundException, однак, нижче перехоплюємо виняток не прямо цього класу, а його "батька" - FilesystemException. Так як будь-який об'єкт типу FileNotFoundException є також і об'єктом класу FilesystemException, блок catch "спрацьовує" для нього. Крім того, на всяк випадок ми використовуємо блок "упіймання" об'єктів класу Exception - "родоначальника" всіх винятків. Якщо раптом у програмі відбудеться виняток іншого типу (обов'язково похідного від Exception), воно також буде опрацьовано.
На жаль, в сучасній версії РНР реалізація винятками інтерфейсів (а отже, і множинна класифікація) не підтримується. Точніше, можна створити клас-виключення, наследующий деякий інтерфейс, але спроба перехопити сгенерированное виняток на ім'я його інтерфейсу (а не по імені класу) не дасть результату. Є підстави сподіватися, що в майбутніх версіях РНР дане незручність буде усунено.

3.7 БАЗОВИЙ КЛАС Exception

РНР останніх версій не допускає використання об'єктів довільного типу в якості винятків. Якщо ви створюєте свій власний клас-виключення, то повинні успадкувати його від вбудованого типу Exception.
До цих пір ми користувалися тільки стандартним класом Exception, не визначаючи від нього похідних. Справа в тому, що даний клас вже містить досить багато корисних методів (наприклад, getMessage ()), які можна застосовувати в програмі.
Отже, кожен клас-виключення в лістингу 3.5 успадковує вбудований в РНР тип Exception. У цьому типі є багато корисних методів і властивостей, які ми зараз перерахуємо (наведено інтерфейс класу):
class Exception {
protected $ message; / / текстове повідомлення
protected $ code; / / числовий код
protected $ file; / / ім'я файлу, де створено виняток
protected $ line; / / номер рядка, де створено об'єкт
private $ trace; / / стек викликів
public function__construct ([string $ message] [, int $ code]);
public final function getMessageО; / / повертає $ this-> message
public final function getCode {); / / повертає $ this-> code
public final function getFileO; / / повертає $ this-> file
public final function getLine (); / / повертає $ this-> line
public final function getTrace ();
public final function getTraceAsStringO;
public function __toStringO;
}
Як бачите, кожен об'єкт-виключення зберігає в собі багато різних даних, заблокованих для прямого доступу (protected і private). Втім, їх все можна отримати за допомогою відповідних методів.
Ми не будемо детально розглядати всі методи класу Exception, тому що більшість з них виконують цілком очевидні дії, які випливають з їхніх назв. Зупинимося лише на деяких. Зверніть увагу, що більшість методів визначено як final, а значить, їх не можна перевизначати в похідних класах.
Конструктор класу приймає два необов'язкових аргументу, які він записує у відповідні властивості об'єкта. Він також заповнює властивості $ fiie, $ line і $ trace, відповідно, ім'ям файлу, номером рядка і результатом виклику функції debug_backtrace () (інформацію про функції, що викликали дану, див п. 2).
Стек викликів, збережений у властивості $ trace, являє собою список з іменами функцій інформацією про них), які викликали поточну процедуру перед генерацією винятку. Дана інформація корисна при налагодженні скрипта і може бути отримана за допомогою методу getTrace (). Додатковий метод getTraceAsString () повертає те ж саме, але в строковому поданні.
Оператор перетворення в рядок _toString () видає всю інформацію, збережену в об'єкті-виключення. При цьому використовуються всі властивості об'єкта, а також викликається getTraceAsString () для перетворення стека викликів в рядок. Результат, який генерує метод, досить цікавий (лістінг.3.6).
Лістинг 3.6. Файл tostring.php
<? Php # # Висновок відомостей про його виключення.
function test ($ n) {
$ E = new Exception ("bang-bang # $ n!");
echo "<div>", $ e, "</ div>";
}
function outer () {test (101);}
outer ();
?>
Виведений текст буде приблизно таким:
exception 'Exception' with message 'bang-bang # 101!' in tostring.php: 3
Stack trace:
# 0 tostring.php (6): test (101)
# 1 tostring.php {7): outer ()
# 2 (main)

3.8 ВИКОРИСТАННЯ ІНТЕРФЕЙСІВ

У РНР підтримується тільки одиночне наслідування класів: в одного і того ж типу не може бути відразу двох "предків". Застосування інтерфейсів дає можливість реалізувати множинну класифікацію - віднести певний клас не до одного, а відразу до кількох можливим типам.
Множинна класифікація виявляється як не можна до речі при роботі з винятками. З використанням інтерфейсів ви можете створювати нові класи-виключення, вказуючи їм не одного, а відразу декількох "предків" (і, таким чином, класифікуючи їх за типами).
Використання інтерфейсів разом з винятками можливо, починаючи з РНР 5.0.1.
Припустимо, у нас в програмі можуть виникати серйозні помилки наступних основних видів:
● внутрішні: детальна інформація в браузері не відображається, але записується у файл журналу. Внутрішні помилки додатково поділяються на:
• файлові (помилка відкриття, читання або запису у файл);
• мережні (наприклад, неможливість з'єднання з сервером);
● користувача: повідомлення видаються прямо в браузер.
Для класифікації сутностей у програмі зручно використовувати інтерфейси. Давайте так і зробимо по відношенню до об'єктів-виключень (лістинг 3.7).
Лістинг 3.7. Файл iface / interfaces.php
<? Php # # Класифікація винятків.
interface IException {}
interface IInternalException extends IException {}
interface IFileException extends IInternalException {}
interface INetException extends IInternalException {}
interface IUserException extends IException {}
?>
Зверніть увагу, що інтерфейси не містять жодного методу і властивості, а використовуються тільки для побудови дерева класифікації.
Тепер, якщо в програмі є деякий об'єкт-виключення, чий клас реалізує інтерфейс INetException, ми також зможемо переконатися, що він реалізує і інтерфейс IInternalException:
if ($ obj instanceof IlnternalException) echo "Це внутрішня помилка.";
Крім того, якщо ми будемо використовувати конструкцію catch (InternalException ...), то зможемо перехопити будь-яке з винятків, що реалізують інтерфейси IFileException і INetException.
Ми також "про всяк випадок" задаємо одного загального предка у всіх інтерфейсів - lException. Взагалі кажучи, це робити не обов'язково.
Інтерфейси, звичайно, не можуть існувати самі по собі, і ми не можемо створювати об'єкти типів IFileException (наприклад) безпосередньо. Необхідно визначити класи, які будуть реалізовувати наші інтерфейси (лістинг 3.8).
Лістинг 3.8. Файл iface / exceptions.php
<? Php # # Класи-виключення.
require_once "interfaces.php";
/ / Помилка: файл не знайдений.
class FileNotFoundException extends Exception
implements IFileException {}
/ / Помилка: помилка доступу до сокета.
class SocketException extends Exception
implements INetException {}
/ / Помилка: неправильний пароль користувача.
class WrongPassException extends Exception
implements IUserException {}
/ / Помилка: неможливо записати дані на мережевий принтер.
class NetPrinterWriteException extends Exception
implements IFileException, INetException {}
/ / Помилка: неможливо з'єднатися з SQL-сервером.
class SqlConnectException extends Exception
implements IInternalException, IUserException {}
?>
Зверніть увагу на те, що виключення типу NetPrinterWriteException реалізує відразу два інтерфейси. Таким чином, воно може одночасно трактуватися і як файлове, і як мережеве виняток, і перехоплюватися як конструкцією catch (IFileException ...), так і catch (InetException ...).
За рахунок того, що всі класи-виключення обов'язково повинні наслідувати базовий тип Exception, ми можемо, як зазвичай, перевірити, чи є змінна об'єктом-винятком, або вона має якийсь інший тип:
if ($ obj instanceof Exception) echo "Це об'єкт-виняток.";
Розглянемо тепер приклад коду, який використовує наведені вище класи (лістінг3.9).
Лістинг 3.9. Файл iface / test.php
<? Php # # Використання ієрархії винятків.
require_once "exceptions.php";
try {
printDocument ();
} Catch (IFileException $ e) {
/ / Перехоплюємо тільки файлові винятку.
echo "Файлова помилка: {$ e-> getMessage ()}.< br>";
} Catch (Exception $ e) {
/ / Перехоплення всіх інших винятків.
echo "Невідоме виняток: <div>", $ e, "</ div>";
}
function printDocument () {
$ Printer = "//./ printer ";
/ / Генеруємо виключення типів IFileException і INetException.
if (! file_exists ($ printer))
throw new NetPrinterWriteException ($ printer);
}
?>
Результатом роботи цієї програми (у разі помилки) буде рядок:
Помилка запису у файл / /. / Printer.

3.9 БЛОКИ-ФІНАЛІЗАТОРИ

Як ми знаємо, інструкція throw змушує програму негайно покинути охоплює try-блок, навіть якщо при цьому буде необхідно вийти з декількох проміжних функцій (і навіть вкладених try-блоків, якщо вони є). Такий "несподіваний" вихід іноді виявляється небажаним, і програміст хоче написати код - фіналізатор, який би виконувався, наприклад, при завершенні функції в будь-якому випадку - незалежно від того, як саме був здійснений вихід з блоку.

3.9.1 Непідтримувана конструкція try ... finally

У мовах програмування Java і Delphi для реалізації коду-фіналізатора є дуже зручна конструкція try ... finally, покликана гарантувати виконання деяких дій у разі виникнення виключення або раптового завершення функції по return. На РНР це можна було б записати так:
function eatThis () {throw new Exception ("bang-bang!");} function hello () {
echo "Все, що має початок,";
try {
eatThis ();
} Finally {
echo "має і кінець.";
}
echo "this never prints!";}
/ / Викликаємо функцію, hello ();
Семантика інструкції try ... finally повинна бути зрозуміла: вона гарантує виконання finally-блоку, навіть якщо раптово буде здійснений вихід з try-блоку.
На жаль, Zend Engine 2, на якій побудований РНР 5, поки не підтримує конструкцію try ... finally, так що наведений вище код, швидше за все, відмовиться працювати. Чому "швидше за все"? Та тому, що є всі підстави вважати, що рано чи пізно інструкція finally в РНР з'явиться, оскільки вона дуже зручна. Можливо, що інструкція finally вже з'явилася.

3.9.2 "Виділення ресурсу є ініціалізація"

Як же бути у випадку, якщо нам потрібно написати код, який буде обов'язково виконаний при завершенні роботи функції? Єдина на даний момент можливість домогтися цього - приміщення такого коду в деструктор деякого класу і створення об'єкта цього класу безпосередньо у функції. Ми знаємо, що при виході з процедури РНР автоматично знищує всі змінні-посилання, створені всередині тіла процедури. Відповідно, якщо посилання на об'єкт буде єдиною, то викличеться деструктор його класу. У лістингу 3.3 ми вже розглядали такий підхід.
Відповідно до термінологією Страуструпа даний підхід називають "виділення ресурсу є ініціалізація". Це пояснюється ось чим: зазвичай у finally-блоках програми проводиться "звільнення" деяких об'єктів-ресурсів, "виділених" до моменту виникнення виключення. Виклик конструктора об'єкту - це його ініціалізація.
Якщо роботу з будь-якими ресурсами в програмі реалізувати через об'єкти, то необхідність у finally-блоках просто не виникне. Справді, програма буде сама стежити, коли потрібно звільнити той чи інший ресурс (викликати деструктор відповідного об'єкта), і нам не доведеться замислюватися про явне написанні коду звільнення.

3.9.3 Перехоплення всіх винятків

Оскільки будь-який клас-виключення довільний від класу Exception, ми можемо написати один-єдиний блок-обробник для всіх можливих винятків в програмі:
echo "Початок програми. <br>";
try {
eatThis ();
}
catch (Exception $ e)
{
echo "Неперехваченное виняток:", $ e;
}
echo "Кінець програми. <br>";
Таким чином, якщо у функції eatThis () виникне будь-яка виняткова ситуація, і об'єкт-виключення "вийде" за її межі (тобто не буде перехоплений всередині самої процедури), спрацює наш універсальний код відновлення (оператор echo).
Перехоплення всіх винятків за допомогою конструкції catch (Exception ...) дозволяє нам убезпечитися від несподіваного завершення роботи функції (або блоку) і гарантувати виконання деякого коду в разі виникнення виключення. У цьому відношенні конструкція дуже схожа на інструкцію finally, якої в РНР на даний момент немає.
На жаль, несподівані виклики return у функції при цьому не обробляються, і відстежити їх поки не можна.
Розглянемо приклад функції, яку ми намагалися написати вище з використанням try ... finally. Фактично, лістинг 3.10 ілюструє, як можна проемуліровать finally у програмі на РНР.
Лістинг 3.10. Файл catchall.php
<? Php # # Перехоплення всіх винятків.
/ / Користувача виняток.
class HeadshotException extends Exception {}
/ / Функція, що генерує виняток.
function eatThis () {throw new HeadshotException ("bang-bang!");}
/ / Функція з кодом-фіналізатором.
function action () {
echo "Все, що має початок,";
try {
/ / Увага, небезпечний момент!
eatThis ();
} Catch (Exception $ e) {
/ / Ловимо БУДЬ виняток, виводимо текст ...
echo "має і кінець. <br>";
/ / ... А потім передаємо це виняток далі.
throw $ e;
}
}
try {
/ / Викликаємо функцію.
action ();
} Catch (HeadshotException $ e) {
echo "Вибачте, ви застрелилися: {$ e-> getMessage ()}";
}
?>
У результаті роботи програми в браузері буде виведений наступний текст:
Все, що має початок, має й кінець.
Вибачте, ви застрелилися: bang-bang!
Як бачите, код-фіналізатор у функції action () спрацьовує "прозоро" для викликає програми: виключення типу HeadsnotException не втрачається, а виходить за межі функції за рахунок повторного використання throw всередині catch-блоку.
Така техніка вкладеного виклику throw називається повторної генерацією винятку. Зазвичай її застосовують у разі, коли внутрішній обробник не може повністю обробити виняток, і його потрібно передати далі, щоб помилка була проаналізована в більш відповідному місці.

3.10 ТРАНСФОРМАЦІЯ ПОМИЛОК

Ми розділили всі помилки на два види:
● "несерйозні" - діагностичні повідомлення; перехоплюються за допомогою set_error_handier ();
● "серйозні" - неможливо продовжити нормальний хід роботи коду, представлені винятками.
Ми також відзначали, що, ці два види помилок не перетинаються і в ідеалі повинні оброблятися незалежними механізмами (бо мають різні підходи до написання коду відновлення).
Відомо, що в програмуванні будь-яка помилка може бути посилена, принаймні, без погіршення якості коду. Наприклад, якщо змусити РНР негайно завершувати роботу скрипта не тільки при виявленні помилок класу E_ERROR і E_PARSE (перехоплення яких взагалі неможливий), але також і при виникненні E_WARNING і навіть E_NOTICE, програма стане більш "крихкою" до неточностей у вхідних даних. Але зате програміст буде просто змушений волею-неволею писати більш якісний код, перевіряючий кожну дрібницю при своїй роботі. Таким чином, якість написання коду при "посилення" реакції на помилку здатне тільки зрости, а це звичайно є великою гідністю.

3.10.1 Серйозність "несерйозних" помилок
Що стосується збільшення "крихкості" при жорсткості реакції на помилки, то це дуже невизначена формулювання. Часто навіть не можна заздалегідь передбачити, наскільки та чи інша ділянка коду чутливий до несподіваних ситуацій.
Для прикладу розглянемо повідомлення класу E_WARNING, що виникає при помилку відкриття файлу. Чи є воно фатальним, і чи можливо подальше виконання програми при його виникненні без будь-яких розгалужень? Однозначної відповіді на це питання дати не можна.
Ось дві крайні ситуації.
● Неможливість відкриття файлу вкрай фатальна. Наприклад, нехай скрипт відкриває який-небудь файл, що містить програмний код, який необхідно виконати (такі ситуації зустрічаються при модульній організації сценаріїв). При неможливості запуску цього коду вся подальша робота програми може стати просто безглуздою.
● Неможливість відкриття файлу практично ні на що не впливає. Наприклад, програма може записувати в цей файл інформацію про те, коли вона була запущена. Або навіть більш простий приклад: скрипт просто перевіряє, чи існує потрібний файл, а якщо його немає, то створює новий порожній.
Розглянемо тепер саме "слабке" повідомлення, класу E_NOTICE, яка генерується РНР, наприклад, при використанні неініціалізованої змінної. Часто такі помилки вважають настільки незначними, що навіть відключають реакцію на них у файлі php.ini (error_reporting = E_ALL ~ E_NOTICE). Більше того, саме таке значення error_reporting виставляється за замовчуванням у дистрибутиві PHP.
Неважко знову навести два приклади крайніх ситуацій, коли E_NOTICE грає дуже важливу роль і, навпаки, ні на що не впливає (на прикладі використання змінної або елементу масиву, якої раніше не було присвоєно значення).
Припустимо, ви виконуєте SQL-запит для додавання нового запису в таблицю MySQL:
INSERT INTO table (id, parent_id, text)
VALUES (NULL, '$ pid', 'Have you ever had a dream, that you were so sure was real?')
У змінній $ pid зберігається деякий ідентифікатор, який повинен бути обов'язково числовим. Якщо ця змінна виявиться неініціалізованої (наприклад, де-то в програмі вище сталася помилка), буде згенерована помилка E_NOTICE, а замість $ pid підставиться порожній рядок. SQL-запит ж все одно залишиться синтаксично коректним. У результаті в базі даних з'явиться запис з полем parent_id, рівним нулю (порожній рядок''без будь-яких попереджень трактується MySQL як 0). Це значення може бути недопустимим для поля parent_id (наприклад, якщо воно є зовнішнім ключем для таблиці table, тобто вказує на іншу "батьківську" запис з певним ID). А раз значення неприпустимо, то цілісність бази даних порушена, і це надалі цілком може призвести до серйозних наслідків (заздалегідь непередбачуваним) в інших частинах скрипта, причому про їх зв'язок з одним-єдиним E_NOTICE, що згенерував раніше, залишиться тільки здогадуватися.
● Тепер про те, коли E_NOTICE може бути нешкідливою. Ось приклад коду:
cinput type = "text" name "field"
value ="<?= htmlspecialchars ($ _REQUEST ['field'])?>">
Очевидно, що якщо осередок $ _REQUEST ['field'] не була ініціалізований (наприклад, скрипт викликаний шляхом набору його адреси в браузері і не приймає ніяких вхідних даних), елемент форми повинен бути порожній. Подібна ситуація настільки широко поширена, що зазвичай ставлять @ перед зверненням до елементу масиву, або навіть перед htmlspecialchars (). У цьому випадку повідомлення буде точно придушене.

3.10.2 Перетворення помилок у виключення

Ми приходимо до висновку, що помилки будь-якого рівня можна трактувати як "серйозну" (за винятком ситуації, коли перед вираженням явно вказаний оператор @, пригнічує виведення всіх помилок. Для обробки же серйозних помилок в РНР є прекрасний засіб - виключення.
Приклад. Рішення, яке ми тут розглянемо, - бібліотека для автоматичного перетворення всіх перехоплюваних помилок РНР (на кшталт E_WARNING, E_NOTICE і т. д.) в об'єкти-виключення однойменних класів. Таким чином, якщо програма не зможе, наприклад, відкрити якийсь файл, тепер буде згенеровано виняток, яке можна перехопити у відповідній ділянці програми. Лістинг 3.11 ілюструє сказане.
Лістинг 3.11. Файл w2e_simple.php
<? Php # # Перетворення помилок у виключення.
require_once "lib / config.php";
require_once "PHP / Exceptionizer.php";
/ / Для більшої наочності помістимо основний перевірочний код у функцію.
suffer ();
/ / Переконуємося, що перехоплення дійсно був відключений.
echo "<b> Далі має йти звичайне повідомлення PHP. </ b>";
fopen ("fork", "r");
function suffer () {
/ / Створюємо новий об'єкт-перетворювач. Починаючи з цього моменту
/ / І до знищення змінної $ w2e всі перехоплюваним помилки
/ / Перетворюються на однойменні винятку.
$ W2e = new PHP_Exceptionizer (E_ALL);
try {
/ / Відкриваємо неіснуючий файл. Тут буде помилка E_WARNING.
fopen ("spoon", "r");
} Catch (E_WARNING $ e) {
/ / Перехоплюємо виняток класу E_WARNING.
echo "<div> <b> перехоплена помилка! </ b> \ n", $ e, "</ div>";
}
/ / Наприкінці можна явно видалити перетворювач командою:
/ / Unset ($ w2e);
/ / Але можна цього й не робити - змінна і так віддалиться при
/ / Виході з функції (при цьому викличеться деструктор об'єкта $ w2e,
/ / Відключає стеження за помилками).
}
?>
Зверніть увагу на заголовок catch-блоку. Він може спочатку ввести в оману: адже перехоплювати можна тільки об'єкти-виключення, вказуючи ім'я класу, але ніяк не числове значення (E_WARNING - взагалі кажучи, константа РНР, числове значення якої дорівнює 2 - можете переконатися в цьому, запустивши оператор echo E_WARNING). Тим не менш помилки немає: E_WARNING - це одночасно і ім'я класу, який визначається в бібліотеці PHP_Exceptionizer.
Зауважте також, що для обмеження області роботи перехоплювача використовується вже знайома нам ідеологія: "виділення ресурсу є ініціалізація". А саме в тому місці, з якого необхідно почати перетворення, ми поміщаємо оператор створення нового об'єкта PHP_Exceptionizer і запам'ятовуємо останній у змінній, а там, де перетворення слід закінчити, просто знищуємо об'єкт-перехоплювач (явно або, як у прикладі, неявно, при виході з функції).

3.10.3 Код бібліотеки PHP_Exceptionizer
Перш ніж продовжити опис можливостей перехоплення, давайте розглянемо код класу PHP_Exceptionizer, що реалізує перетворення стандартних помилок РНР у виключення (лістінг.3.12).
Лістинг 3.12. Файл lib / PHP / Exceptionizer.php
<? Php # # Клас для перетворення помилок PHP у виключення.
/ **
* Клас для перетворення перехоплюваних (див. set_error_handler ())
* Помилок і попереджень PHP у виключення.
*
* Наступні типи помилок, хоча і підтримуються формально, не можуть
* Бути перехоплені:
* E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR,
* E_COMPILE_WARNING
* /
class PHP_Exceptionizer {
/ / Створює новий об'єкт-перехоплювач і підключає його до стека
/ / Обробників помилок PHP (використовується ідеологія "виділення
/ / Ресурсу є ініціалізація ").
public function __construct ($ mask = E_ALL, $ ignoreOther = false) {
$ Catcher = new PHP_Exceptionizer_Catcher ();
$ Catcher-> mask = $ mask;
$ Catcher-> ignoreOther = $ ignoreOther;
$ Catcher-> divvHdl = set_error_handler (array ($ catcher, "handler"));
}
/ / Викликається при знищенні об'єкта-перехоплювача (наприклад,
/ / При виході його з області видимості функції). Відновлює
/ / Попередній обробник помилок.
public function __destruct () {
restore_error_handler ();
}
}
/ **
* Внутрішній клас, що містить метод перехоплення помилок.
* Ми не можемо використовувати для цієї ж мети безпосередньо $ this
* (Класу PHP_Exceptionizer): виклик set_error_handler () збільшує
* Лічильник посилань на об'єкт, а він повинен залишитися незмінним, щоб у
* Програмі завжди залишалася рівно одне посилання.
* /
class PHP_Exceptionizer_Catcher {
/ / Бітові прапори попереджень, які перехоплюватимуться.
public $ mask = E_ALL;
/ / Ознака, чи потрібно ігнорувати інші типи помилок, або ж
/ / Слід використовувати стандартний механізм обробки PHP.
public $ ignoreOther = false;
/ / Попередній обробник помилок.
public $ divvHdl = null;
/ / Функція-обробник помилок PHP.
public function handler ($ errno, $ errstr, $ errfile, $ errline) {
/ / Якщо error_reporting нульовою, значить, використаний оператор @,
/ / І всі помилки мають ігноруватися.
if (! error_reporting ()) return;
/ / Перехоплювач НЕ повинен обробляти цей тип помилки?
if (! ($ errno & $ this-> mask)) {
/ / Якщо помилку НЕ слід ігнорувати ...
if (! $ this-> ignoreOther) {
if ($ this-> divvHdl) {
/ / Якщо попередній обробник існує, викликаємо його.
$ Args = func_get_args ();
call_user_func_array ($ this-> divvHdl, $ args);
} Else {
/ / Інакше повертаємо false, що викликає запуск вбудованого
/ / Обробника PHP.
return false;
}
}
/ / Повертаємо true (все зроблено).
return true;
}
/ / Отримуємо текстове представлення типу помилки.
$ Types = array (
"E_ERROR", "E_WARNING", "E_PARSE", "E_NOTICE", "E_CORE_ERROR",
"E_CORE_WARNING", "E_COMPILE_ERROR", "E_COMPILE_WARNING",
"E_USER_ERROR", "E_USER_WARNING", "E_USER_NOTICE", "E_STRICT",
);
/ / Формуємо ім'я класу-винятки в залежності від типу помилки.
$ ClassName = __CLASS__. "_". "Exception";
foreach ($ types as $ t) {
$ E = constant ($ t);
if ($ errno & $ e) {
$ ClassName = $ t;
break;
}
}
/ / Генеруємо виняток потрібного типу.
throw new $ className ($ errno, $ errstr, $ errfile, $ errline);
}
}
/ **
* Базовий клас для всіх винятків, отриманих в результаті помилки PHP.
* /
abstract class PHP_Exceptionizer_Exception extends Exception {
public function __construct ($ no = 0, $ str = null, $ file = null, $ line = 0) {
parent:: __construct ($ str, $ no);
$ This-> file = $ file;
$ This-> line = $ line;
}
}
/ **
* Створюємо ієрархію "серйозності" помилок, щоб можна було
* Ловити не тільки виключення із зазначенням точного типу, але
* І повідомлення, не менш "фатальні", ніж вказано.
* /
class E_EXCEPTION extends PHP_Exceptionizer_Exception {}
class AboveE_STRICT extends E_EXCEPTION {}
class E_STRICT extends AboveE_STRICT {}
class AboveE_NOTICE extends AboveE_STRICT {}
class E_NOTICE extends AboveE_NOTICE {}
class AboveE_WARNING extends AboveE_NOTICE {}
class E_WARNING extends AboveE_WARNING {}
class AboveE_PARSE extends AboveE_WARNING {}
class E_PARSE extends AboveE_PARSE {}
class AboveE_ERROR extends AboveE_PARSE {}
class E_ERROR extends AboveE_ERROR {}
class E_CORE_ERROR extends AboveE_ERROR {}
class E_CORE_WARNING extends AboveE_ERROR {}
class E_COMPILE_ERROR extends AboveE_ERROR {}
class E_COMPILE_WARNING extends AboveE_ERROR {}
class AboveE_USER_NOTICE extends E_EXCEPTION {}
class E_USER_NOTICE extends AboveE_USER_NOTICE {}
class AboveE_USER_WARNING extends AboveE_USER_NOTICE {}
class E_USER_WARNING extends AboveE_USER_WARNING {}
class AboveE_USER_ERROR extends AboveE_USER_WARNING {}
class E_USER_ERROR extends AboveE_USER_ERROR {}
/ / Ієрархії користувача і вбудованих помилок не можна порівняти,
/ / Тому вони використовуються для різних цілей, і оцінити
/ / "Серйозність" не можна.
?>
Перерахуємо гідності описаного підходу.
● Жодна помилка не може бути випадково пропущена або проігнорована. Програма виходить більш "крихкою", але зате якість і "передбачуваність" поведінки коду сильно зростають.
● Використовується зручний синтаксис обробки виключень, набагато більш "прозорий", ніж робота з set_error_handler (). Кожен об'єкт-виключення додатково містить інформацію про місце виникнення помилки, а також відомості про стеку викликів функцій, і всі ці дані можна витягти за допомогою відповідних методів класу Exception.
● Можна перехоплювати помилки вибірково, за типами, наприклад, окремо обробляти повідомлення E_WARNING і окремо - E_NOTICE.
● Можлива установка "перетворювача" не для всіх різновидів помилок, а тільки для деяких з них (наприклад, перетворювати помилки E_WARNING у виключення класу E_WARNING, але "нічого не робити" з E_NOTICE).
● Класи-виключення об'єднані в ієрархію успадкування, що дозволяє при необхідності перехоплювати не тільки помилки, точно збігаються з вказаним типом, але також заодно і більш "серйозні".

3.10.4 Ієрархія винятків

Зупинимося на останньому пункті наведеного вище списку. Поглянувши ще раз на кінець лістингу 3.12, ви можете виявити, що класи-виключення об'єднані в досить складну ієрархію спадкування. Головною "родзинкою" методу є введення ще однієї групи класів, імена яких мають префікс Above. При цьому більш "серйозні" Above-класи помилок є нащадками всіх "менш серйозних". Наприклад, AboveERROR, сама "серйозна" з помилок, має в "предків" всі інші Above-класи, a AboveE_STRICT, найслабша, не успадковує ніяких інших Above-класів. Подібна ієрархія дозволяє нам перехоплювати помилки не тільки з типом, в точності збігається з зазначеним, але також і більш серйозні.
Наприклад, нам може знадобитися перехоплювати в програмі всі помилки класу E_USER_WARNING і більше фатальні E_USER_ERROR. Дійсно, якщо ми дбаємо про якісь там попередженнях, то вже звісно повинні подбати й про серйозні помилки. Ми могли б вчинити так:
try {
/ / Генерація помилки
} Catch (E_USER_WARNING $ e) {
/ / Код відновлення
} Catch (E_USER_ERROR $ e) {
/ / Точно такий же код відновлення - доводиться дублювати
}
Складна ієрархія винятків дозволяє нам записати той же фрагмент простіше і зрозуміліше (лістінг3.13).
Лістинг 3.13. Файл w2e_hier.php
<? Php # # Ієрархія помилок.
require_once "lib / config.php";
require_once "PHP / Exceptionizer.php";
suffer ();
function suffer () {
$ W2e = new PHP_Exceptionizer (E_ALL);
try {
/ / Генеруємо помилку.
trigger_error ("Damn it!", E_USER_ERROR);
} Catch (AboveE_USER_WARNING $ e) {
/ / Перехоплення помилок: E_USER_WARNING і більш серйозних.
echo "<div> <b> перехоплена помилка! </ b> \ n", $ e, "</ div>";
}
}
?>

3.10.5 Фільтрація за типами помилок

Використання механізму обробки винятків увазі, що після виникнення помилки "назад ходу немає": управління передається в catch-блок, а нормальний хід виконання програми переривається. Можливо, ви не захочете такої поведінки для всіх типів попереджень РНР. Наприклад, помилки класу E_NOTICE іноді не має сенсу перетворювати у виключення і робити їх, таким чином, надмірно фатальними.
Тим не менш, в більшості випадків E_NOTICE свідчить про логічну помилку в програмі і може розглядатися, як тривожний сигнал програмісту. Ігнорування таких ситуацій може спричинити проблеми при налагодженні, тому на практиці має сенс перетворювати у виключення і E_NOTICE теж.
Ви можете вказати в першому параметрі конструктора PHP_Exceptionizer, які типи помилок необхідно перехоплювати. За умовчанням там стоїть E_ALL (тобто перехоплювати всі помилки та попередження), але ви можете задати і будь-яке інше значення (наприклад, бітову маску E_ALL ~ E_NOTICE ~ E_STRICT), якщо побажаєте.
Існує ще і другий параметр конструктора. Він вказує, що потрібно робити з повідомленнями, тип яких не задовольняє бітової масці, наведеною в першому параметрі. Можна їх або обробляти звичайним способом, тобто передавати раніше встановленому обробнику (false), або ж просто ігнорувати (true).
Нагадаємо, що в РНР 5 функція set_error_handler () приймає другий необов'язковий параметр, в якому можна вказати бітову маску "спрацьовування" обробника. А саме для тих типів помилок, які "підходять" під маску, буде викликана користувацька функція, а для всіх інших-стандартна, вбудована в РНР. Клас PHP_Exceptionizer веде себе дещо по-іншому: у випадку розбіжності типу помилки з бітовою маскою буде викликаний не вбудований в РНР обробник, а попередній призначений (якщо він був). Таким чином, реалізується стек перехоплювачів помилок. У ряді ситуацій це виявляється більш зручним.

3.10.6 Перспективи

За неофіційними даними, в РНР версії 5.1 (і старше) розробники планують реалізувати вбудований механізм перетворення помилок у виключення. Для цього, імовірно, буде використовуватися інструкція declare, що дозволяє задавати блоку програми різні властивості (в тому числі, що робити при виникненні помилки). Код перехоплення може виглядати, наприклад, так:
/ / Включаємо "виключне" поведінка помилок в РНР.
declare (exception_map = '+ standard: streams: *') {
try {
/ / Насправді генерується виключення, а не попередження.
fopen ("spoon", 'r');
} Catch (Exception $ e) {
if ($ e-> getCode () = = 'standard: streams: E_NOENT') {
echo "Ложка не існує!";
}
}
}
/ / При виході з declare-блоку попередні властивості відновлюються.
На жаль, в РНР версії 5.0 нічого подібного немає. Перевірте, можливо, дана функціональність з'явилася у вашій версії інтерпретатора (див. документацію на інструкцію declare за адресою http://php.net/dedare).

ВИСНОВОК

Розглянуто один із найбільш важливих і популярних програмуючи - обробка помилок у коді програми. Уточнено поняття терміна "помилка" та визначено його роль у програмуванні, а також наведено різні класифікації помилкових ситуацій. Представлено опис поняття "виключення" і способи використання конструкції try ... catch, а також описані деякі особливості її роботи в РНР. Описано механізм успадкування та класифікації винятків, використання якого може сильно скоротити код програми і зробити його універсальним. Представлений код бібліотеки, яка дозволяє обробляти численні помилки та попередження, що генеруються функціями РНР, як звичайні винятку.
Грамотний перехоплення помилок з самого зародження програмування вважався важким завданням. Механізм обробки виключень, хоча і спрощує її, але все одно залишається дуже складним.

ЛІТЕРАТУРА

1. Скляр Д., Трахтенберг А. PHP. Збірник рецептів. - Пер. з англ. - СПб: Символ - Плюс, 2005. - 627 с., Іл.
2. Котеров Д., Костарев А. PHP 5 в оригіналі. - СПб: Символ - Плюс, 2005. - 1120 с., Іл.
3. Дюбуа П. MySQL. Збірник рецептів. - Пер. з англ. - СПб: Символ - Плюс, 2004. - 1056 с., Іл.
4. Томсон Лаура, Веллінг Люк. Розробка web - додатків на PHP і MySQL. - Пер. з англ. - СПб: ТОВ «ДіаСофтЮП», 2003. 672 с., Іл.
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Книга
150.3кб. | скачати


Схожі роботи:
Що таке РНР
Етапи розробки програм Тестування та налагодження Документування програм
Обробка сировини виробництво напівфабрикатів обробка овочів і грибів
Розробка програми генерації тестів з бази даних на мові РНР
Спадковість уявлення про генетичному коді гени індивідуальності
Додавання і віднімання цілих невід`ємних чисел у двійковому коді
Множення і ділення цілих невід`ємних чисел у двійковому коді
Гідроабразивне обробка Обробка вибухом
Кілька поширених рекламних помилок
© Усі права захищені
написати до нас