PHP Manual

Виключення та їх перехоплення в PHP

16. 02. 2020

Виняток становлять засоби об'єктно-орієнтованого програмування, які забезпечують елегантний спосіб викидання та обробки (лікування) помилок додатків.

Виняток становлять перші кинуті (thrown), оброблені (try) та спіймані (catch). Обов'язковим є тільки метання.

Філософія покоління винятків

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

Насправді, самі функції не забезпечують обробку помилок, що може призвести до фатальних проблем, але про це Девід написав у статті Програмісти не ігнорують помилки.

Приклад забутої обробки помилок:

// переміщення з диска на диск
copy('c:/oldfile', 'd:/newfile');
unlink('c:/oldfile');
// якщо перша операція завершилась невдачею, то файл видаляється безповоротно

Це пов'язано з тим, що правильний спосіб обробки виводу функції copy() - не продовжувати виконання і згенерувати помилку. У випадку зі старими добрими функціями це могло б виглядати так:

function backup(): bool
{
if (copy('c:/oldfile', 'd:/newfile')) {
return unlink('c:/oldfile');
}
return false;
}

Наша функція backup() поверне значення true тільки в тому випадку, якщо функція copy() не зазнала невдачі і функція unlink() повернула значення true. В іншому випадку буде повернуто false.

Але чи безпечно це зараз для застосування? Це не так! Тому що тепер нам доведеться обробляти вихід функції backup() в точці її виклику, і якщо вона не спрацює, ми навіть не будемо знати чому. Коротше кажучи, він поверне "false", і ми повинні самі якось виявити помилку. Думаю, в даному випадку добре, що програмісти часто відмовляються від обробки помилок, або просто забувають щось обробити і додаток через це викидає важко виявлені помилки.

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

Базове визначення винятків

У мові PHP виключення - це особливий вид інтерфейсу, реалізований нативним класом Exception, який ми будемо використовувати.

Якщо обробка якоїсь частини програми завершується невдачею, ми просто генеруємо виключення з описом проблеми:

if (copy('c:/oldfile', 'd:/newfile') === false) {
throw new \Exception('Не вдається скопіювати файл "oldfile".');
}

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

Першим аргументом конструктора класу \Exception приймається текст виключення, який повинен стисло пояснювати, що щойно відбулось. Належною практикою є також включення інформації про операцію, що виконується, та посилання на дані. Наприклад, якщо копія файлу не вдалася, рекомендується передавати ім'я файлу. Якщо виконання SQL-запиту завершилося невдачею, знову передаємо запит, що виконується. Це дуже допоможе нам потім при роботі з помилками, адже ми зможемо побачити, в чому саме полягає проблема.

Обробка винятків

Наприклад, нехай у нас є функція backup(), яка виконує резервне копіювання даних і може видавати пару помилок:

function backup(): void
{
if (copy('c:/oldfile', 'd:/newfile')) {
if (unlink('c:/oldfile') === false) {
throw new \Exception('Неможливо видалити старий файл.');
}
}
throw new \Exception('Не вдається скопіювати файли резервних копій.');
}

Зверніть увагу, що функція не повертає жодного результату, а у визначенні ми вказуємо тип void. Функція не повинна нічого повертати, оскільки успішним вважається стан, коли не викидається помилка і нам не потрібно обробляти позитивний сценарій.

Якби ми використовували функцію в додатку без обробки, наприклад, наступним чином:

echo 'Резервні копії файлів...';
backup();
echo 'Резервне копіювання завершено.';

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

Якщо ми хочемо продовжити виконання, нам потрібно очистити помилку, що ми і робимо з допомогою конструкцій try та catch:

echo 'Резервні копії файлів...';
try {
backup();
} catch (\Exception $e) {
echo 'Резервне копіювання не вдалося:' . $e->getMessage();
}
echo 'Резервне копіювання завершено.';

У випадку виникнення виключення викликається код в області catch() (який приймає виключення, якщо воно відповідає типу даних) та виконується внутрішній код.

Завжди отримуємо екземпляр класу exception, який можна використати, наприклад, для виведення повідомлення про помилку, яке обробляється методом getMessage(). Також корисно знати метод getFile(), який повертає шлях на диску до файлу, що містить помилку, getCode(), який повертає код статусу помилки, та getLine(), який повертає номер рядка, де було згенеровано виключення.

Підготовлені винятки

На додаток до базового виключення \Exception, PHP включає в себе інші попередньо визначені типи винятків, які підходять для різних випадків використання.

| Тип даних | Пояснення | Пояснення |------------|-----------| | Логічне виключення | Логічна помилка, передбачувана при проектуванні програми | BadFunctionCallException | Помилка виклику функції; функція не знайдена; виклик не дозволений | BADMethodCallException - помилка виклику методу | Виключення InvalidArgumentException | Поганий (недійсний) аргумент, переданий у функцію або метод | OutOfRangeException | Значення за межами діапазону масиву або колекції | LengthException | Значення перевищує допустиму довжину | DomainException | Значення не потрапляє в необхідний домен або діапазон | RuntimeException | Помилка, що виявляється тільки під час виконання (наприклад, неможливість запису у файл) | Виняток переповнення буфера або арифметичної операції, часто викликане переповненням буфера або арифметичної операції, часто викликане обробкою більшої кількості даних, ніж очікувалося. | UnderflowException | Недоповнення буфера або арифметична операція, передано менше даних, ніж очікувалося | Виключення OutOfBoundsException | Індекс за межами діапазону масиву або колекції | RangeException | Значення не в межах запитуваного діапазону | Виключення UnexpectedValueException | Неочікуване значення (наприклад, значення, що повертається функцією)

Винятки LogicException та RuntimeException повинні бути попереджені належним проектуванням програми. Особисто я використовую їх лише у виняткових ситуаціях, таких як збій запису у файл та зв'язок із зовнішнім сервісом.

Я рекомендую взагалі не перехоплювати RuntimeException і дати програмі завершити роботу. Зазвичай це серйозна проблема, про яку слід повідомляти якнайшвидше.

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
11.
Status:
All systems normal.
2024