Не робить помилок тільки той, хто нічого не робить, і ми тому приклад – працюємо не покладаючи рук над створенням робочих місць для тестувальників 🙂

Так, у цій статті я поведу свій розповіді про помилки в PHP, і тому як їх приборкати.

Помилки

Різновиди в сімействі помилок

Перед тим як приручати помилки, я б рекомендував вивчити кожен вид і окремо звернути увагу на найбільш яскравих представників.

Щоб жодна помилка не пішла непоміченою потрібно включити відстеження всіх помилок з допомогою функції error_reporting(), а з допомогою директиви display_errors включити їх відображення:

Фатальні помилки

Найнебезпечніший вид помилок – фатальні, вони можуть виникнути як при компіляції, так і при роботі парсера або PHP-скрипта, виконання скрипта при цьому переривається.

E_PARSE
Це помилка з’являється, коли ви допускаєте грубу помилку синтаксису і інтерпретатор PHP не розуміє, що ви від нього хочете, наприклад, якщо не закрили фігурну або круглу скобочку:

Чи написали на незрозумілій мові:

Зайві дужки теж зустрічаються, і не важливо круглі або фігурні:

Зазначу один важливий момент – код файлу, в якому ви допустили parse error не буде виконаний, отже, якщо ви спробуєте включити відображення помилок в тому ж файлі, де виникла помилка парсера то це не спрацює:

E_ERROR
Це помилка з’являється, коли PHP зрозумів що ви хочете, але зробити це не вийшло через низку причин, так само перериває виконання скрипта, при цьому код до появи помилки спрацює:

Не був знайдений підключається файл:

/**
Fatal error: require_once(): Failed opening required ‘not-exists.php’ (include_path=’.:/usr/share/php:/usr/share/pear’)
*/
require_once ‘not-exists.php’;

Було кинуто виняток (що це за звір, розповім трохи згодом), але не було опрацьовано:

/**
Fatal error: Uncaught exception ‘Exception’
*/
throw new Exception();

При спробі викликати неіснуючий метод класу:

/**
Fatal error: Call to undefined method stdClass::notExists()
*/
$stdClass = new stdClass();
$stdClass->notExists();

Відсутність вільної пам’яті (більше, ніж прописано в директиві memory_limit) або ще чого-небудь подібного:

/**
Fatal Error: Allowed Memory Size
*/
$arr = array();
while (true) {
$arr[] = str_pad(‘ ‘, 1024);
}

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

Рекурсивний виклик функції. В даному прикладі він закінчився на 256-ій ітерації, бо так прописано у налаштуваннях xdebug:

/**
Fatal error: Maximum function nesting level of ‘256’ reached, aborting!
*/
function deep() {
deep();
}
deep();

Не фатальні

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

E_WARNING
Частенько зустрічається, коли підключаєш файл з використанням include, а його не виявляється на сервері або помилилися вказуючи шлях до файлу:

/**
Warning: include_once(): Failed opening ‘not-exists.php’ for inclusion
*/
include_once ‘not-exists.php’;

Буває, якщо використовуєш неправильний тип аргументів при виклику функцій:

/**
Warning: join(): Invalid arguments passed
*/
join(‘string’, ‘string’);

Їх дуже багато, і перераховувати все не має сенсу…

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

Коли звертаються до невизначеної змінної:

/**
Notice: Undefined variable: a
*/
echo $a;

Коли звертаються до неіснуючого елемента масиву:

Коли звертаються до неіснуючої константі:

/**
Notice: Use of undefined constant UNKNOWN_CONSTANT – assumed ‘UNKNOWN_CONSTANT’
*/
echo UNKNOWN_CONSTANT;

Коли не конвертують типи даних:

/**
Notice: Array to string conversion
*/
echo array();

Для уникнення подібних помилок – будьте уважніше, і якщо вам IDE підказує про щось- не ігноруйте її:

