Реферати

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

Петро Абеляр. Життя Абеляра. Абеляр як родоначальник опозиційної максимально раціоналізованої філософії західноєвропейського середньовіччя.

Системи й учасники зобов'язань у сфері платного утворення. КОНТРОЛЬНА РОБОТА З ПРАВОВОГО ЗАБЕЗПЕЧЕННЯ СОЦІАЛЬНО_КУЛЬТУРНОГО СЕРВІСУ І ТУРИЗМУ СИСТЕМИ Й УЧАСНИКИ ЗОБОВ'ЯЗАНЬ У СФЕРІ ПЛАТНОГО УТВОРЕННЯ

Инкское право. Уведення 1 Історичні джерела 2 Використання стос для запису законів 3 Принципи 4 Галузі 5 Законодавці Список літератури Введення Инкское чи право Закони Інків - правова система, що виникла на основі Андских культур і, що розвивалася аж до падіння Імперії Інків у 1533 році, потім у Новоинкском державі у Вилькабамбе до 1572 року, і просуществовавшая кілька десятиліть[1] (а в окремих випадках і кілька сторіч) після іспанської конкісти, завдяки як специфіці високогірних суспільств, де європейські порядки викликали збурювання місцевих жителів, так і налагодженій системі виконання.

Давньогрецький театр у період свого розквіту. ГОУ ВПО НОВОСИБІРСЬКИЙ ДЕРЖАВНИЙ МЕДИЧНИЙ УНІВЕРСИТЕТ Кафедра соціально-історичних наук Реферат Давньогрецький театр у період свого розквіту

Розкрадання чужого майна. Зміст Уведення......3 1 Поняття й ознаки розкрадання чужого майна......6 1.1 Поняття і загальна характеристика розкрадання чужого майна......6

ФЕДЕРАЛЬНЕ АГЕНТСТВО ЗА ОСВІТОЮ

РОСІЙСЬКИЙ ХИМИКО-ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ

ім. Д. І. Менделеєва

НОВОМОСКОВСКИЙ ІНСТИТУТ

ОБРОБКА ПОМИЛОК В КОДІ ПРОГРАММРНР

УЧБОВА ДОПОМОГА

Новомоськовськ 2008

ФЕДЕРАЛЬНЕ АГЕНТСТВО ЗА ОСВІТОЮ

РОСІЙСЬКИЙ ХИМИКО-ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ

ім. Д. І. Менделеєва

НОВОМОСКОВСКИЙ ІНСТИТУТ

ОБРОБКА ПОМИЛОК В КОДІ ПРОГРАММРНР

УЧБОВА ДОПОМОГА

Укладач: В. С. Прохоров

Зміст

ВВЕДЕННЯ

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_ALLE_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-файлах з вставками РНР-коду, якщо дуже лінь писати багато лапки:)( <? =@$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 ## Перехоплення помилок і попереджень.

// Визначаємо нову функція-обробник.

functionmyErrorHandler($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=ПРО] [,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 " < pre > "; print_r(debug_backtrace()); echo " < /pre > ";

}

function outer($х) {

// Батьківська функція.

inner($х*$х);

}

// Головна програма.

outer(3);?

>

Після запуску цього скрипта буде надрукований приблизно наступний результат (його трохи стисли):

Array ([0] = > Array ( [file] = > z:\home\book\original\src\interpreter\trace.php

[line] = > 6

[function] = > inner

[args] = > Array ([0] = > 9)

)

[1] = > Array ( [file] = > z:\home\book\original\src\interpreter\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 $е) {

// Кодобработчика.

echo " Виключення: {$е- > 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 $е) {

echo " Виключення: {$е- > 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 $е) {

echo " Виключення: {$е- > 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 $е) {

// Ловимо БУДЬ-ЯКЕ файлове виключення!

echo "Помилка при роботі з файлом '{$е- > getName()}'. < br > ";

} catch (Exception $е) {

// Ловимо всі інші виключення, які ще не піймали.

echo "Інше виключення: {$е- > 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) {

$е = new Exception( bang-bang #$n!");

echo " < pre > ", $е, " < /pre > ";

}

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 $е) {

// Перехоплюємо тільки файлові виключення.

echo "Файлова помилка: {$е- > getMessage()}. < br > ";

} catch (Exception $е) {

// Перехоплення всіх інших виключень.

echo "Невідоме виключення: < pre > ", $е, " < /pre > ";

}

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 $е)

{

echo "неперехоплене виключення:, $"е;

}

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 $е) {

// Ловимо БУДЬ-ЯКЕ виключення, виводимо текст...

echo "має і кінець. < br > ";

// ... а потім передаємо це виключення далі.

throw $е;

}

}

try {

// Викликаємо функцію.

action();

} catch (HeadshotException $е) {

echo "Вибачите, визастрелились: {$е- > 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 а 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_WARNING.

echo " < pre > < b > Перехоплена помилка! < /b > \n", $е, " < /pre > ";

}

// У кінці можна явно видалити перетворювач командою:

// unset($w2e);

// Але можна цього і не робити - змінна і так віддалиться при

// виході з функції (ри цьому викличеться деструктор об'єкта $w2e,

// що відключає стеження за помилками).

}?

>

Зверніть увагу на заголовок catch-блоку. Він може спочатку ввести в помилку: адже перехоплювати можна тільки об'єктів-виключення, вказуючи ім'я класу, але ніяк не числове значення (E_WARNING - взагалі говорячи, константа РНР, числове значення якої дорівнює 2 - можете пересвідчитися в цьому, запустивши оператор echoE_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- > prevHdl = set_error_handler(array($catcher, "handler"));

}

// Викликається при знищенні об'єкта-перехоплювача (апример,

// при виході його з області видимості функції). Відновлює

// попередній обробник помилок.

publicfunction _destruct() {

restore_error_handler();

}

}

/**

* Внутрішній клас, вмісний метод перехоплення помилок.

* Ми не можемо використати для цієї ж мети безпосередньо $this

* (класу PHP_Exceptionizer): виклик set_error_handler() збільшує

* лічильник посилань на об'єкт, а він повинен залишитися незмінним, щоб в

* програмі завжди залишалося рівне одне посилання.

*/

classPHP_Exceptionizer_Catcher {

// Бітові прапори попереджень, які будуть перехоплюватися.

public $mask = E_ALL;

// Ознака, чи треба ігнорувати інші типи помилок, або ж

// потрібно використати стандартний механізм обробки PHP.

public $ignoreOther = false;

// Попередній обробник помилок.

public $prevHdl = null;

// Функція-обробник помилок PHP.

public function handler($errno, $errstr, $errfile, $errline) {

// Якщо error_reporting нульової, значить, використаний оператор @,

// і всі помилки повинні ігноруватися.

if (!error_reporting()) return;

// Перехоплювач НЕ повинен обробляти цей тип помилки?

if (! ($errno & $this- > mask)) {

// Якщо помилку НЕ треба ігнорувати...

if (!$this- > ignoreOther) {

if ($this- > prevHdl) {

// Якщо попередній обробник існує, викликаємо його.

$args = func_get_args();

call_user_func_array($this- > prevHdl, $args);

} else {

// Інакше повертаємо false, що викликає запуск вбудованого

// обробника PHP.

returnfalse;

}

}

// Повертаємо true (все зроблене).

returntrue;

}

// Отримуємо текстове представлення типу помилки.

$types = array(_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) {

$е = constant($t);

if ($errno & $е) {

$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-класи, а AboveE_STRICT, сама слаба, не успадковує ніяких інших Above-класів. Подібна ієрархія дозволяє нам перехоплювати помилки не тільки з типом, в точності співпадаючим з вказаним, але також і більш серйозні.

Наприклад, нам може зажадатися перехоплювати в програмі всі помилки класу E_USER_WARNING і більш фатальну E_USER_ERROR. Дійсно, якщо ми піклуємося про якихсь там попередженнях, то уже звісно повинні потурбуватися і про серйозні помилки. Ми могли б поступити так:

try {

// генерацияошибки

} catch (E_USER_WARNING $е) {

// кодвосстановления

} catch (E_USER_ERROR $е) {

// точно такий же код відновлення - доводиться дублювати

}

Складна ієрархія виключень дозволяє нам записати той же фрагмент простіше і зрозуміло (листинг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_USER_WARNING і більш серйозних.

echo " < pre > < b > Перехоплена помилка! < /b > \n", $е, " < /pre > ";

}

}?

>

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 $е) {

if ($е- > getCode() = = 'standard:)(streams:)(E_NOENT ') {

echo "Ложка не існує!";

}

}

}

// При виході з declare-блоку попередні властивості відновлюються.

На жаль, в РНР версії 5.0 нічого подібного немає. Перевірте, можливо, дана функціональність з'явилася у вашій версії інтерпретатора (див. документацію на інструкцію declare за адресою http://php.net/dedare).

ВИСНОВОК

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

Грамотне перехоплення помилок з самого зародження програмування вважалося важкою задачею. Механізм обробки виключень, хоч і спрощує її, але все одно залишається вельми складним.

ЛІТЕРАТУРА

1. Скляр Д., Трахтенберг А. PHP. Збірник рецептів. - Пер. з англ. - СПб: Символ - Плюс, 2005. - 627 з., мул.

2. Котеров Д., Костарев А. PHP5 в оригіналі. - СПб: Символ - Плюс, 2005. - 1120 з., мул.

3. Дюбуа П. MySQL. Збірник рецептів. - Пер. з англ. - СПб: Символ - Плюс, 2004. - 1056 з., мул.

4. Томсон Лаура, Веллінг Люк. Розробка web - додатків на PHP і MySQL. - Пер. з англ. - СПб: ТОВ «ДіаСофтЮП», 2003. 672 з., мул.