E_STRICT
Це помилки, які навчать вас правильно писати код, щоб не було соромно, тим більше IDE вам ці помилки відразу показують. Ось наприклад, якщо викликали не статичний метод як статику, то код буде працювати, але це якось неправильно, і можлива поява серйозних помилок, якщо в подальшому метод класу буде змінено, і з’явиться звернення до $this:

/**
Strict standards: Non-static method Strict::test() should not be called statically
*/
class Strict {
public function test() {
echo “Test”;
}
}
Strict::test();

E_DEPRECATED
Так PHP буде лаятися, якщо ви використовуєте застарілі функції (тобто ті, що позначені як deprecated, і в наступному мажорному релізі їх не буде):

/**
Deprecated: Function split() is deprecated
*/
// популярна функція, все ніяк не видалять з PHP
// deprecated since 5.3
split(‘,’, ‘a,b’);

В моєму редакторі подібні функції будуть закреслені:

Оброблювані

Цей вид, які розводить сам розробник коду, я їх вже давно не зустрічав, не рекомендую їх вам заводити:

  • E_USER_ERROR – критична помилка
  • E_USER_WARNING – не критична помилка
  • E_USER_NOTICE – повідомлення які не є помилками

Окремо варто відзначити E_USER_DEPRECATED – цей вид використовується дуже часто для того, щоб нагадати програмісту, що метод або функція застаріли і пора переписати код без використання неї. Для створення цієї і подібних помилок використовується функція trigger_error():

/**
* @deprecated Deprecated since version 1.2, to be removed in 2.0
*/
function generateToken() {
trigger_error(‘Function `generateToken` is deprecated, use class `Token` instead’, E_USER_DEPRECATED);
// …
// code …
// …
}

Тепер, коли ви познайомилися з більшістю видів і типів помилок, пора озвучити невелике пояснення по роботі директиви display_errors:

  • якщо display_errors = on, то у разі помилки браузер отримає html c текстом помилки і кодом 200
  • якщо ж display_errors = off, то для фатальних помилок код відповіді 500 і результат не буде повернений користувачеві, для інших помилок – код буде працювати неправильно, але нікому про це не розповість

Приручення

Для роботи з помилками у PHP існує 3 функції:

  • set_error_handler() — встановлює обробник для помилок, які не обривають роботу скрипта (тобто не фатальних помилок)
  • error_get_last() — отримує інформацію про останню помилку
  • register_shutdown_function() — реєструє обробник який буде запущено під час завершення роботи скрипта. Дана функція не відноситься безпосередньо до обробників помилок, але найчастіше використовується саме для цього

Тепер трохи подробиць про обробку помилок з використанням set_error_handler(), в якості аргументів дана функція приймає ім’я функції, на яку буде покладена місія по обробці помилок і типи помилок які будуть відслідковуватися. Обробником помилок може так само бути методом класу, або анонімної функцією, головне, щоб він приймав наступний список аргументів:

  • $errno – перший аргумент містить тип помилки у вигляді цілого числа
  • $errstr – другий аргумент містить повідомлення про помилку
  • $errfile – необов’язковий третій аргумент містить ім’я файлу, в якому сталася помилка
  • $errline – необов’язковий четвертий аргумент містить номер рядка, в якій сталася помилка
  • $errcontext – необов’язковий п’ятий аргумент містить масив змінних, що існують в області видимості, де сталася помилка

У разі якщо обробник повернув true, то помилка буде вважатися обробленої і виконання скрипта продовжиться, інакше — буде викликаний стандартний обробник, який логирует помилку і залежно від її типу продовжить виконання скрипту або завершить його. Ось приклад обробника:

$type: $message

“;
echo “

File: $file:$line

“;
echo “

Context: $”. join(‘, $’, array_keys($context)).”

“;
// повідомляємо, що ми обробили помилку, і подальша обробка не потрібна
return true;
}
// реєструємо наш обробник, він буде спрацьовувати на для всіх типів помилок
set_error_handler(‘myHandler’, E_ALL);

У вас не вийде призначити більше однієї функції для обробки помилок, хоча дуже б хотілося реєструвати для кожного типу помилок свій обробник, але немає – пишіть один обробник, і всю логіку відображення для кожного типу описуйте вже безпосередньо в ньому

З обробником, який написаний вище є одна суттєва проблема – він не ловить фатальні помилки, і замість сайту користувачі побачать лише порожню сторінку, або, що ще гірше, повідомлення про помилку. Щоб не допустити подібного сценарію слід скористатися функцією register_shutdown_function() і з її допомогою зареєструвати функцію, яка завжди буде виконуватися по закінченню роботи скрипта:

function shutdown() {
echo ‘Цей текст буде завжди відображатись’;
}
register_shutdown_function(‘shutdown’);

Дана функція буде спрацьовувати завжди!

Але повернемося до помилок, для відстеження появи в коді помилки скористаємося функцією error_get_last(), з її допомогою можна отримати інформацію про останньої виявленої помилки, а оскільки фатальні помилки переривають виконання коду, то вони завжди будуть виконувати роль “останніх”:

function shutdown() {
$error = error_get_last();
if (
// якщо в коді була допущена помилка
is_array($error) &&
// і це одна з фатальних помилок
in_array($error[‘type’], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])
) {
// очищаємо буфер висновку (про нього ми ще поговоримо в наступних статтях)
while (ob_get_level()) {
ob_end_clean();
}
// виводимо опис проблеми
echo “Сервер знаходиться на технічному обслуговуванні, зайдіть пізніше”;
}
}
register_shutdown_function(‘shutdown’);

Завдання
Доповнити обробник фатальних помилок висновком вихідного коду файлу де була допущена помилка, а так само додайте підсвічування синтаксису виведеного коду.

Про ненажерливості

Проведемо простий тест, і з’ясуємо – скільки дорогоцінних ресурсів їсть сама тривіальна помилка:

/**
* Цей код не викликає помилок
*/
// зберігаємо параметри пам’яті і часу виконання скрипта
$memory = memory_get_usage();
$time= microtime(true);
$a = “;
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[$a] = $i;
}
printf(‘%f seconds
‘, microtime(true) – $time);
echo number_format(memory_get_usage() – $memory, 0, ‘.’, ”), ‘ bytes
‘;

У результаті запуску даного скрипта у мене вийшов ось такий результат:

0.002867 seconds
984 bytes

Тепер додамо помилку в циклі:

/**
* Цей код містить помилку
*/
// зберігаємо параметри пам’яті і часу виконання скрипта
$memory = memory_get_usage();
$time= microtime(true);
$a = “;
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[$b] = $i; // тут помилилися з ім’ям змінної
}
printf(‘%f seconds
‘, microtime(true) – $time);
echo number_format(memory_get_usage() – $memory, 0, ‘.’, ”), ‘ bytes
‘;

Результат очікувано гірше, і на порядок (навіть на два порядки!):

0.263645 seconds
992 bytes

Висновок однозначний – помилки в коді призводять до зайвої ненажерливості скриптів – так що під час розробки і тестування програми вмикайте відображення всіх помилок!

Тестування проводив на PHP версії 5.6, в сьомій версії результат краще – 0.0004 секунди проти 0.0050 – різниця тільки на один порядок, але в будь-якому випадку результат варто прикладених зусиль по виправленню помилок

Де собака зарита

В PHP є спец символ «@» – оператор придушення помилок, його використовують щоб не писати обробку помилок, а покластися на коректне поведінка PHP в разі чого:

При цьому обробник помилок зазначений в set_error_handler() все одно буде викликаний, а факт того, що помилку було застосовано придушення можна відстежити викликавши функцію error_reporting() всередині обробника, в цьому випадку вона поверне 0.

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

Винятки

В еру PHP4 не було виключень (exceptions), все було набагато складніше, і розробники боролися з помилками як могли, це було битва не на життя, а на смерть… Зануритися в цю захоплюючу історію протистояння можете в статті Винятковий код. Частина 1. Варто її читати зараз? Думаю, так, адже це допоможе вам зрозуміти еволюцію мови, і розкриє всю принадність винятків

Виключення — виняткові подія в PHP, на відміну від помилок не просто констатують наявність проблеми, а вимагають від програміста додаткових дій з обробки кожного конкретного випадку.

Наприклад, скрипт повинен зберегти якісь дані в кеш файл, якщо щось пішло не так (немає доступу на запис, немає місця на диску), генерується виключення відповідного типу, а в обробника виключень приймається рішення – зберегти в інше місце або повідомити користувачеві про проблему.

Виняток – це об’єкт який успадковується від класу Exception, містить текст помилки, статус, а також може містити посилання на інше виняток яке стало першопричиною цього. Модель виключень в PHP схожа з використовуваними в інших мовах програмування. Виняток можна ініціювати (як кажуть, “кинути”) за допомогою оператора throw, і можна перехопити (“зловити”) оператором catch. Код генерує виняток, повинен бути оточений блоком try, для того щоб можна було перехопити виняток. Кожен блок try повинен мати як мінімум один відповідний йому блок catch або finally:

try {
// код який може викинути виключення
if (rand(0, 1)) {
throw new Exception(“One”)
} else {
echo “Zero”
}
} catch (Exception $e) {
// код який може обробити виняток
echo $e->getMessage();
}

У яких випадках варто застосовувати винятки:

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

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

$directory = __DIR__ . DIRECTORY_SEPARATOR . ‘logs’;
// директорії може не бути
if (!is_dir($directory)) {
throw new Exception(‘Directory `logs` is not exists’);
}
// не може бути прав на запис у каталог
if (!is_writable($directory)) {
throw new Exception(‘Directory `logs` is not writable’);
}
// можливо хтось вже створив файл, і закрив доступ до нього
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date(‘Y-m-d’) . ‘.log’, ‘a+’)) {
throw new Exception(‘System can\’t create log file’);
}
fputs($file, date(“[H:i:s]”) . “done\n”);
fclose($file);

Відповідно ловити виключення будемо приблизно так:

try {
// код який пише у файл
// …
} catch (Exception $e) {
// виводимо текст помилки
echo “Не вийшло: “. $e->getMessage();
}

У цьому прикладі наведено дуже простий сценарій обробки винятків, коли у нас будь-яка виняткова ситуація обробляється на один манер. Але часто – різні винятки вимагають різного підходу до обробки, і тоді слід використовувати коди винятків і встановити ієрархію винятків у додатку:

// виключення файлової системи
class FileSystemException extends Exception {}
// виключення пов’язані з директоріями
class DirectoryException extends FileSystemException {
// коди винятків
const DIRECTORY_NOT_EXISTS = 1;
const DIRECTORY_NOT_WRITABLE = 2;
}
// виключення пов’язані з файлами
class FileException extends FileSystemException {}

Тепер, якщо використовувати ці винятки то можна отримати наступний код:

try {
// код який пише у файл
if (!is_dir($directory)) {
throw new DirectoryException(‘Directory `logs` is not exists’, DirectoryException::DIRECTORY_NOT_EXISTS);
}
if (!is_writable($directory)) {
throw new DirectoryException(‘Directory `logs` is not writable’, DirectoryException::DIRECTORY_NOT_WRITABLE);
}
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date(‘Y-m-d’) . ‘.log’, ‘a+’)) {
throw new FileException(‘System can\’t open log file’);
}
fputs($file, date(“[H:i:s]”) . “done\n”);
fclose($file);
} catch (DirectoryException $e) {
echo “З директорією виникла проблема: “. $e->getMessage();
} catch (FileException $e) {
echo “З файлом виникла проблема: “. $e->getMessage();
} catch (FileSystemException $e) {
echo “Помилка файлової системи: “. $e->getMessage();
} catch (Exception $e) {
echo “Помилка сервера: “. $e->getMessage();
}

Важливо пам’ятати, що Exception — це насамперед виняткова подія, іншими словами виключення з правил. Не потрібно використовувати їх для обробки очевидних помилок, наприклад, для валідації введених користувачем даних (хоча тут не все так однозначно). При цьому обробник виключень повинен бути написаний у тому місці, де він буде здатний його обробити. Наприклад, обробник виключень для викликані недоступністю файлу для запису повинен бути в методі, який відповідає за вибір файлу або метод його викликає, для того що б він мав можливість вибрати інший файл або іншу директорію.

Так, а що буде якщо не спіймати виняток? Ви отримаєте “Fatal Error: Uncaught exception …”. Неприємно.
Щоб уникнути подібної ситуації слід використовувати функцію set_exception_handler() і встановити обробник для винятків, які кинуті поза блоку try-catch і не були оброблені. Після такого виклику обробника виконання скрипта буде зупинено:

// в якості обробника подій
// будемо використовувати анонімну функцію
set_exception_handler(function($exception) {
/** @var Exception $exception */
echo $exception->getMessage(), “
\n”;
echo $exception->getFile(), ‘:’, $exception->getLine(), “
\n”;
echo $exception->getTraceAsString(), “
\n”;
});

Ще розповім про конструкцію з використанням блоку finally – цей блок буде виконаний незалежно від того, було викинуто виключення або ні:

try {
// код який може викинути виключення
} catch (Exception $e) {
// код який може обробити виняток
// якщо звичайно воно з’явиться
} finally {
// код, який буде виконаний при будь-якому розкладі
}

Для розуміння того, що це нам дає наведу такий приклад використання блоку finally:

try {
// десь глибоко всередині коду
// з’єднання з базою даних
$handler = mysqli_connect(‘localhost’, ‘root’, “, ‘test’);
try {
// при роботі з БД виникла виключна ситуація
// …
throw new Exception(‘DB error’);
} catch (Exception $e) {
// виключення зловили, обробили на своєму рівні
// і повинні його прокинути вгору, для подальшої обробки
throw new Exception(‘Catch exception’, 0, $e);
} finally {
// але, з’єднання з БД необхідно закрити
// будемо робити це в блоці finally
mysqli_close($handler);
}
// цей код не буде виконано, якщо відбудеться виключення в коді вище
echo “Ok”;
} catch (Exception $e) {
// ловимо виняток, і виводимо текст
echo $e->getMessage();
echo “
“;
// виводимо інформацію про початковому виключення
echo $e->getPrevious()->getMessage();
}

Тобто запам’ятайте – блок finally буде виконаний навіть у тому випадку, якщо ви у блоці catch пробрасываете виняток вище (власне саме так він і задумувався).

Для вступної статті інформації в самий раз, хто жадає ще подробиць, то ви їх знайдете у статті Винятковий код 😉

Завдання
Написати свій обробник виключень, з виведенням тексту файла де сталася помилка, і все це з підсвічуванням синтаксису, так само не забудьте вивести trace в читаному вигляді. Для орієнтиру – подивіться як це круто виглядає у whoops.

PHP7 – все не так, як було раніше

Так, от ви зараз всю інформацію вище засвоїли і тепер я буду вантажити вас нововведеннями в PHP7, тобто я буду розповідати про те, з чим ви зустрінетесь через рік роботи PHP розробником. Раніше я вам розповідав і показував на прикладах який милицю потрібно спорудити, щоб відловлювати критичні помилки, так от – в PHP7 це вирішили виправити, але як зазвичай зав’язалися на зворотну сумісність коду, і отримали хоч і універсальне рішення, але воно далеко від ідеалу. А тепер по пунктах про зміни:

  • при виникненні фатальних помилок типу E_ERROR або фатальних помилок з можливістю обробки E_RECOVERABLE_ERROR PHP викидає виключення
  • ці винятки не успадковують клас Exception (пам’ятайте я говорив про зворотної сумісності, це все заради неї)
  • ці винятки успадковують клас Error
  • обидва класу Exception і Error реалізують інтерфейс Throwable
  • ви не можете реалізувати інтерфейс Throwable в своєму коді
  • Інтерфейс Throwable практично повністю повторює нам Exception:

    interface Throwable
    {
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getTraceAsString(): string;
    public function getPrevious(): Throwable;
    public function __toString(): string;
    }

    Складно? Тепер на прикладах, візьмемо ті, що були вище і злегка модернізуємо:

    try {
    // файл, який викликає помилку парсера
    include ‘e_parse_include.php’;
    } catch (Error $e) {
    var_dump($e);
    }

    В результаті помилку зловимо і виведемо:

    object(ParseError)#1 (7) {
    [“message”:protected] => string(48) “syntax error, unexpected ‘буде’ (T_STRING)”
    [“string”:”Error”:private] => string(0) “”
    [“code”:protected] => int(0)
    [“file”:protected] => string(49) “/www/education/error/e_parse_include.php”
    [“line”:protected] => int(4)
    [“trace”:”Error”:private] => array(0) { }
    [“previous”:”Error”:private] => NULL
    }

    Як бачите – зловили виняток ParseError, яка є спадкоємцем виключення Error, який реалізує інтерфейс Throwable, у будинку, який побудував Джек. Ще є інші, але не буду мучити – для наочності наведу ієрархію винятків:

    interface Throwable
    |- Exception implements Throwable
    | |- ErrorException extends Exception
    | |- … extends Exception
    | `- … extends Exception
    `- Error implements Throwable
    |- TypeError extends Error
    |- ParseError extends Error
    |- ArithmeticError extends Error
    | `- DivisionByZeroError extends ArithmeticError
    `- AssertionError extends Error

    TypeError – для помилок, коли тип аргументів функції не збігається з переданими типом:

    try {
    (function(int $one, int $two) {
    return;
    })(‘one’, ‘two’);
    } catch (TypeError $e) {
    echo $e->getMessage();
    }

    ArithmeticError – можуть виникнути при математичних операціях, наприклад коли результат обчислення перевищує ліміт виділений для цілого числа:

    try {
    1 DivisionByZeroError – помилка ділення на нуль:

    try {
    1 / 0;
    } catch (ArithmeticError $e) {
    echo $e->getMessage();
    }

    AssertionError – рідкісний звір, з’являється коли умова заданий в assert() не виконується:

    ini_set(‘zend.assertions’, 1);
    ini_set(‘assert.exception’, 1);
    try {
    assert(1 === 0);
    } catch (AssertionError $e) {
    echo $e->getMessage();
    }

    При налаштуваннях production-серверів, директиви zend.assertions і assert.exception відключають, і це правильно

    Завдання
    Написати універсальний обробник помилок для PHP7, який буде відловлювати всі можливі виключення.

    При написанні даного розділу були використані матеріали з статті Throwable Exceptions and Errors in PHP 7

    Налагодження

    Іноді для налагодження коду потрібно відстежити що відбувалося зі змінною або об’єктом на певному етапі, для цих цілей є функція debug_backtrace() і debug_print_backtrace() які повернуть історію викликів функцій/методів у зворотному порядку:

    В результаті виконання функції debug_print_backtrace() буде виведено список викликів призвели нас до даній точці:

    #0 example() called at [/www/education/error/backtrace.php:10]
    #1 ExampleClass::method() called at [/www/education/error/backtrace.php:14]

    Перевірити код на наявність синтаксичних помилок можна за допомогою функції php_check_syntax() або ж команди php -l [шлях], але я не зустрічав використання оних.

    Assert

    Окремо хочу розповісти про такому екзотичному звіра як assert() в PHP, власне це шматочок контрактної методології програмування, і далі я розповім вам як я ніколи його не використовував 🙂

    Перший випадок – це коли вам треба написати TODO прямо в коді, та так, щоб не забути реалізувати заданий функціонал:

    // включаємо висновок помилок
    error_reporting(E_ALL);
    ini_set(‘display_errors’, 1);
    // включаємо asserts
    ini_set(‘zend.assertions’, 1);
    ini_set(‘assert.active’, 1);
    assert(false, “Remove it!”);

    В результаті виконання даного коду отримаємо E_WARNING:

    Warning: assert(): Remove it! failed

    PHP7 можна перемкнути в режим exception, і замість помилки буде завжди з’являтися виняток AssertionError:

    // включаємо asserts
    ini_set(‘zend.assertions’, 1);
    ini_set(‘assert.active’, 1);
    // перемикаємо на виключення
    ini_set(‘assert.exception’, 1);
    assert(false, “Remove it!”);

    В результаті очікувано отримуємо не спійманий AssertionError. При необхідності, можна викидати довільне виняток:

    assert(false, new Exception(“Remove it!”));

    Але я б рекомендував використовувати мітки @TODO, сучасні IDE відмінно з ними працюють, і вам не потрібно буде докладати додаткові зусилля і ресурси для роботи з ними

    Другий варіант використання – це створення якоїсь подібності TDD, але пам’ятайте – це лише подібність. Хоча, якщо постаратися, то можна отримати цікавий результат, який допоможе у тестуванні вашого коду:

    // callback-функція для виводу інформації в браузер
    function backlog($script, $line, $code, $message) {
    echo “

    $message

    “;
    highlight_string ($code);
    }
    // встановлюємо callback функцію
    assert_options(ASSERT_CALLBACK, ‘backlog’);
    // відключаємо висновок попереджень
    assert_options(ASSERT_WARNING, false);
    // пишемо перевірку і її опис
    assert(“sqr(4) == 16”, “When I send integer, function should return square of it”);
    // функція, яку перевіряємо
    function sqr($a) {
    return; // вона не працює
    }

    Третій теоретичний варіант – це безпосередньо контрактне програмування – коли ви описали правила використання своєї бібліотеки, але хочете точно переконається, що вас зрозуміли правильно, і в разі чого відразу вказати розробнику на помилку (я ось навіть не впевнений, що правильно його розумію, але приклад коду цілком робочий):

    /**
    * Налаштування з’єднання повинні передаватися в наступному вигляді
    *
    * [
    * ‘host’ => ‘localhost’,
    * ‘port’ => 3306,
    * ‘name’ => ‘dbname’,
    * ‘user’ => ‘root’,
    * ‘pass’ =>”
    * ]
    *
    * @param $settings
    */
    function setupDb ($settings) {
    // перевіряємо налаштування
    assert(isset($settings[‘host’]), ‘Db `host` is required’);
    assert(isset($settings[‘port’]) && is_int($settings[‘port’]), ‘Db `port` is required, should be integer’);
    assert(isset($settings[‘name’]), ‘Db `name` is required, should be integer’);
    // з’єднуємо з БД
    // …
    }
    setupDb([‘host’ => ‘localhost’]);

    Ніколи не використовуйте assert() для перевірки вхідних параметрів, адже фактично assert() інтерпретує строкову змінну (веде себе як eval()), а це загрожує PHP-ін’єкцією. І так, це правильна поведінка, т. к. просто відключивши assert’и все що передається всередину буде проігноровано, а якщо робити як у прикладі вище, то код буде виконуватися, а всередину відключеного assert’a буде переданий булевий результат виконання

    Якщо у вас є живий досвід використання assert() – поділіться зі мною, буду вдячний. І так, ось вам ще цікаво чтива по цій темі – PHP Assertions, з таким же питанням в кінці 🙂

    Висновок

    Я за вас напишу висновки з цієї статті:

    • Помилкам бій – їх не повинно бути у вашому коді
    • Використовуйте виключення – роботу з ними потрібно правильно організувати і буде щастя
    • Assert – дізналися про них, і добре

    P. S. Дякую Максиму Слєсаренко за допомогу в написанні статті