Глава 79 · От основ к практике
Исключения и ошибки: весь API Throwable, Exception, Error и SPL
От первого throw до выбора типа, цепочек причин, стека и всех предопределённых классов.
Из операции не пришёл результат: что передать вызывающему коду
От сбоя к понятному решению
Исключение несёт данные и меняет маршрут выполнения.
- Загрузка каталога
Операция начала работу, результат ещё не получен.
- throw new RuntimeException
Создан объект с описанием и сохранённой причиной.
- Выход к catch
Оставшаяся часть неудачного пути не выполняется.
- Проверка типа и причины
Обработчик решает остановить операцию или запланировать допустимый повтор.
- Понятный ответ и закрытый журнал
Посетителю не раскрывают стек, пути и секреты.
catch не делает автоматический rollback, повтор или восстановление состояния. Эти действия задаются явно согласно договору операции.
Покупатель указал ноль упаковок чая. Заказ пока нельзя оформлять, но нужно сохранить понятный путь исправления: сообщить об ошибке и продолжить работу формы. Начнём с одной проверки числа без собственного класса или сложного сервиса.
Объект исключения похож на карточку происшествия: тип, описание, место и путь вызовов. new InvalidArgumentException(...) создаёт карточку неверного входа; throw прерывает обычный путь. try отмечает участок, catch (InvalidArgumentException $error) получает подходящую ошибку, а ->getMessage() читает сообщение. Сам объект ничего не исправляет, не откатывает БД и не отправляет ответ.
- $quantity = 0 — вход в проверку
- Условие $quantity < 1 истинно → throw
- Строка Заказ готов к оформлению пропускается
- catch выводит Выберите хотя бы одну упаковку
- После catch выполнение продолжается: форму можно исправить
<?php
declare(strict_types=1);
$quantity = 0;
try {
echo "Проверяем количество чая\n";
if ($quantity < 1) { throw new InvalidArgumentException('Выберите хотя бы одну упаковку'); }
echo "Заказ готов к оформлению\n";
} catch (InvalidArgumentException $error) {
echo $error->getMessage(), "\n";
}
echo "Форма остаётся доступной для исправления\n";
Ожидаемый результат
Проверяем количество чая
Выберите хотя бы одну упаковку
Форма остаётся доступной для исправления
try начинает защищённый участок. throw прерывает его, поэтому строка об успешном оформлении не выполняется. catch выбирается по типу, получает объект ошибки и извлекает getMessage. Затем выполнение продолжается после блока; само исключение не рисует HTML-форму.
Далее мы используем функции и классы, чтобы показать настоящие границы приложения. Перед ними доступны вызов, параметры и return, класс и объект, try/catch/finally с простых примеров. Здесь справочник всех предопределённых типов этой ветви и их методов; безопасность вывода и жизненный цикл HTTP рассматриваются также в обработке ошибок.
Exception, Error и Throwable: два семейства одного договора
- Операция обнаруживает невозможность выполнить договор
- Создаёт подходящий объект Exception/Error
- throw прекращает обычный путь
- Стек ищет ближайший совместимый catch
- catch выбирает восстановление или понятный отказ; finally освобождает ресурс
Exception предназначен для отказов, которые могут возникать при нормальной работе: недоступность поставщика, непригодные данные. Error обычно сообщает о дефекте программы: неправильный тип, отсутствующий метод, невозможная операция. Оба семейства реализуют Throwable, поэтому catch (Throwable $error) ловит оба. catch (Exception $error) не ловит Error. Различие помогает выбрать действие; оно не гарантирует, что любая Exception безопасно игнорируется, а любой Error можно восстановить.
| Дерево | Что означает |
|---|---|
| Throwable → Exception | Операционные и пользовательские исключения; также ErrorException, ClosedGeneratorException, RequestParseBodyException, SPL LogicException/RuntimeException. |
| Throwable → Error | Ошибки выполнения/договора программы. TypeError → ArgumentCountError; ArithmeticError → DivisionByZeroError; CompileError → ParseError; также ValueError, AssertionError, UnhandledMatchError, FiberError. |
| Не все PHP-сбои — Throwable | Предупреждения, уведомления, часть фатальных/стартовых/компиляционных ошибок имеют иной путь обработки. set_error_handler имеет ограничения; catch не является защитой от любого сбоя процесса. |
<?php
foreach ([new Exception('Сеть'), new Error('Дефект кода')] as $error) {
try { throw $error; }
catch (Throwable $caught) {
echo get_class($caught), ':', $caught->getMessage(), "\n";
}
}
Ожидаемый результат
Exception:Сеть
Error:Дефект кода
Массив содержит два учебных объекта. get_class читает конкретный тип. На границе worker или HTTP-запроса общий catch полезен для завершения и закрытой диагностики; глубоко внутри не надо скрывать все Throwable и подменять результат пустым массивом.
<?php
try { throw new InvalidArgumentException('Количество не прошло проверку'); }
catch (InvalidArgumentException $error) { echo "Исправьте входные данные\n"; }
catch (Exception $error) { echo "Операция недоступна\n"; }
Ожидаемый результат
Исправьте входные данные
Первый подходящий catch выигрывает. Если поставить общий Exception раньше, специальная ветвь перестанет получать этот объект. Для повторов платёжных запросов дополнительно нужны идемпотентность и правила API.
Throwable: общий интерфейс без собственного new
Throwable — интерфейс, появившийся в PHP 7. Пользовательский класс не может напрямую написать implements Throwable: надо наследовать Exception либо подходящую разрешённую ветвь. У интерфейса нет собственного конструктора или свойств данных. Он требует getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious и __toString. С PHP 8 он также расширяет Stringable. Это позволяет функции диагностики принять оба семейства одним параметром.
<?php
function describeFailure(Throwable $error): string {
return get_class($error) . ': ' . $error->getMessage();
}
echo describeFailure(new RuntimeException('Таймаут')), "\n";
echo describeFailure(new TypeError('Неверный тип')), "\n";
Ожидаемый результат
RuntimeException: Таймаут
TypeError: Неверный тип
Параметр Throwable ограничивает вход объектами этого договора. return отдаёт строку вызывающему коду; echo находится снаружи, поэтому обработчик не отправляет текст посетителю сам.
<?php
declare(strict_types=1);
class SupplierUnavailable extends RuntimeException {}
function loadTitles(): array { throw new SupplierUnavailable('Поставщик не ответил'); }
try { loadTitles(); }
catch (SupplierUnavailable $error) { echo 'Каталог будет обновлён позже: ', $error->getMessage(), "\n"; }
catch (Throwable $error) { echo "Неожиданная ошибка каталога\n"; }
Ожидаемый результат
Каталог будет обновлён позже: Поставщик не ответил
Собственный тип наследует RuntimeException и тем самым Exception/Throwable. Его польза — отдельная реакция вызывающего кода. Обычный класс не может сам implements Throwable; расширяют разрешённое семейство Exception или Error. Общий catch ставится после конкретного.
Методы интерфейса: Throwable::getMessage, Throwable::getCode, Throwable::getFile, Throwable::getLine, Throwable::getTrace, Throwable::getTraceAsString, Throwable::getPrevious, Throwable::__toString.
Что хранится внутри Exception и Error
У Exception и Error одинаковая основная форма данных. Внешний код читает публичные методы, а не обращается к закрытым полям. protected означает доступ внутри класса/его наследников; private — внутреннее состояние. Текстовый synopsis официального справочника показывает смысл типов; не копируйте объявления свойств из новой версии в старую без проверки совместимости наследования.
| Поле | Доступ и смысл | Как прочитать |
|---|---|---|
| message | protected; текст описания, исходно пустой. | Exception::getMessage / Error::getMessage |
| code | protected; код источника, обычно 0. У некоторых потомков Exception возможен строковый SQLSTATE. | Exception::getCode / Error::getCode |
| file, line | protected; файл и строка появления объекта. ErrorException может хранить переданное место предупреждения. | Exception::getFile, Exception::getLine |
| trace | private; кадры вызовов. | Exception::getTrace / Error::getTrace |
| previous | private; предыдущий Throwable или null. | Exception::getPrevious / Error::getPrevious |
| string | private; внутреннее строковое представление. Не меняйте его вручную. | Exception::__toString / Error::__toString |
Объект исключения нельзя клонировать. Унаследованные геттеры Exception/Error final, поэтому их нельзя переопределять. Конструктор и __toString можно настраивать в своём классе; соблюдайте договор и сохраняйте причину. Создание исключения фиксирует стек и требует ресурсов, поэтому обычный выбор ветви/поиск без результата лучше описывать обычным условием или явно документированным null.
Exception: базовая карточка отказа приложения
Exception существует с PHP 5. Конструктор принимает message, code и previous; в современном PHP previous имеет тип ?Throwable. new возвращает объект, но конструктор сам не возвращает значение и не выбрасывает созданную Exception. Для нового типа прикладной ошибки часто достаточно extends RuntimeException с понятным именем.
<?php
declare(strict_types=1);
$error = new Exception('Не удалось сохранить заказ A-42', 41);
echo 'Сообщение: ', $error->getMessage(), "\n";
echo 'Код приложения: ', $error->getCode(), "\n";
echo $error->getPrevious() === null ? "Вложенная причина не передана\n" : "Есть вложенная причина\n";
$empty = new Exception();
echo $empty->getMessage() === '' ? "Без сообщения читателю нечего объяснить\n" : $empty->getMessage();
Ожидаемый результат
Сообщение: Не удалось сохранить заказ A-42
Код приложения: 41
Вложенная причина не передана
Без сообщения читателю нечего объяснить
Конструктор Exception принимает message, code и previous. По умолчанию это пустая строка, 0 и null. Код 41 здесь условный код приложения, а не HTTP-статус. Технические сообщения с путями, SQL и секретами не выдавайте посетителю напрямую.
<?php
class PresetFailure extends Exception {
protected $message = 'Базовое описание';
protected $code = 42;
}
$first = new PresetFailure();
$second = new PresetFailure('', 0);
$third = new PresetFailure('Другое описание', 7);
echo $first->getMessage(), ':', $first->getCode(), "\n";
echo '[', $second->getMessage(), ']:', $second->getCode(), "\n";
echo $third->getMessage(), ':', $third->getCode(), "\n";
Ожидаемый результат
Базовое описание:42
[]:42
Другое описание:7
Наблюдение PHP 8.5.11: пропущенное сообщение сохраняет предустановленное, явная пустая строка заменяет его; code=0 не перезаписывает предустановленный 42, а ненулевой 7 заменяет. Чтобы избежать неожиданного скрытого состояния, в новом коде обычно явно задают смысл через собственный конструктор и parent::__construct. Не делайте такой приём основой внешнего протокола кодов.
Error: увидеть дефект программы и сохранить диагностику
Error появился в PHP 7 и реализует Throwable независимо от Exception. Его можно поймать, но удачный catch не доказывает, что приложение осталось в согласованном состоянии. Например, часть изменения уже могла выполниться. Завершите текущую операцию корректно, сохраните безопасную диагностику и исправьте исходный дефект.
<?php
try { missingCatalogHelper(); }
catch (Error $error) {
echo get_class($error), "\n";
echo "Операция остановлена\n";
}
Ожидаемый результат
Error
Операция остановлена
В реальном проекте проверьте имя, namespace, Composer autoload и подключение кода. Убрать сообщение через пустой catch — не исправление.
<?php
try {
try { throw new Error('Ошибка реализации'); }
catch (Exception $error) { echo "Эта ветвь не сработает\n"; }
} catch (Throwable $error) { echo "Пойман общий Throwable\n"; }
Ожидаемый результат
Пойман общий Throwable
Вложенность показана только для сравнения двух договоров. Обычно обработчик границы один и выбирает конкретные действия в зависимости от типа.
ErrorException: превратить выбранное предупреждение в объект
ErrorException наследует Exception с PHP 5.1. Она добавляет protected severity — уровень E_*, читаемый getSeverity. Это отдельное поле от code приложения. Само создание ErrorException не меняет глобальную обработку PHP. Чтобы предупреждение стало исключением, нужно явно поставить set_error_handler и выбрать, какие уровни переводить.
Ниже function создаёт обработчик, который PHP вызовет с уровнем, текстом, файлом и строкой предупреждения. Проверка error_reporting учитывает текущую маску. return false оставляет стандартный путь PHP; throw передаёт выбранное предупреждение в catch. finally гарантирует восстановление обработчика при обычном выходе/исключении из блока.
- trigger_error → E_USER_WARNING
- set_error_handler получает четыре поля
- throw new ErrorException с исходным file/line
- catch ErrorException читает severity
- finally восстанавливает прежний обработчик
<?php
set_error_handler(static function (int $level, string $message, string $file, int $line): bool {
if (!(error_reporting() & $level) || $level !== E_USER_WARNING) {
return false;
}
throw new ErrorException($message, 0, $level, $file, $line);
});
try {
trigger_error('Учебный отказ источника', E_USER_WARNING);
} catch (ErrorException $error) {
echo $error->getMessage(), "\n";
echo $error->getSeverity() === E_USER_WARNING ? "Уровень: пользовательское предупреждение\n" : "Другой уровень\n";
} finally {
restore_error_handler();
}
Ожидаемый результат
Учебный отказ источника
Уровень: пользовательское предупреждение
Обработчик переводит только выбранный E_USER_WARNING, сохраняя сообщение, уровень, файл и строку. Возврат false оставляет остальные события штатному механизму. finally восстанавливает предыдущий обработчик; глобально превращать любую диагностику в исключение без политики опасно для совместимости.
Контракты: ErrorException::__construct, ErrorException::getSeverity. Другие методы унаследованы от Exception. Сравните set_error_handler и уровни E_*.
SPL: выбрать смысл ошибки, чтобы вызывающий код понимал отказ
Названия SPL помогают описать смысл отказа, но PHP не угадывает ваш бизнес-договор. Следующие пары показывают сначала реальное условие, затем ту же операцию с успешным и ошибочным входом. Выбирайте тип последовательно: отсутствие ключа, неверный вход и неожиданный ответ зависимости требуют разных действий.
SPL предоставляет стандартные имена для типичных нарушений договоров. Эти классы встроены в современный PHP вместе с SPL; они не автоматически следят за каждым массивом или функцией. Большинство учебных проверок ниже явно выбрасывает нужный тип. Один и тот же бытовой случай можно описать разными API, но в пределах библиотеки выбор должен быть последовательным.
| Ветвь | Дочерние типы | Практический вопрос |
|---|---|---|
| LogicException | BadFunctionCallException → BadMethodCallException; DomainException; InvalidArgumentException; LengthException; OutOfRangeException. | Нарушил ли вызывающий код известные правила или последовательность действий? |
| RuntimeException | OutOfBoundsException; OverflowException; RangeException; UnderflowException; UnexpectedValueException. | Что случилось с текущим источником, результатом или состоянием контейнера во время операции? |
Различайте похожие имена: OutOfRange — недопустимый индекс по известному договору, OutOfBounds — ключ вне текущего набора; Domain — недопустимое значение предметной области, Range — вычисленный результат вне диапазона; InvalidArgument — неподходящий вход, UnexpectedValue — неожиданный результат зависимости. Конкретный контракт библиотеки важнее догадки по английскому слову.
ArgumentCountError: Число аргументов не соответствует вызову
Число аргументов не соответствует вызову.
| Устройство | Правило |
|---|---|
| Иерархия | ArgumentCountError extends TypeError; PHP 7.1+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
Аргумент — значение между скобками при вызове. Обязательный параметр без значения по умолчанию требует аргумента. Этот тип возникает при нехватке аргументов пользовательской функции и при лишних аргументах встроенной невариативной функции. Не переносите правило о лишних аргументах на все пользовательские функции: они могут принимать дополнительные позиционные аргументы без этой ошибки.
- greeting() без значения
- Параметру $name не хватает аргумента
- ArgumentCountError передан в catch
- Исправляем вызов greeting("Анна")
<?php
declare(strict_types=1);
function greeting(string $name): string { return 'Здравствуйте, ' . $name; }
try { greeting(); }
catch (ArgumentCountError $error) { echo "Не передан обязательный аргумент
"; }
Ожидаемый результат
Не передан обязательный аргумент
Перехват здесь показывает тип ошибки. В приложении исправляют место вызова или задают осмысленное значение по умолчанию, если аргумент действительно необязателен.
<?php
declare(strict_types=1);
try { strlen('SKU', 'extra'); }
catch (ArgumentCountError $error) { echo "strlen принимает одну строку
"; }
Ожидаемый результат
strlen принимает одну строку
Перед изменением вызова проверьте сигнатуру: дополнительное значение не станет автоматически настройкой функции.
Сравните: параметры, аргументы и значения по умолчанию, TypeError.
TypeError: Тип значения нарушает объявленный договор
Тип значения нарушает объявленный договор.
| Устройство | Правило |
|---|---|
| Иерархия | TypeError extends Error; PHP 7+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
Проверяться может параметр, возвращаемое значение или типизированное свойство. int означает целое, string — строку; : int после параметров — тип результата. strict_types=1 усиливает проверку скалярных вызовов из этого файла, но не заменяет проверку внешнего JSON или формы. В нестрогом режиме некоторые скаляры могут преобразоваться.
- doubleQuantity("4") в строгом файле
- Параметр требует int, пришла string
- TypeError до выполнения расчёта
- Проверяем и нормализуем вход отдельно
<?php
declare(strict_types=1);
function doubleQuantity(int $quantity): int { return $quantity * 2; }
try { doubleQuantity('4'); }
catch (TypeError $error) { echo "Ожидалось целое число
"; }
Ожидаемый результат
Ожидалось целое число
В этом самостоятельном файле включён strict_types=1. На границе формы сначала проверяют текст по правилам целого количества и лишь затем преобразуют.
<?php
declare(strict_types=1);
function itemCount(): int { return 'many'; }
try { itemCount(); }
catch (TypeError $error) { echo "Функция вернула неверный тип
"; }
Ожидаемый результат
Функция вернула неверный тип
Это ошибка реализации функции. Перехват не превращает many в корректное количество. Для typed-свойства аналогичная проверка сработает при несовместимом присваивании.
Сравните: типы данных, ValueError: тип подходит, значение нет.
ValueError: Тип подходит, но конкретное значение недопустимо
Тип подходит, но конкретное значение недопустимо.
| Устройство | Правило |
|---|---|
| Иерархия | ValueError extends Error; PHP 8+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
Например, разделитель explode должен быть строкой, но пустая строка не определяет границы частей. Это иной случай, чем передать массив вместо строки. Точный тип и версию ошибки смотрите у конкретной функции: до PHP 8 ряд операций предупреждал и возвращал специальное значение.
- explode("", "A-42")
- Тип string подходит, пустой разделитель запрещён
- ValueError
- Выбираем осмысленный разделитель или другую операцию
<?php
declare(strict_types=1);
try { explode('', 'A-42'); }
catch (ValueError $error) { echo "Нужен непустой разделитель
"; }
Ожидаемый результат
Нужен непустой разделитель
Перед вызовом в настройках интеграции проверьте, что выбран реальный разделитель. Для разбора символов существуют str_split/mb_str_split с другим договором.
<?php
declare(strict_types=1);
try { array_chunk([1, 2, 3], 0); }
catch (ValueError $error) { echo "Размер порции должен быть больше нуля
"; }
Ожидаемый результат
Размер порции должен быть больше нуля
В пакетной отправке лимит 0 не означает «без лимита». Задайте отдельный договор такой настройки, а в array_chunk передавайте допустимый размер.
Сравните: операции с массивами, разделение строк.
ArithmeticError: Арифметическая операция не имеет допустимого результата в своём договоре
Арифметическая операция не имеет допустимого результата в своём договоре.
| Устройство | Правило |
|---|---|
| Иерархия | ArithmeticError extends Error; PHP 7+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
Это базовый тип для ряда арифметических ошибок, включая DivisionByZeroError. Не любое вычисление за пределами int вызывает его: обычная арифметика может перейти во float. intdiv требует целочисленного результата; отрицательный сдвиг битов тоже недопустим.
- intdiv(PHP_INT_MIN, -1)
- Положительный результат не помещается в int
- ArithmeticError
- Выбираем подходящий диапазон/арифметику
<?php
declare(strict_types=1);
try { $mask = 1 << -1; }
catch (ArithmeticError $error) { echo "Нельзя сдвинуть на отрицательное число битов
"; }
Ожидаемый результат
Нельзя сдвинуть на отрицательное число битов
Оператор << сдвигает биты влево. Для выбора направления нужен другой оператор >> с неотрицательным количеством, а не отрицательная настройка.
<?php
declare(strict_types=1);
try { intdiv(PHP_INT_MIN, -1); }
catch (ArithmeticError $error) { echo "Результат вне диапазона int
"; }
Ожидаемый результат
Результат вне диапазона int
Отрицательная граница int по модулю больше положительной. Её деление на −1 невозможно представить тем же int. Для больших точных чисел выбирают соответствующую библиотеку, а не незаметную потерю точности во float.
Сравните: DivisionByZeroError, диапазон int и float.
DivisionByZeroError: Делитель оказался равен нулю
Делитель оказался равен нулю.
| Устройство | Правило |
|---|---|
| Иерархия | DivisionByZeroError extends ArithmeticError; PHP 7+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
В современном PHP обычное деление /, остаток % и intdiv при нулевом делителе приводят к этой ошибке. Для / это поведение PHP 8; старые версии могли выдавать предупреждение. fdiv имеет другой договор IEEE 754 и может вернуть INF/NAN. Выбор функции должен соответствовать смыслу данных.
- $sum = 120, $count = 0
- $sum / $count не определяет среднее
- DivisionByZeroError
- Для пустой выборки показываем «нет данных»
<?php
declare(strict_types=1);
$sum = 120;
$count = 0;
try { $average = $sum / $count; }
catch (DivisionByZeroError $error) { echo "Нет наблюдений для среднего
"; }
Ожидаемый результат
Нет наблюдений для среднего
Для ожидаемого пустого набора лучше заранее проверить count и показать «нет данных». Значение 0 в роли среднего скрыло бы разницу между пустым набором и реальным нулём.
<?php
declare(strict_types=1);
$prices = [];
if ($prices === []) {
echo "Нет данных
";
} else {
echo array_sum($prices) / count($prices), "
";
}
Ожидаемый результат
Нет данных
Проверка пустоты — нормальная ветвь задачи. Исключение нужно для неожиданного нарушения договора или границы библиотеки.
Сравните: ArithmeticError, операторы арифметики.
AssertionError: Внутренняя проверка assert обнаружила нарушенное предположение
Внутренняя проверка assert обнаружила нарушенное предположение.
| Устройство | Правило |
|---|---|
| Иерархия | AssertionError extends Error; PHP 7+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
assert проверяет утверждение разработчика, например «после нормализации список непуст». Проверки могут быть отключены через zend.assertions, поэтому ими нельзя защищать форму, права доступа или платёж. AssertionError можно создать явно; это не включает выключенные assert. Для включённого assert поведение в современных настройках — выбрасывание AssertionError.
- Разработчик утверждает: набор заполнен
- При включённом assert условие false
- AssertionError описывает нарушенный инвариант
- Исправляем внутреннюю логику
<?php
declare(strict_types=1);
try { throw new AssertionError('Нарушен внутренний инвариант'); }
catch (AssertionError $error) { echo $error->getMessage(), "
"; }
Ожидаемый результат
Нарушен внутренний инвариант
Пример учит типу и catch; он не доказывает, что assert включён на вашем сервере.
<?php
declare(strict_types=1);
if ((int) ini_get('zend.assertions') !== 1) {
echo "Для опыта нужен zend.assertions=1
";
} else {
try { assert(false, 'Ожидался заполненный набор'); }
catch (AssertionError $error) { echo $error->getMessage(), "
"; }
}
Ожидаемый результат
Ожидался заполненный набор
Этот результат проверен в локальном CLI с zend.assertions=1. На среде с отключёнными assertions выводится диагностическая первая ветвь. Проверку пользовательского ввода оформляют обычным if, который нельзя вырезать настройкой.
Сравните: assert и обработка ошибок.
CompileError: Некоторые ошибки компиляции представлены объектом
Некоторые ошибки компиляции представлены объектом.
| Устройство | Правило |
|---|---|
| Иерархия | CompileError extends Error; PHP 7.3+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
PHP сначала разбирает/компилирует файл, затем выполняет его инструкции. Если ошибка находится в основном файле, его try ещё не исполняется. CompileError охватывает только часть компиляционных сбоев. Например, break вне цикла может завершить выполнение фатальной ошибкой даже внутри eval: обещать catch для любого плохого PHP нельзя.
- Выполняющийся код разбирает дополнительный фрагмент
- Часть ошибок представлена Throwable
- CompileError может принять дочерний ParseError
- Другие фатальные ошибки этим не становятся перехватываемыми
<?php
declare(strict_types=1);
try { eval('function broken( {'); }
catch (CompileError $error) { echo get_class($error), "
"; }
Ожидаемый результат
ParseError
eval здесь получает постоянную ошибочную учебную строку. Никогда не передавайте в eval текст формы, API или редактируемой пользователем настройки. ParseError — потомок CompileError начиная с PHP 7.3.
<?php
declare(strict_types=1);
try {
throw new CompileError('Учебная ошибка подготовки шаблона');
} catch (Exception $error) {
echo "Это ветвь отказов приложения\n";
} catch (Error $error) {
echo 'Шаблон не подготовлен: ', $error->getMessage(), "\n";
}
Ожидаемый результат
Шаблон не подготовлен: Учебная ошибка подготовки шаблона
Это явная локальная модель выбора catch, а не обещание перехватить любую ошибку компиляции основного файла. Если основной файл не разобран, его try ещё не начал работать. ParseError отдельного загружаемого файла можно перехватить уже выполняющимся загрузчиком — следующий опыт делает именно это.
Сравните: ParseError, подготовка среды.
ParseError: Синтаксис PHP не удалось разобрать
Синтаксис PHP не удалось разобрать.
| Устройство | Правило |
|---|---|
| Иерархия | ParseError extends CompileError; PHP 7+; родитель CompileError с 7.3. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
До PHP 7.3 этот класс непосредственно наследовал Error. Ловить синтаксическую ошибку можно в уже выполняющемся внешнем коде при разборе дополнительного фрагмента/файла. Ошибочный основной файл не сможет исполнить собственный try. Это повод исправить код, а не продолжать важную операцию с частично загруженной логикой.
- Загрузчик вызывает require broken.php
- PHP разбирает синтаксис нового файла
- return (1 + ); не завершает выражение
- ParseError возвращается во внешний catch
<?php
declare(strict_types=1);
try { eval('return (1 + );'); }
catch (ParseError $error) { echo "Синтаксис фрагмента повреждён
"; }
Ожидаемый результат
Синтаксис фрагмента повреждён
Строка константная и предназначена только для опыта. Для формул, правил и шаблонов пользователя применяют ограниченный формат данных/выражений вместо выполнения произвольного PHP.
<?php
declare(strict_types=1);
try { require __DIR__ . '/broken.php'; }
catch (ParseError $error) { echo basename($error->getFile()), "
"; }
Ожидаемый результат
broken.php
В скачиваемом самостоятельном примере есть отдельный broken.php с намеренно повреждённым синтаксисом. Проверен реальный require, а не только создание объекта.
Сравните: синтаксис PHP, getFile и getLine.
UnhandledMatchError: Для значения не нашлась ветвь match
Для значения не нашлась ветвь match.
| Устройство | Правило |
|---|---|
| Иерархия | UnhandledMatchError extends Error; PHP 8+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
match сравнивает строго, через ===. Если нет совпадения и default, возникает UnhandledMatchError. Если неизвестный вариант должен быть ошибкой, это полезный сигнал. Если неизвестное значение ожидаемо, задайте осмысленную ветвь default или проверьте вход раньше.
- $method = "drone"
- match знает только pickup и courier
- Совпадения и default нет
- UnhandledMatchError сообщает о пропущенном варианте
<?php
declare(strict_types=1);
$method = 'drone';
try { $label = match ($method) { 'pickup' => 'Самовывоз', 'courier' => 'Курьер' }; }
catch (UnhandledMatchError $error) { echo "Неизвестная доставка
"; }
Ожидаемый результат
Неизвестная доставка
Не подставляйте автоматически дешёвый вариант доставки: это может изменить стоимость заказа. Разделяйте неизвестные данные и допустимый выбор.
<?php
declare(strict_types=1);
$status = '1';
$label = match ($status) { 1 => 'Числовой статус', default => 'Нужна нормализация входа' };
echo $label, "
";
Ожидаемый результат
Нужна нормализация входа
default здесь возвращает понятное описание для опыта. В обработчике API нормализуют тип по его договору; обычное приведение любого текста в int может скрыть ошибку.
Сравните: условия и match.
ClosedGeneratorException: Делегирующий генератор потерял результат аварийно закрытого источника
Делегирующий генератор потерял результат аварийно закрытого источника.
| Устройство | Правило |
|---|---|
| Иерархия | ClosedGeneratorException extends Exception; PHP 5.5+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
Общее описание PHP.net говорит о получении значения из закрытого генератора. На практике важно различать операции: current после нормального завершения может вернуть null, а getReturn до завершения даёт другую ошибку. Здесь воспроизведён точный случай: внешний код отдельно продвинул вложенный генератор, тот упал, а yield from затем пытается продолжить работу с ним.
- $outer делегирует $source через yield from
- Чужой вызов $source->next() закрывает источник ошибкой
- $outer->next() пытается продолжить делегирование
- ClosedGeneratorException: итог источника недоступен
<?php
declare(strict_types=1);
function feed(): Generator { yield 'SKU-1'; throw new RuntimeException('Источник упал'); }
function relay(Generator $source): Generator { yield from $source; }
$source = feed();
$outer = relay($source);
echo $outer->current(), "
";
try { $source->next(); } catch (RuntimeException $error) { echo "Источник закрыт ошибкой
"; }
try { $outer->next(); } catch (ClosedGeneratorException $error) { echo "У делегата нет результата
"; }
Ожидаемый результат
SKU-1
Источник закрыт ошибкой
У делегата нет результата
Не управляйте одним генератором независимо из нескольких частей приложения. Пусть делегирующий код владеет продвижением источника; для повторного прохода создавайте новый генератор.
<?php
declare(strict_types=1);
function oneItem(): Generator { yield 'SKU-1'; return 'done'; }
$items = oneItem();
foreach ($items as $item) { echo $item, "
"; }
var_dump($items->current());
echo $items->getReturn(), "
";
Ожидаемый результат
SKU-1
NULL
done
Не пишите catch ClosedGeneratorException вокруг любого current в предположении, что завершение всегда вызывает исключение. Смотрите договор конкретного метода.
Сравните: полный API Generator.
FiberError: Операция Fiber недопустима в текущем состоянии
Операция Fiber недопустима в текущем состоянии.
| Устройство | Правило |
|---|---|
| Иерархия | FiberError extends Error; PHP 8.1+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
Этот класс final и предназначен для внутренних ошибок управления Fiber. Его публичный конструктор специально запрещает ручное создание и сам выбрасывает Error. Используйте отдельный пользовательский тип для отказа задачи; FiberError сообщает о неверном переходе состояния, например suspend вне Fiber или повторном start.
- Основной поток выполняет Fiber::suspend()
- Активной Fiber сейчас нет
- Движок создаёт FiberError
- catch показывает неверный переход состояния
<?php
declare(strict_types=1);
try { Fiber::suspend(); }
catch (FiberError $error) { echo "Сейчас нет выполняющейся Fiber
"; }
Ожидаемый результат
Сейчас нет выполняющейся Fiber
Fiber::suspend возвращает управление тому, кто запустил Fiber. В основном потоке такого владельца нет.
<?php
declare(strict_types=1);
$fiber = new Fiber(static function (): void {});
$fiber->start();
try { $fiber->start(); }
catch (FiberError $error) { echo "Fiber уже запускалась
"; }
Ожидаемый результат
Fiber уже запускалась
Для новой задачи создают новую Fiber. Для приостановленной используют resume; для завершённой возобновление тоже недопустимо.
Сравните: состояния и методы Fiber.
RequestParseBodyException: PHP не смог разобрать тело HTTP-запроса по его типу и ограничениям
PHP не смог разобрать тело HTTP-запроса по его типу и ограничениям.
| Устройство | Правило |
|---|---|
| Иерархия | RequestParseBodyException extends Exception; PHP 8.4+. |
| Поля | Общие поля message/code/file/line/trace/previous. Собственные поля здесь не добавлены. |
| Методы | __construct, getMessage, getCode, getFile, getLine, getTrace, getTraceAsString, getPrevious, __toString, __clone. |
request_parse_body разбирает поддерживаемые тела формы по Content-Type; неподходящий/отсутствующий тип или нарушения ограничений могут дать этот класс. Он не заменяет json_decode для application/json. После чтения потока данные нельзя бездумно прочитать повторно. Реальная обработка HTTP и лимиты проверяются в подходящем SAPI.
- request_parse_body читает тип тела
- В CLI Content-Type отсутствует
- RequestParseBodyException
- У реального HTTP-запроса отдельно проверяем поддерживаемый формат
<?php
declare(strict_types=1);
function parseDemoBody(): array
{
throw new RequestParseBodyException('Неподдерживаемый формат тела');
}
try { parseDemoBody(); }
catch (RequestParseBodyException $error) {
echo "Запрос отклонён: проверьте Content-Type и формат тела\n";
}
Ожидаемый результат
Запрос отклонён: проверьте Content-Type и формат тела
PHP 8.4+. Это самостоятельная демонстрация выбора типа catch на явно созданном объекте; она не выполняет HTTP-разбор. Следующий CLI-опыт показывает только отсутствие Content-Type. Полный HTTP-сценарий с реальным телом относится к главе о жизненном цикле HTTP и проверяется серверным запуском.
<?php
declare(strict_types=1);
try { request_parse_body(); }
catch (RequestParseBodyException $error) { echo "Нет данных о типе тела
"; }
Ожидаемый результат
Нет данных о типе тела
Самостоятельный CLI-пример не имеет HTTP Content-Type. Проверен именно этот путь; успешный multipart и серверные лимиты этим опытом не подтверждены.
Сравните: HTML-форма и её данные, суперглобальные массивы.
LogicException: Нарушен внутренний договор программы
Практическая потребность. Публиковать только проверенную карточку. Это внутренний порядок действий: опубликованию предшествует проверка. Повтор того же вызова без исправления состояния не поможет.
Нарушен внутренний договор программы. Такое условие обычно исправляют в коде, а не повторяют ту же операцию. Это базовый класс ветви, а не автоматический анализ логики PHP.
| Договор | Описание |
|---|---|
| Иерархия | LogicException extends Exception, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- validated = false, вызвана публикация
- if обнаруживает нарушение
- Код явно выполняет throw new LogicException
- catch получает сообщение: Сначала проверьте данные
<?php
declare(strict_types=1);
try {
$validated = false;
if (!$validated) { throw new LogicException('Карточка ещё не проверена'); }
echo "Карточка опубликована\n";
} catch (LogicException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Карточка ещё не проверена
Сначала конкретное условие задачи, затем LogicException переносит причину отказа к catch. Это внутренний порядок действий: опубликованию предшествует проверка. Повтор того же вызова без исправления состояния не поможет.
<?php
declare(strict_types=1);
function publishCard(bool $validated, string $title): string
{
if (!$validated) { throw new LogicException('Сначала проверьте карточку'); }
return 'Опубликовано: ' . $title;
}
echo publishCard(true, 'Чай'), "\n";
try { echo publishCard(false, 'Кофе'); }
catch (LogicException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Опубликовано: Чай
Сначала проверьте карточку
Это внутренний порядок действий: опубликованию предшествует проверка. Повтор того же вызова без исправления состояния не поможет. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Внутренний код нарушил заранее известный порядок или invariant. |
| Когда не подходит | Ошибка пользователя формы, которую можно штатно показать рядом с полем. |
| Что должен решить обработчик | Исправить вызывающий код; не повторять ту же операцию автоматически. |
| Откуда методы | LogicException extends Exception; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The LogicException class.
BadFunctionCallException: Запрошена недоступная операция-функция
Практическая потребность. Выбирать только зарегистрированный обработчик экспорта. Исключение выбирает диспетчер вашего приложения. Обычный вызов несуществующей PHP-функции приводит к Error, а не автоматически к BadFunctionCallException.
Запрошена недоступная операция-функция. Применяется к договору вызова: отсутствующий callback или запрещённый набор аргументов в вашем диспетчере. Обычный вызов несуществующей PHP-функции сам по себе даёт Error, а не этот SPL-класс.
| Договор | Описание |
|---|---|
| Иерархия | BadFunctionCallException extends LogicException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- Запрошен compress, в реестре есть только upper
- if обнаруживает нарушение
- Код явно выполняет throw new BadFunctionCallException
- catch получает сообщение: Нет обработчика: compress
<?php
declare(strict_types=1);
try {
$format = 'pdf';
$handlers = ['text' => static fn(string $title): string => 'Товар: ' . $title];
if (!isset($handlers[$format])) { throw new BadFunctionCallException('Формат экспорта не подключён: ' . $format); }
echo $handlers[$format]('Чай'), "\n";
} catch (BadFunctionCallException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Формат экспорта не подключён: pdf
Сначала конкретное условие задачи, затем BadFunctionCallException переносит причину отказа к catch. Исключение выбирает диспетчер вашего приложения. Обычный вызов несуществующей PHP-функции приводит к Error, а не автоматически к BadFunctionCallException.
<?php
declare(strict_types=1);
function exportTitle(string $format, string $title): string
{
$handlers = ['text' => static fn(string $value): string => 'Товар: ' . $value];
if (!isset($handlers[$format])) { throw new BadFunctionCallException('Неизвестный экспорт: ' . $format); }
return $handlers[$format]($title);
}
echo exportTitle('text', 'Чай'), "\n";
try { echo exportTitle('pdf', 'Чай'); }
catch (BadFunctionCallException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Товар: Чай
Неизвестный экспорт: pdf
Исключение выбирает диспетчер вашего приложения. Обычный вызов несуществующей PHP-функции приводит к Error, а не автоматически к BadFunctionCallException. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Ваш диспетчер получил имя отсутствующего/недопустимого callback. |
| Когда не подходит | Прямой вызов несуществующей PHP-функции: язык выдаст Error. |
| Что должен решить обработчик | Исправить реестр или имя операции; пользовательский ввод сверять с белым списком. |
| Откуда методы | BadFunctionCallException extends LogicException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The BadFunctionCallException class.
BadMethodCallException: Объект получил неподдерживаемый вызов метода
Практическая потребность. Отклонить неподдерживаемую операцию адаптера. Совместимый адаптер поддерживает ровно одно старое имя; опечатка не превращается в произвольное действие. Без __call недоступный метод сам по себе относится к ветви Error.
Объект получил неподдерживаемый вызов метода. Подходит для своего динамического API, например __call. Реальный несуществующий метод без __call приводит к Error; PHP не выбирает этот SPL-тип автоматически.
| Договор | Описание |
|---|---|
| Иерархия | BadMethodCallException extends BadFunctionCallException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- ReportApi получил неподдерживаемый eraseAll()
- __call отклоняет недоступный метод
- Код явно выполняет throw new BadMethodCallException
- catch получает сообщение: Метод не поддерживается: eraseAll
<?php
declare(strict_types=1);
try {
class ReportAdapter
{
public function __call(string $name, array $arguments): mixed
{
throw new BadMethodCallException('Отчёт не поддерживает: ' . $name);
}
}
(new ReportAdapter())->eraseAll();
} catch (BadMethodCallException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Отчёт не поддерживает: eraseAll
Сначала конкретное условие задачи, затем BadMethodCallException переносит причину отказа к catch. Совместимый адаптер поддерживает ровно одно старое имя; опечатка не превращается в произвольное действие. Без __call недоступный метод сам по себе относится к ветви Error.
<?php
declare(strict_types=1);
class ReportAdapter
{
public function title(): string { return 'Каталог товаров'; }
public function __call(string $name, array $arguments): mixed
{
if ($name === 'oldTitle' && $arguments === []) { return $this->title(); }
throw new BadMethodCallException('Неизвестный метод: ' . $name);
}
}
$report = new ReportAdapter();
echo $report->oldTitle(), "\n";
try { $report->oldTtile(); }
catch (BadMethodCallException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Каталог товаров
Неизвестный метод: oldTtile
Совместимый адаптер поддерживает ровно одно старое имя; опечатка не превращается в произвольное действие. Без __call недоступный метод сам по себе относится к ветви Error. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Динамический API (__call) отклонил неизвестную операцию. |
| Когда не подходит | Обычный отсутствующий метод без __call: PHP выдаёт Error. |
| Что должен решить обработчик | Исправить имя/контракт; для известного API предпочесть явные методы. |
| Откуда методы | BadMethodCallException extends BadFunctionCallException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The BadMethodCallException class.
DomainException: Значение вне допустимого набора предметной области
Практическая потребность. Допустить только известный способ доставки. Тип string подходит, но значение не принадлежит выбранной предметной области. Этот смысл отличается от сбоя сети и отсутствующего элемента конкретного массива.
Значение вне допустимого набора предметной области. Тип может быть правильным, но смысл неверен: у доставки допустимы только известные коды. Для автоматически проверяемых параметров встроенных функций чаще встречается ValueError. Выбор собственного типа должен быть последовательным.
| Договор | Описание |
|---|---|
| Иерархия | DomainException extends LogicException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- delivery = "teleport", разрешены pickup/courier
- if обнаруживает нарушение
- Код явно выполняет throw new DomainException
- catch получает сообщение: Неизвестный способ доставки
<?php
declare(strict_types=1);
try {
$delivery = 'teleport';
if (!in_array($delivery, ['pickup', 'courier'], true)) {
throw new DomainException('Доставка teleport недоступна');
}
echo "Способ выбран\n";
} catch (DomainException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Доставка teleport недоступна
Сначала конкретное условие задачи, затем DomainException переносит причину отказа к catch. Тип string подходит, но значение не принадлежит выбранной предметной области. Этот смысл отличается от сбоя сети и отсутствующего элемента конкретного массива.
<?php
declare(strict_types=1);
function deliveryLabel(string $code): string
{
return match ($code) {
'pickup' => 'Самовывоз', 'courier' => 'Курьер',
default => throw new DomainException('Неизвестный способ доставки: ' . $code),
};
}
echo deliveryLabel('pickup'), "\n";
try { echo deliveryLabel('teleport'); }
catch (DomainException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Самовывоз
Неизвестный способ доставки: teleport
Тип string подходит, но значение не принадлежит выбранной предметной области. Этот смысл отличается от сбоя сети и отсутствующего элемента конкретного массива. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Тип значения верен, но значение не входит в предметный набор. |
| Когда не подходит | Сбой сети или отсутствующая запись в коллекции. |
| Что должен решить обработчик | Вернуть ошибку доменного правила; перечислить допустимые варианты. |
| Откуда методы | DomainException extends LogicException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The DomainException class.
InvalidArgumentException: Аргумент пользовательской операции имеет недопустимый тип или форму
Практическая потребность. Разобрать форму настроек на входе в операцию. mixed намеренно оставляет ручную границу разбора. Если объявить параметр array, неподходящий тип будет проверять сам PHP через TypeError. Диапазон секунд — следующий отдельный договор.
Аргумент пользовательской операции имеет недопустимый тип или форму. Официальный смысл — неожиданный тип аргумента. Его выбирает ваш код при ручной проверке. Если PHP проверяет объявленный тип параметра, он сам выдаст TypeError. Не называйте любой сетевой отказ неверным аргументом.
| Договор | Описание |
|---|---|
| Иерархия | InvalidArgumentException extends LogicException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- Вместо массива настроек передана строка
- if обнаруживает нарушение
- Код явно выполняет throw new InvalidArgumentException
- catch получает сообщение: Настройки должны быть массивом
<?php
declare(strict_types=1);
try {
$options = 'timeout=5';
if (!is_array($options)) { throw new InvalidArgumentException('Настройки должны быть массивом'); }
echo "Настройки приняты\n";
} catch (InvalidArgumentException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Настройки должны быть массивом
Сначала конкретное условие задачи, затем InvalidArgumentException переносит причину отказа к catch. mixed намеренно оставляет ручную границу разбора. Если объявить параметр array, неподходящий тип будет проверять сам PHP через TypeError. Диапазон секунд — следующий отдельный договор.
<?php
declare(strict_types=1);
function timeoutFrom(mixed $options): int
{
if (!is_array($options) || !isset($options['timeout']) || !is_int($options['timeout'])) {
throw new InvalidArgumentException('Нужен массив с целым timeout');
}
return $options['timeout'];
}
echo 'Тайм-аут: ', timeoutFrom(['timeout' => 5]), " с\n";
try { timeoutFrom('timeout=5'); }
catch (InvalidArgumentException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Тайм-аут: 5 с
Нужен массив с целым timeout
mixed намеренно оставляет ручную границу разбора. Если объявить параметр array, неподходящий тип будет проверять сам PHP через TypeError. Диапазон секунд — следующий отдельный договор. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Ваш метод вручную отвергает форму/тип аргумента. |
| Когда не подходит | Нарушение объявленного type hint: PHP выдаёт TypeError. |
| Что должен решить обработчик | Исправить вызов или валидировать вход на границе. |
| Откуда методы | InvalidArgumentException extends LogicException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The InvalidArgumentException class.
LengthException: Длина нарушает установленный договор
Практическая потребность. Проверить длину ASCII-кода наклейки. strlen считает байты. Здесь вход по договору ASCII; допустимые символы проверяются отдельно. Для пользовательского русского названия нельзя называть этот результат числом букв или графем.
Длина нарушает установленный договор. Применяется к недопустимой длине значения/контейнера. Сначала решите, что считается длиной: байты, Unicode-кодовые точки или видимые графемы. strlen для UTF-8 не равен числу букв.
| Договор | Описание |
|---|---|
| Иерархия | LengthException extends LogicException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- Код AB занимает 2 байта вместо 6
- if обнаруживает нарушение
- Код явно выполняет throw new LengthException
- catch получает сообщение: ASCII-код должен содержать 6 байтов
<?php
declare(strict_types=1);
try {
$code = 'AB';
if (strlen($code) !== 6) { throw new LengthException('Код наклейки должен занимать 6 байтов'); }
echo 'Наклейка: ', $code, "\n";
} catch (LengthException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Код наклейки должен занимать 6 байтов
Сначала конкретное условие задачи, затем LengthException переносит причину отказа к catch. strlen считает байты. Здесь вход по договору ASCII; допустимые символы проверяются отдельно. Для пользовательского русского названия нельзя называть этот результат числом букв или графем.
<?php
declare(strict_types=1);
function labelCode(string $code): string
{
if (strlen($code) !== 6) { throw new LengthException('Нужен шестибайтовый ASCII-код'); }
return 'Наклейка: ' . $code;
}
echo labelCode('TEA042'), "\n";
try { echo labelCode('AB'); }
catch (LengthException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Наклейка: TEA042
Нужен шестибайтовый ASCII-код
strlen считает байты. Здесь вход по договору ASCII; допустимые символы проверяются отдельно. Для пользовательского русского названия нельзя называть этот результат числом букв или графем. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Длина нарушает явный договор контейнера или значения. |
| Когда не подходит | Строка просто пустая, если пустое значение разрешено. |
| Что должен решить обработчик | Уточнить единицу длины: байты, кодовые точки, графемы, элементы. |
| Откуда методы | LengthException extends LogicException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The LengthException class.
OutOfRangeException: Индекс вне диапазона, разрешённого договором
Практическая потребность. Выбрать номер месяца отчёта в разрешённом диапазоне. Допустимый диапазон индекса известен из договора заранее. Название SPL-ветви не означает, что PHP-компилятор автоматически выбросит это исключение: проверка написана явно.
Индекс вне диапазона, разрешённого договором. Логическая ошибка адресации известного диапазона, например номер месяца 13. Упоминание compile-time в описании PHP.net описывает смысл ошибки договора, а не обещает, что PHP-компилятор сам выбросит этот класс.
| Договор | Описание |
|---|---|
| Иерархия | OutOfRangeException extends LogicException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- month = 13 при договоре 1…12
- if обнаруживает нарушение
- Код явно выполняет throw new OutOfRangeException
- catch получает сообщение: Номер месяца от 1 до 12
<?php
declare(strict_types=1);
try {
$month = 13;
if ($month < 1 || $month > 12) { throw new OutOfRangeException('Месяц отчёта должен быть от 1 до 12'); }
echo 'Месяц: ', $month, "\n";
} catch (OutOfRangeException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Месяц отчёта должен быть от 1 до 12
Сначала конкретное условие задачи, затем OutOfRangeException переносит причину отказа к catch. Допустимый диапазон индекса известен из договора заранее. Название SPL-ветви не означает, что PHP-компилятор автоматически выбросит это исключение: проверка написана явно.
<?php
declare(strict_types=1);
function reportMonth(int $month): string
{
if ($month < 1 || $month > 12) { throw new OutOfRangeException('Номер месяца вне 1–12'); }
return 'Отчёт за месяц ' . $month;
}
echo reportMonth(10), "\n";
try { echo reportMonth(13); }
catch (OutOfRangeException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Отчёт за месяц 10
Номер месяца вне 1–12
Допустимый диапазон индекса известен из договора заранее. Название SPL-ветви не означает, что PHP-компилятор автоматически выбросит это исключение: проверка написана явно. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Код запросил индекс вне заранее известного допустимого диапазона. |
| Когда не подходит | Ключа нет в конкретной коллекции, состав которой известен только во время работы. |
| Что должен решить обработчик | Исправить расчёт/вызов; сравнить с OutOfBoundsException. |
| Откуда методы | OutOfRangeException extends LogicException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The OutOfRangeException class.
RuntimeException: Операция не выполнена из-за ситуации во время работы
Практическая потребность. Отделить недоступность поставщика от отсутствия товаров. Ответы — локальные фикстуры. RuntimeException обозначает выбранный отказ операции во время работы. Возможность повтора определяется договором поставщика; не все такие ошибки временны. Проверку формы titles добавляют до использования внешнего ответа.
Операция не выполнена из-за ситуации во время работы. Базовый тип для отказов, зависящих от окружения/данных: сервис недоступен, операция не завершилась. Он сам не говорит, можно ли повторять запрос. Не все runtime-ошибки временные.
| Договор | Описание |
|---|---|
| Иерархия | RuntimeException extends Exception, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- Поставщик оказался недоступен во время операции
- if обнаруживает нарушение
- Код явно выполняет throw new RuntimeException
- catch получает сообщение: Поставщик сейчас недоступен
<?php
declare(strict_types=1);
try {
$response = ['available' => false];
if (!$response['available']) { throw new RuntimeException('Поставщик сейчас недоступен'); }
echo "Каталог получен\n";
} catch (RuntimeException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Поставщик сейчас недоступен
Сначала конкретное условие задачи, затем RuntimeException переносит причину отказа к catch. Ответы — локальные фикстуры. RuntimeException обозначает выбранный отказ операции во время работы. Возможность повтора определяется договором поставщика; не все такие ошибки временны. Проверку формы titles добавляют до использования внешнего ответа.
<?php
declare(strict_types=1);
function supplierTitles(array $reply): array
{
if (($reply['available'] ?? false) !== true) { throw new RuntimeException('Каталог пока нельзя получить'); }
return $reply['titles'];
}
echo implode(', ', supplierTitles(['available' => true, 'titles' => ['Чай', 'Кофе']])), "\n";
try { supplierTitles(['available' => false]); }
catch (RuntimeException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Чай, Кофе
Каталог пока нельзя получить
Ответы — локальные фикстуры. RuntimeException обозначает выбранный отказ операции во время работы. Возможность повтора определяется договором поставщика; не все такие ошибки временны. Проверку формы titles добавляют до использования внешнего ответа. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Операция не завершилась из-за данных, файла, сети или окружения во время работы. |
| Когда не подходит | Дефект порядка вызовов, известный программисту заранее. |
| Что должен решить обработчик | Выбрать: повтор, отказ сценария или перевод в понятный прикладной тип. |
| Откуда методы | RuntimeException extends Exception; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The RuntimeException class.
OutOfBoundsException: В текущей коллекции нет запрошенного ключа
Практическая потребность. Найти конкретный товар в текущем каталоге. Ключ допустимой формы отсутствует именно в текущей коллекции. Обычный доступ к отсутствующему ключу массива выдаёт предупреждение; наше исключение появляется из явного условия, а не автоматически.
В текущей коллекции нет запрошенного ключа. Применяется к выходу за границы набора во время работы. Доступ к обычному отсутствующему ключу массива сам выдаёт предупреждение; SPL-исключение нужно явно выбросить или получить от контейнера, который так документирован.
| Договор | Описание |
|---|---|
| Иерархия | OutOfBoundsException extends RuntimeException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- Запрошен SKU-2, в коллекции есть только SKU-1
- if обнаруживает нарушение
- Код явно выполняет throw new OutOfBoundsException
- catch получает сообщение: Товар не найден: SKU-2
<?php
declare(strict_types=1);
try {
$catalog = ['tea' => 'Чай'];
$sku = 'coffee';
if (!array_key_exists($sku, $catalog)) { throw new OutOfBoundsException('Нет товара: ' . $sku); }
echo $catalog[$sku], "\n";
} catch (OutOfBoundsException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Нет товара: coffee
Сначала конкретное условие задачи, затем OutOfBoundsException переносит причину отказа к catch. Ключ допустимой формы отсутствует именно в текущей коллекции. Обычный доступ к отсутствующему ключу массива выдаёт предупреждение; наше исключение появляется из явного условия, а не автоматически.
<?php
declare(strict_types=1);
function productTitle(array $catalog, string $sku): string
{
if (!array_key_exists($sku, $catalog)) { throw new OutOfBoundsException('Товар не найден: ' . $sku); }
return $catalog[$sku];
}
$catalog = ['tea' => 'Чай'];
echo productTitle($catalog, 'tea'), "\n";
try { echo productTitle($catalog, 'coffee'); }
catch (OutOfBoundsException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Чай
Товар не найден: coffee
Ключ допустимой формы отсутствует именно в текущей коллекции. Обычный доступ к отсутствующему ключу массива выдаёт предупреждение; наше исключение появляется из явного условия, а не автоматически. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | В фактической коллекции нет позиции/ключа во время работы. |
| Когда не подходит | Номер нарушает статически известный диапазон независимо от текущей коллекции. |
| Что должен решить обработчик | Вернуть not found/null или исключение согласно договору поиска. |
| Откуда методы | OutOfBoundsException extends RuntimeException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The OutOfBoundsException class.
OverflowException: Нельзя добавить элемент в заполненный контейнер
Практическая потребность. Не потерять новое уведомление при переполнении очереди. Проверка предшествует добавлению; прежняя очередь сохраняется. &$queue разрешает функции изменить переданный массив. Это ограниченная очередь в памяти; OverflowException не является общим типом арифметического переполнения PHP.
Нельзя добавить элемент в заполненный контейнер. Описывает переполнение ограниченного контейнера. Не следует считать это универсальным типом арифметического переполнения PHP: у чисел другой договор.
| Договор | Описание |
|---|---|
| Иерархия | OverflowException extends RuntimeException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- В очереди 2 задания при вместимости 2
- if обнаруживает нарушение
- Код явно выполняет throw new OverflowException
- catch получает сообщение: Очередь заполнена
<?php
declare(strict_types=1);
try {
$queue = ['Заказ 42', 'Заказ 43'];
if (count($queue) >= 2) { throw new OverflowException('Локальная очередь заполнена'); }
$queue[] = 'Заказ 44';
} catch (OverflowException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Локальная очередь заполнена
Сначала конкретное условие задачи, затем OverflowException переносит причину отказа к catch. Проверка предшествует добавлению; прежняя очередь сохраняется. &$queue разрешает функции изменить переданный массив. Это ограниченная очередь в памяти; OverflowException не является общим типом арифметического переполнения PHP.
<?php
declare(strict_types=1);
function enqueue(array &$queue, string $message, int $capacity): void
{
if (count($queue) >= $capacity) { throw new OverflowException('Нет места для: ' . $message); }
$queue[] = $message;
}
$queue = [];
enqueue($queue, 'Заказ 42', 1);
try { enqueue($queue, 'Заказ 43', 1); }
catch (OverflowException $error) { echo $error->getMessage(), "\n"; }
echo 'Сохранено в очереди: ', implode(', ', $queue), "\n";
Ожидаемый результат
Нет места для: Заказ 43
Сохранено в очереди: Заказ 42
Проверка предшествует добавлению; прежняя очередь сохраняется. &$queue разрешает функции изменить переданный массив. Это ограниченная очередь в памяти; OverflowException не является общим типом арифметического переполнения PHP. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | В ограниченный контейнер нельзя добавить ещё один элемент. |
| Когда не подходит | Арифметическое переполнение числа как универсальное правило. |
| Что должен решить обработчик | Освободить место, отклонить добавление или применить backpressure. |
| Откуда методы | OverflowException extends RuntimeException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The OverflowException class.
RangeException: Вычисленный результат вышел из допустимого диапазона
Практическая потребность. Не показывать прогресс выше 100 процентов. Здесь проверяется уже вычисленный результат. Проверка общего числа до деления решает другую ошибку входа. Исключение не маскируется обрезанием до 100%: сначала надо выяснить несогласованность счётчиков.
Вычисленный результат вышел из допустимого диапазона. Runtime-аналог DomainException: значение вычислилось, но его нельзя использовать по договору результата. По описанию SPL это математический диапазон, не переполнение/опустошение буфера.
| Договор | Описание |
|---|---|
| Иерархия | RangeException extends RuntimeException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- 12 / 10 → 1.2, допустима доля 0…1
- if обнаруживает нарушение
- Код явно выполняет throw new RangeException
- catch получает сообщение: Доля выполнения вне 0..1
<?php
declare(strict_types=1);
try {
$ratio = 12 / 10;
if ($ratio < 0 || $ratio > 1) { throw new RangeException('Результат прогресса вне 0–100%'); }
echo $ratio * 100, "%\n";
} catch (RangeException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Результат прогресса вне 0–100%
Сначала конкретное условие задачи, затем RangeException переносит причину отказа к catch. Здесь проверяется уже вычисленный результат. Проверка общего числа до деления решает другую ошибку входа. Исключение не маскируется обрезанием до 100%: сначала надо выяснить несогласованность счётчиков.
<?php
declare(strict_types=1);
function progress(int $done, int $total): string
{
if ($total < 1) { throw new InvalidArgumentException('Общее число должно быть положительным'); }
$ratio = $done / $total;
if ($ratio < 0 || $ratio > 1) { throw new RangeException('Вычислен недопустимый прогресс'); }
return ($ratio * 100) . '%';
}
echo 'Импорт: ', progress(3, 10), "\n";
try { echo progress(12, 10); }
catch (RangeException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Импорт: 30%
Вычислен недопустимый прогресс
Здесь проверяется уже вычисленный результат. Проверка общего числа до деления решает другую ошибку входа. Исключение не маскируется обрезанием до 100%: сначала надо выяснить несогласованность счётчиков. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Вычисленный результат оказался вне допустимого математического диапазона. |
| Когда не подходит | Входной аргумент не входит в перечисление предметной области. |
| Что должен решить обработчик | Проверить исходные данные; не обрезать результат молча. |
| Откуда методы | RangeException extends RuntimeException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The RangeException class.
UnderflowException: Нельзя извлечь элемент из пустого контейнера
Практическая потребность. Не извлекать несуществующее задание. array_shift сам вернул бы null для пустого массива. Наш договор takeJob обещает реальную строковую задачу, поэтому пустота оформлена явным исключением. В другом worker нормальное ожидание можно выразить ?string и null — главное выбрать понятный договор.
Нельзя извлечь элемент из пустого контейнера. Например, собственная операция take требует имеющееся задание. array_pop обычного пустого массива возвращает null; не ожидайте от него автоматического UnderflowException.
| Договор | Описание |
|---|---|
| Иерархия | UnderflowException extends RuntimeException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- Из пустого списка пытаются извлечь задание
- if обнаруживает нарушение
- Код явно выполняет throw new UnderflowException
- catch получает сообщение: Нет задания для извлечения
<?php
declare(strict_types=1);
try {
$jobs = [];
if ($jobs === []) { throw new UnderflowException('В очереди нет задания'); }
echo array_shift($jobs), "\n";
} catch (UnderflowException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
В очереди нет задания
Сначала конкретное условие задачи, затем UnderflowException переносит причину отказа к catch. array_shift сам вернул бы null для пустого массива. Наш договор takeJob обещает реальную строковую задачу, поэтому пустота оформлена явным исключением. В другом worker нормальное ожидание можно выразить ?string и null — главное выбрать понятный договор.
<?php
declare(strict_types=1);
function takeJob(array &$jobs): string
{
if ($jobs === []) { throw new UnderflowException('Очередь закончилась'); }
return array_shift($jobs);
}
$jobs = ['Уведомить о заказе 42'];
echo takeJob($jobs), "\n";
try { echo takeJob($jobs); }
catch (UnderflowException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Уведомить о заказе 42
Очередь закончилась
array_shift сам вернул бы null для пустого массива. Наш договор takeJob обещает реальную строковую задачу, поэтому пустота оформлена явным исключением. В другом worker нормальное ожидание можно выразить ?string и null — главное выбрать понятный договор. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Из ограниченного контейнера требуют элемент, когда он пуст. |
| Когда не подходит | Пустая очередь является штатным результатом poll/tryTake. |
| Что должен решить обработчик | Либо исключение для нарушенного обещания, либо null/result-object для ожидаемой пустоты. |
| Откуда методы | UnderflowException extends RuntimeException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The UnderflowException class.
UnexpectedValueException: Зависимость вернула результат неожиданной формы
Практическая потребность. Проверить форму ответа поставщика перед импортом. Это проверка результата зависимости, а не типа аргумента, который PHP уже принял. В реальном импорте дополнительно проверяют каждый элемент списка. Не превращайте ошибочную строку в пустой успешный массив.
Зависимость вернула результат неожиданной формы. Это договор результата другой операции: например, поставщик обещал список товаров, а вернул строку. От InvalidArgumentException отличается направлением: ваш вход против результата внешнего/внутреннего источника.
| Договор | Описание |
|---|---|
| Иерархия | UnexpectedValueException extends RuntimeException, PHP 5.1+, расширение SPL. |
| Конструктор | Унаследован Exception::__construct(message = "", code = 0, previous = null). |
| Состояние и методы | Общие поля и полный API Exception. Собственных новых полей/методов этот класс не объявляет. |
- В response.items пришла строка вместо массива
- if обнаруживает нарушение
- Код явно выполняет throw new UnexpectedValueException
- catch получает сообщение: Поставщик вернул не список товаров
<?php
declare(strict_types=1);
try {
$response = ['items' => 'temporarily unavailable'];
if (!isset($response['items']) || !is_array($response['items'])) {
throw new UnexpectedValueException('Поставщик вернул не список товаров');
}
} catch (UnexpectedValueException $error) {
echo $error->getMessage(), "\n";
}
Ожидаемый результат
Поставщик вернул не список товаров
Сначала конкретное условие задачи, затем UnexpectedValueException переносит причину отказа к catch. Это проверка результата зависимости, а не типа аргумента, который PHP уже принял. В реальном импорте дополнительно проверяют каждый элемент списка. Не превращайте ошибочную строку в пустой успешный массив.
<?php
declare(strict_types=1);
function titlesFromReply(array $response): array
{
if (!isset($response['items']) || !is_array($response['items'])) {
throw new UnexpectedValueException('Ответ не содержит список товаров');
}
return $response['items'];
}
echo implode(', ', titlesFromReply(['items' => ['Чай', 'Кофе']])), "\n";
try { titlesFromReply(['items' => 'error']); }
catch (UnexpectedValueException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Чай, Кофе
Ответ не содержит список товаров
Это проверка результата зависимости, а не типа аргумента, который PHP уже принял. В реальном импорте дополнительно проверяют каждый элемент списка. Не превращайте ошибочную строку в пустой успешный массив. Функция или объект выделяет операцию, а два входа показывают её успешную и отклонённую ветви. Обработка стоит у вызывающего сценария.
Выбор типа: LogicException и RuntimeException. Сохранение первопричины: Exception::getPrevious.
Как выбрать этот тип в реальном коде. Имя исключения сообщает вызывающему коду не текст ошибки, а категорию нарушенного договора. Сообщение может меняться и переводиться; catch обычно выбирают по классу.
| Вопрос | Решение |
|---|---|
| Когда подходит | Зависимость/парсер вернул значение неожиданной формы. |
| Когда не подходит | Ваш вызывающий код передал плохой аргумент. |
| Что должен решить обработчик | Сохранить безопасный контекст, остановить этот путь и проверить контракт источника. |
| Откуда методы | UnexpectedValueException extends RuntimeException; message, code, previous и стек наследуются от Exception. |
Граница приложения должна преобразовать технический тип в уместный результат: сообщение поля, HTTP-статус, запись задания для повтора или закрытую диагностику. Нельзя автоматически показывать посетителю путь файла, trace или полный ответ внешнего сервиса.
Связи: карта LogicException и RuntimeException; try, catch, finally от первого примера; цепочка previous. Официальная страница: The UnexpectedValueException class.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
ErrorException::__constructСохранить сообщение и исходное место PHP-предупреждения.
public function __construct(string $message = "", int $code = 0, int $severity = E_ERROR, ?string $filename = null, ?int $line = null, ?Throwable $previous = null)| Часть вызова | Договор |
|---|---|
| Назначение | Сохранить сообщение и исходное место PHP-предупреждения. |
| message/code | Описание и независимый код приложения; по умолчанию "" и 0. |
| severity | int уровня ошибки, по умолчанию E_ERROR; intended значения E_*, хотя конструктор принимает int. |
| filename/line | Место исходного предупреждения; null оставляет место создания. С PHP 8.0 параметры явно nullable. |
| previous | Предыдущий Throwable или null. |
| Возврат | Конструктор не возвращает значение. new создаёт ErrorException. |
- message, E_WARNING, supplier.php, строка 80
- new ErrorException сохраняет поля отдельно
- getFile/getLine ведут к supplier.php:80
- getSeverity читает E_WARNING, getCode читает code приложения
<?php
$error = new ErrorException('Не удалось прочитать данные', 17, E_WARNING, 'supplier.php', 80);
echo $error->getFile(), ':', $error->getLine(), "\n";
echo $error->getCode(), "\n";
var_dump($error->getSeverity() === E_WARNING);
Ожидаемый результат
supplier.php:80
17
bool(true)
severity характеризует PHP-сигнал, code выбран приложением. Разные поля не нужно склеивать. Здесь место задано явно, как его передал бы обработчик.
<?php
$cause = new RuntimeException('Не хватает места');
$error = new ErrorException('Не удалось записать', 0, E_WARNING, null, null, $cause);
echo basename($error->getFile()), "\n", $error->getPrevious()->getMessage(), "\n";
Ожидаемый результат
example.php
Не хватает места
null для file/line означает место создания этого объекта; это не стирание диагностики. previous сохраняет отдельную первопричину.
ErrorException::getSeverityПрочитать уровень PHP-ошибки, записанный в ErrorException.
final public function getSeverity(): int| Часть вызова | Договор |
|---|---|
| Назначение | Прочитать уровень PHP-ошибки, записанный в ErrorException. |
| Параметры | Нет. |
| Возврат | int severity; не code приложения. |
| Наследование | final, не переопределяется. |
- Создан ErrorException с E_WARNING
- getSeverity() читает этот уровень
- match выбирает метку warning
- Закрытая диагностика получает подходящую категорию
<?php
$error = new ErrorException('Пример');
var_dump($error->getSeverity() === E_ERROR);
echo $error->getCode(), "\n";
Ожидаемый результат
bool(true)
0
E_ERROR в поле ещё не означает, что этот объект завершил процесс. Здесь он создан вручную и не был выброшен.
<?php
$error = new ErrorException('Диагностическое предупреждение', 0, E_WARNING);
$label = match ($error->getSeverity()) {
E_WARNING, E_USER_WARNING => 'warning',
default => 'other',
};
echo $label, "\n";
Ожидаемый результат
warning
Выбор уровня журнала — политика приложения. Не используйте getCode для определения E_WARNING, если туда записан номер прикладной ошибки.
FiberError::__constructПоказать запрет ручного создания внутреннего FiberError.
public function __construct()| Часть вызова | Договор |
|---|---|
| Назначение | Показать запрет ручного создания внутреннего FiberError. |
| Аргументы | Нет. |
| Результат | Прямой new FiberError() выбрасывает Error. Несмотря на public-сигнатуру, рабочий объект так не создаётся. |
- Код выполняет new FiberError()
- Конструктор запрещает ручное создание
- Выбрасывается Error
- Для своей ошибки задачи нужен другой тип
<?php
try { new FiberError(); }
catch (Error $error) { echo get_class($error), "\n"; }
Ожидаемый результат
Error
Это именно Error, а не созданный пользователем FiberError. В свой код задач вводят собственный RuntimeException.
<?php
try { Fiber::suspend(); }
catch (FiberError $error) { var_dump($error instanceof Error, $error instanceof Throwable); }
Ожидаемый результат
bool(true)
bool(true)
В основном потоке отсутствует активная Fiber. Здесь подтверждён реальный внутренний отказ, а не ручной объект.
Throwable::getMessageПрочитать описание сбоя, не печатая его автоматически.
public function getMessage(): stringПрочитать описание сбоя, не печатая его автоматически.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строка message, переданная при создании объекта; по умолчанию пустая. |
| Наследование | У интерфейса Throwable это договор; выполнить его может объект Exception, Error или их потомок. Создать new Throwable нельзя. |
- new RuntimeException("Сервис недоступен")
- $error хранит объект
- $error->getMessage() возвращает строку
- echo выводит строку отдельно
<?php
declare(strict_types=1);
$error = new RuntimeException('Сервис недоступен');
$message = $error->getMessage();
echo $message, "\n";
Ожидаемый результат
Сервис недоступен
Получение строки не повторяет операцию и не исправляет сбой. Объект можно создать ещё до throw.
<?php
declare(strict_types=1);
$error = new RuntimeException('Сбой поставщика: request-42');
$diagnostic = ['type' => get_class($error), 'message' => $error->getMessage()];
$publicMessage = 'Не удалось загрузить товары. Попробуйте позже.';
echo $diagnostic['message'], "\n", $publicMessage, "\n";
Ожидаемый результат
Сбой поставщика: request-42
Не удалось загрузить товары. Попробуйте позже.
В примере обе строки выведены для сравнения. В рабочем HTTP-ответе показывают publicMessage; диагностическую запись отправляют в закрытый журнал без секретов. Сообщение библиотеки может содержать путь, SQL или часть входных данных.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Throwable::getCodeПрочитать код, который выбрал источник исключения.
public function getCode(): intПрочитать код, который выбрал источник исключения.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Обычно int, по умолчанию 0. Для Exception/Throwable отдельные потомки, например PDOException, могут хранить строковый код. |
| Смысл | Это не автоматически HTTP-статус и не обязательно уровень E_WARNING. Значение определяется конкретным классом/операцией. |
- new RuntimeException("Поставщик", 1201)
- В поле code сохранено 1201
- getCode() → 1201
- Обработчик применяет договор именно этого источника
<?php
declare(strict_types=1);
$error = new RuntimeException('Поставщик не ответил', 1201);
echo 'Код отказа поставщика: ', $error->getCode(), "\n";
echo 'Без заданного кода: ', (new RuntimeException('Без кода'))->getCode(), "\n";
Ожидаемый результат
Код отказа поставщика: 1201
Без заданного кода: 0
getCode возвращает код объекта. 1201 имеет смысл только в нашем договоре; он не превращается автоматически в HTTP 1201 или разрешение повторить операцию. Для ветвления учитывайте также тип исключения и описание ошибки.
<?php
declare(strict_types=1);
$error = new RuntimeException('Повторите позже', 1201);
$retryable = get_class($error) === 'RuntimeException' && $error->getCode() === 1201;
echo $retryable ? "Можно запланировать повтор\n" : "Нужен разбор\n";
Ожидаемый результат
Можно запланировать повтор
В реальной библиотеке удобнее отдельный тип временного сбоя. Если используете код, проверяйте и происхождение. Не приводите любой code к int: строковый SQLSTATE может потерять смысл.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Throwable::getFileНайти файл создания объекта сбоя.
public function getFile(): stringНайти файл создания объекта сбоя.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Путь к файлу. |
| Момент | Место создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю. |
| Особый случай | ErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе. |
- Объект создан в example.php
- Позже тот же объект попадает в catch
- getFile() → путь исходного example.php
<?php
declare(strict_types=1);
$createdLine = __LINE__ + 1;
$error = new RuntimeException('Диагностика');
echo basename($error->getFile()), "\n";
Ожидаемый результат
example.php
__FILE__ и __LINE__ — магические константы текущего файла и строки. basename оставляет только имя файла для воспроизводимого вывода. Полный путь зависит от сервера.
<?php
declare(strict_types=1);
$error = new RuntimeException('Первичный отказ каталога');
try { throw $error; }
catch (Throwable $caught) {
echo 'Исходное место: ', basename($caught->getFile()), ':', $caught->getLine(), "\n";
echo 'Причина: ', $caught->getMessage(), "\n";
}
Ожидаемый результат
Исходное место: example.php:3
Причина: Первичный отказ каталога
Тот же объект создан на строке 3 и выброшен на следующей. Его file/line не переписались местом throw. Если добавляете новый уровень контекста другим объектом, передайте original как previous, чтобы сохранить обе причины.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Throwable::getLineНайти строку создания объекта сбоя.
public function getLine(): intНайти строку создания объекта сбоя.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Номер строки, начиная с 1. |
| Момент | Место создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю. |
| Особый случай | ErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе. |
- __LINE__ + 1 сохраняет ожидаемую строку
- На следующей строке создаётся объект
- getLine() возвращает сохранённое место
- Номер catch не заменяет его
<?php
declare(strict_types=1);
$error = new RuntimeException('Каталог не обновлён');
echo 'Диагностика: ', basename($error->getFile()), ':', $error->getLine(), "\n";
echo $error->getMessage(), "\n";
Ожидаемый результат
Диагностика: example.php:3
Каталог не обновлён
В самостоятельном файле строка 3 создаёт объект ошибки. getLine возвращает именно это место, а не номер catch или последнего throw. Имя example.php соответствует запуску учебного примера. В закрытом журнале можно хранить полный путь, посетителю его обычно не показывают.
<?php
declare(strict_types=1);
$error = new RuntimeException('Первичный отказ каталога');
try { throw $error; }
catch (Throwable $caught) {
echo 'Исходное место: ', basename($caught->getFile()), ':', $caught->getLine(), "\n";
echo 'Причина: ', $caught->getMessage(), "\n";
}
Ожидаемый результат
Исходное место: example.php:3
Причина: Первичный отказ каталога
Тот же объект создан на строке 3 и выброшен на следующей. Его file/line не переписались местом throw. Если добавляете новый уровень контекста другим объектом, передайте original как previous, чтобы сохранить обе причины.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Throwable::getPreviousПолучить непосредственную причину, сохранённую при обёртывании.
public function getPrevious(): ?ThrowableПолучить непосредственную причину, сохранённую при обёртывании.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Предыдущий Throwable или null. Вопросительный знак означает, что null допустим. |
| Глубина | Один шаг цепочки; более раннюю причину нужно читать следующим getPrevious(). |
| Версия | Exception: PHP 5.3+. Error и общий Throwable: PHP 7+. В современном PHP причиной может быть и Exception, и Error. |
- Сбой соединения
- Сбой загрузки каталога с previous
- Верхний обработчик видит задачу
- getPrevious() ведёт к исходному сбою
<?php
declare(strict_types=1);
$cause = new RuntimeException('Нет соединения');
$error = new RuntimeException('Каталог не загружен', 0, $cause);
echo $error->getMessage(), "\n";
echo $error->getPrevious()->getMessage(), "\n";
echo $error->getPrevious() === $cause ? "Исходный объект причины сохранён\n" : "Причина заменена\n";
Ожидаемый результат
Каталог не загружен
Нет соединения
Исходный объект причины сохранён
previous связывает новый контекст Каталог не загружен с первоначальным Нет соединения. Это тот же объект причины, вместе с её file/line/trace. Клонирование не нужно. Если причины нет, getPrevious возвращает null; обход заканчивается на нём.
<?php
declare(strict_types=1);
$first = new RuntimeException('Сеть');
$second = new RuntimeException('Каталог', 0, $first);
$current = new RuntimeException('Заказ', 0, $second);
while ($current !== null) {
echo $current->getMessage(), "\n";
$current = $current->getPrevious();
}
Ожидаемый результат
Заказ
Каталог
Сеть
while подходит, потому что длина цепочки заранее неизвестна. null заканчивает обход. Сохранение исходной причины помогает отличить ошибку сети от ошибки преобразования данных.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Throwable::getTraceПолучить стек вызовов как массив для диагностического инструмента.
public function getTrace(): arrayПолучить стек вызовов как массив для диагностического инструмента.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Массив кадров в формате debug_backtrace: могут быть file, line, function, class, type, args и другие предусмотренные поля. Не все поля обязаны присутствовать. |
| Порядок | Ближайший вызов идёт первым, затем вызывавшие его места. |
| Данные | Наличие args зависит от конфигурации zend.exception_ignore_args. SensitiveParameter может заменить чувствительный аргумент оболочкой. Трасса не гарантирует очищенный журнал. |
- refreshCatalog() вызывает fetchFeed()
- В fetchFeed() создан объект сбоя
- getTrace() → массив кадров
- Ближайший fetchFeed, затем refreshCatalog
<?php
declare(strict_types=1);
function loadProducts(): Throwable {
return new RuntimeException('Каталог');
}
$error = loadProducts();
$trace = $error->getTrace();
echo $trace[0]['function'], "\n";
Ожидаемый результат
loadProducts
Стек фиксируется при создании объекта. Вызов getTrace позже не снимает новый текущий стек. Для текущего стека без исключения используют debug_backtrace.
<?php
declare(strict_types=1);
function fetchFeed(): Throwable { return new RuntimeException('Источник'); }
function refreshCatalog(): Throwable { return fetchFeed(); }
$error = refreshCatalog();
foreach ($error->getTrace() as $frame) {
echo $frame['function'] ?? '[внутренний вызов]', "\n";
}
Ожидаемый результат
fetchFeed
refreshCatalog
Оператор ?? выбирает запасной текст, если ключ отсутствует/null. Здесь извлечены только имена: в args могут находиться пароли и персональные данные. Полные трассы и большие аргументы занимают память; не создавайте исключения как обычный способ перебирать данные.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Throwable::getTraceAsStringПолучить готовый текст стека вместо массива кадров.
public function getTraceAsString(): stringПолучить готовый текст стека вместо массива кадров.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строковое представление трассировки. |
| Отличие | Это стек, а не полное представление с классом/сообщением/местом как у __toString(). Формат не является протоколом для парсинга бизнес-данных. |
- Объект уже содержит стек вызовов
- getTraceAsString() форматирует этот стек
- Возвращается текст, путь зависит от сервера
- Текст включается в закрытую диагностику
<?php
declare(strict_types=1);
function fetchPrice(): Throwable { return new RuntimeException('Цена недоступна'); }
$error = fetchPrice();
$trace = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $error->getTraceAsString());
echo $trace, "\n";
Ожидаемый результат
#0 example.php(4): fetchPrice()
#1 {main}
getTraceAsString возвращает стек, а сообщение ошибки хранится отдельно. Здесь убрана только общая абсолютная папка, чтобы учебный вывод не зависел от машины; имя файла, номер строки и вызов сохранены. Это диагностика для разработчика, не текст для посетителя.
<?php
declare(strict_types=1);
$error = new RuntimeException('Каталог недоступен');
$record = ['message' => $error->getMessage(), 'trace' => $error->getTraceAsString()];
$json = json_encode($record, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
file_put_contents(__DIR__ . '/diagnostic.json', $json);
echo file_get_contents(__DIR__ . '/diagnostic.json'), "\n";
Ожидаемый результат
{"message":"Каталог недоступен","trace":"#0 {main}"}
Файл diagnostic.json действительно создаётся в каталоге примера. Сообщение и стек не смешиваются в одну неразбираемую строку. Для рабочего журнала проверяйте результат записи, храните его вне публичного каталога и не дампите аргументы с секретами.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Throwable::__toStringПолучить полное диагностическое строковое представление объекта.
public function __toString(): stringПолучить полное диагностическое строковое представление объекта.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строка с типом, сообщением, местом и трассировкой; при цепочке включает связанные причины. |
| Вызов | Явное $error->__toString() или приведение (string) $error. У Exception/Error метод можно переопределить, в отличие от final-геттеров. |
| Интерфейс | С PHP 8 Throwable расширяет Stringable. Строковое представление не означает, что strict-параметр string автоматически принимает объект. |
- Объект содержит тип, сообщение, место и стек
- (string) $error вызывает представление
- Возвращается полный текст с этими данными
- Код отдельно выбирает, куда его передать
<?php
declare(strict_types=1);
$error = new RuntimeException('Сбой каталога');
$text = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', (string) $error);
echo $text, "\n";
Ожидаемый результат
RuntimeException: Сбой каталога in example.php:3
Stack trace:
#0 {main}
__toString формирует тип, сообщение, место и трассировку. В показанном тексте убрана абсолютная папка только для переносимости учебного результата. getMessage вернул бы одну причину без стека. Полное представление пригодно закрытому журналу, а не публичной странице.
<?php
declare(strict_types=1);
$cause = new RuntimeException('Нет соединения');
$error = new RuntimeException('Каталог не загружен', 0, $cause);
$text = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $error->__toString());
echo $text, "\n";
Ожидаемый результат
RuntimeException: Нет соединения in example.php:3
Stack trace:
#0 {main}
Next RuntimeException: Каталог не загружен in example.php:4
Stack trace:
#0 {main}
Полный текст содержит сначала исходную причину, затем обёртку с пометкой Next. Обе имеют собственное место создания. Это полезно разработчику при восстановлении цепочки отказа; пользователю выберите отдельное понятное сообщение.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::getMessageПрочитать описание сбоя, не печатая его автоматически.
public function getMessage(): stringПрочитать описание сбоя, не печатая его автоматически.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строка message, переданная при создании объекта; по умолчанию пустая. |
| Наследование | Метод final: потомок не может подменить его реализацию. |
- new Exception("Сервис недоступен")
- $error хранит объект
- $error->getMessage() возвращает строку
- echo выводит строку отдельно
<?php
declare(strict_types=1);
$error = new Exception('Сервис недоступен');
$message = $error->getMessage();
echo $message, "\n";
Ожидаемый результат
Сервис недоступен
Получение строки не повторяет операцию и не исправляет сбой. Объект можно создать ещё до throw.
<?php
declare(strict_types=1);
$error = new Exception('Сбой поставщика: request-42');
$diagnostic = ['type' => get_class($error), 'message' => $error->getMessage()];
$publicMessage = 'Не удалось загрузить товары. Попробуйте позже.';
echo $diagnostic['message'], "\n", $publicMessage, "\n";
Ожидаемый результат
Сбой поставщика: request-42
Не удалось загрузить товары. Попробуйте позже.
В примере обе строки выведены для сравнения. В рабочем HTTP-ответе показывают publicMessage; диагностическую запись отправляют в закрытый журнал без секретов. Сообщение библиотеки может содержать путь, SQL или часть входных данных.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::getCodeПрочитать код, который выбрал источник исключения.
public function getCode(): intПрочитать код, который выбрал источник исключения.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Обычно int, по умолчанию 0. Для Exception/Throwable отдельные потомки, например PDOException, могут хранить строковый код. |
| Смысл | Это не автоматически HTTP-статус и не обязательно уровень E_WARNING. Значение определяется конкретным классом/операцией. |
- new Exception("Поставщик", 1201)
- В поле code сохранено 1201
- getCode() → 1201
- Обработчик применяет договор именно этого источника
<?php
declare(strict_types=1);
$error = new Exception('Поставщик не ответил', 1201);
echo 'Код отказа поставщика: ', $error->getCode(), "\n";
echo 'Без заданного кода: ', (new Exception('Без кода'))->getCode(), "\n";
Ожидаемый результат
Код отказа поставщика: 1201
Без заданного кода: 0
getCode возвращает код объекта. 1201 имеет смысл только в нашем договоре; он не превращается автоматически в HTTP 1201 или разрешение повторить операцию. Для ветвления учитывайте также тип исключения и описание ошибки.
<?php
declare(strict_types=1);
$error = new Exception('Повторите позже', 1201);
$retryable = get_class($error) === 'Exception' && $error->getCode() === 1201;
echo $retryable ? "Можно запланировать повтор\n" : "Нужен разбор\n";
Ожидаемый результат
Можно запланировать повтор
В реальной библиотеке удобнее отдельный тип временного сбоя. Если используете код, проверяйте и происхождение. Не приводите любой code к int: строковый SQLSTATE может потерять смысл.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::getFileНайти файл создания объекта сбоя.
public function getFile(): stringНайти файл создания объекта сбоя.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Путь к файлу. |
| Момент | Место создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю. |
| Особый случай | ErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе. |
- Объект создан в example.php
- Позже тот же объект попадает в catch
- getFile() → путь исходного example.php
<?php
declare(strict_types=1);
$createdLine = __LINE__ + 1;
$error = new Exception('Диагностика');
echo basename($error->getFile()), "\n";
Ожидаемый результат
example.php
__FILE__ и __LINE__ — магические константы текущего файла и строки. basename оставляет только имя файла для воспроизводимого вывода. Полный путь зависит от сервера.
<?php
declare(strict_types=1);
$error = new Exception('Первичный отказ каталога');
try { throw $error; }
catch (Throwable $caught) {
echo 'Исходное место: ', basename($caught->getFile()), ':', $caught->getLine(), "\n";
echo 'Причина: ', $caught->getMessage(), "\n";
}
Ожидаемый результат
Исходное место: example.php:3
Причина: Первичный отказ каталога
Тот же объект создан на строке 3 и выброшен на следующей. Его file/line не переписались местом throw. Если добавляете новый уровень контекста другим объектом, передайте original как previous, чтобы сохранить обе причины.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::getLineНайти строку создания объекта сбоя.
public function getLine(): intНайти строку создания объекта сбоя.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Номер строки, начиная с 1. |
| Момент | Место создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю. |
| Особый случай | ErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе. |
- __LINE__ + 1 сохраняет ожидаемую строку
- На следующей строке создаётся объект
- getLine() возвращает сохранённое место
- Номер catch не заменяет его
<?php
declare(strict_types=1);
$error = new Exception('Каталог не обновлён');
echo 'Диагностика: ', basename($error->getFile()), ':', $error->getLine(), "\n";
echo $error->getMessage(), "\n";
Ожидаемый результат
Диагностика: example.php:3
Каталог не обновлён
В самостоятельном файле строка 3 создаёт объект ошибки. getLine возвращает именно это место, а не номер catch или последнего throw. Имя example.php соответствует запуску учебного примера. В закрытом журнале можно хранить полный путь, посетителю его обычно не показывают.
<?php
declare(strict_types=1);
$error = new Exception('Первичный отказ каталога');
try { throw $error; }
catch (Throwable $caught) {
echo 'Исходное место: ', basename($caught->getFile()), ':', $caught->getLine(), "\n";
echo 'Причина: ', $caught->getMessage(), "\n";
}
Ожидаемый результат
Исходное место: example.php:3
Причина: Первичный отказ каталога
Тот же объект создан на строке 3 и выброшен на следующей. Его file/line не переписались местом throw. Если добавляете новый уровень контекста другим объектом, передайте original как previous, чтобы сохранить обе причины.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::getPreviousПолучить непосредственную причину, сохранённую при обёртывании.
public function getPrevious(): ?ThrowableПолучить непосредственную причину, сохранённую при обёртывании.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Предыдущий Throwable или null. Вопросительный знак означает, что null допустим. |
| Глубина | Один шаг цепочки; более раннюю причину нужно читать следующим getPrevious(). |
| Версия | Exception: PHP 5.3+. Error и общий Throwable: PHP 7+. В современном PHP причиной может быть и Exception, и Error. |
- Сбой соединения
- Сбой загрузки каталога с previous
- Верхний обработчик видит задачу
- getPrevious() ведёт к исходному сбою
<?php
declare(strict_types=1);
$cause = new RuntimeException('Нет соединения');
$error = new Exception('Каталог не загружен', 0, $cause);
echo $error->getMessage(), "\n";
echo $error->getPrevious()->getMessage(), "\n";
echo $error->getPrevious() === $cause ? "Исходный объект причины сохранён\n" : "Причина заменена\n";
Ожидаемый результат
Каталог не загружен
Нет соединения
Исходный объект причины сохранён
previous связывает новый контекст Каталог не загружен с первоначальным Нет соединения. Это тот же объект причины, вместе с её file/line/trace. Клонирование не нужно. Если причины нет, getPrevious возвращает null; обход заканчивается на нём.
<?php
declare(strict_types=1);
$first = new RuntimeException('Сеть');
$second = new Exception('Каталог', 0, $first);
$current = new Exception('Заказ', 0, $second);
while ($current !== null) {
echo $current->getMessage(), "\n";
$current = $current->getPrevious();
}
Ожидаемый результат
Заказ
Каталог
Сеть
while подходит, потому что длина цепочки заранее неизвестна. null заканчивает обход. Сохранение исходной причины помогает отличить ошибку сети от ошибки преобразования данных.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::getTraceПолучить стек вызовов как массив для диагностического инструмента.
public function getTrace(): arrayПолучить стек вызовов как массив для диагностического инструмента.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Массив кадров в формате debug_backtrace: могут быть file, line, function, class, type, args и другие предусмотренные поля. Не все поля обязаны присутствовать. |
| Порядок | Ближайший вызов идёт первым, затем вызывавшие его места. |
| Данные | Наличие args зависит от конфигурации zend.exception_ignore_args. SensitiveParameter может заменить чувствительный аргумент оболочкой. Трасса не гарантирует очищенный журнал. |
- refreshCatalog() вызывает fetchFeed()
- В fetchFeed() создан объект сбоя
- getTrace() → массив кадров
- Ближайший fetchFeed, затем refreshCatalog
<?php
declare(strict_types=1);
function loadProducts(): Throwable {
return new Exception('Каталог');
}
$error = loadProducts();
$trace = $error->getTrace();
echo $trace[0]['function'], "\n";
Ожидаемый результат
loadProducts
Стек фиксируется при создании объекта. Вызов getTrace позже не снимает новый текущий стек. Для текущего стека без исключения используют debug_backtrace.
<?php
declare(strict_types=1);
function fetchFeed(): Throwable { return new Exception('Источник'); }
function refreshCatalog(): Throwable { return fetchFeed(); }
$error = refreshCatalog();
foreach ($error->getTrace() as $frame) {
echo $frame['function'] ?? '[внутренний вызов]', "\n";
}
Ожидаемый результат
fetchFeed
refreshCatalog
Оператор ?? выбирает запасной текст, если ключ отсутствует/null. Здесь извлечены только имена: в args могут находиться пароли и персональные данные. Полные трассы и большие аргументы занимают память; не создавайте исключения как обычный способ перебирать данные.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::getTraceAsStringПолучить готовый текст стека вместо массива кадров.
public function getTraceAsString(): stringПолучить готовый текст стека вместо массива кадров.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строковое представление трассировки. |
| Отличие | Это стек, а не полное представление с классом/сообщением/местом как у __toString(). Формат не является протоколом для парсинга бизнес-данных. |
- Объект уже содержит стек вызовов
- getTraceAsString() форматирует этот стек
- Возвращается текст, путь зависит от сервера
- Текст включается в закрытую диагностику
<?php
declare(strict_types=1);
function fetchPrice(): Throwable { return new Exception('Цена недоступна'); }
$error = fetchPrice();
$trace = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $error->getTraceAsString());
echo $trace, "\n";
Ожидаемый результат
#0 example.php(4): fetchPrice()
#1 {main}
getTraceAsString возвращает стек, а сообщение ошибки хранится отдельно. Здесь убрана только общая абсолютная папка, чтобы учебный вывод не зависел от машины; имя файла, номер строки и вызов сохранены. Это диагностика для разработчика, не текст для посетителя.
<?php
declare(strict_types=1);
$error = new Exception('Каталог недоступен');
$record = ['message' => $error->getMessage(), 'trace' => $error->getTraceAsString()];
$json = json_encode($record, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
file_put_contents(__DIR__ . '/diagnostic.json', $json);
echo file_get_contents(__DIR__ . '/diagnostic.json'), "\n";
Ожидаемый результат
{"message":"Каталог недоступен","trace":"#0 {main}"}
Файл diagnostic.json действительно создаётся в каталоге примера. Сообщение и стек не смешиваются в одну неразбираемую строку. Для рабочего журнала проверяйте результат записи, храните его вне публичного каталога и не дампите аргументы с секретами.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::__toStringПолучить полное диагностическое строковое представление объекта.
public function __toString(): stringПолучить полное диагностическое строковое представление объекта.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строка с типом, сообщением, местом и трассировкой; при цепочке включает связанные причины. |
| Вызов | Явное $error->__toString() или приведение (string) $error. У Exception/Error метод можно переопределить, в отличие от final-геттеров. |
| Интерфейс | С PHP 8 Throwable расширяет Stringable. Строковое представление не означает, что strict-параметр string автоматически принимает объект. |
- Объект содержит тип, сообщение, место и стек
- (string) $error вызывает представление
- Возвращается полный текст с этими данными
- Код отдельно выбирает, куда его передать
<?php
declare(strict_types=1);
$error = new Exception('Сбой каталога');
$text = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', (string) $error);
echo $text, "\n";
Ожидаемый результат
Exception: Сбой каталога in example.php:3
Stack trace:
#0 {main}
__toString формирует тип, сообщение, место и трассировку. В показанном тексте убрана абсолютная папка только для переносимости учебного результата. getMessage вернул бы одну причину без стека. Полное представление пригодно закрытому журналу, а не публичной странице.
<?php
declare(strict_types=1);
$cause = new RuntimeException('Нет соединения');
$error = new Exception('Каталог не загружен', 0, $cause);
$text = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $error->__toString());
echo $text, "\n";
Ожидаемый результат
RuntimeException: Нет соединения in example.php:3
Stack trace:
#0 {main}
Next Exception: Каталог не загружен in example.php:4
Stack trace:
#0 {main}
Полный текст содержит сначала исходную причину, затем обёртку с пометкой Next. Обе имеют собственное место создания. Это полезно разработчику при восстановлении цепочки отказа; пользователю выберите отдельное понятное сообщение.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::getMessageПрочитать описание сбоя, не печатая его автоматически.
public function getMessage(): stringПрочитать описание сбоя, не печатая его автоматически.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строка message, переданная при создании объекта; по умолчанию пустая. |
| Наследование | Метод final: потомок не может подменить его реализацию. |
- new Error("Сервис недоступен")
- $error хранит объект
- $error->getMessage() возвращает строку
- echo выводит строку отдельно
<?php
declare(strict_types=1);
$error = new Error('Сервис недоступен');
$message = $error->getMessage();
echo $message, "\n";
Ожидаемый результат
Сервис недоступен
Получение строки не повторяет операцию и не исправляет сбой. Объект можно создать ещё до throw.
<?php
declare(strict_types=1);
$error = new Error('Сбой поставщика: request-42');
$diagnostic = ['type' => get_class($error), 'message' => $error->getMessage()];
$publicMessage = 'Не удалось загрузить товары. Попробуйте позже.';
echo $diagnostic['message'], "\n", $publicMessage, "\n";
Ожидаемый результат
Сбой поставщика: request-42
Не удалось загрузить товары. Попробуйте позже.
В примере обе строки выведены для сравнения. В рабочем HTTP-ответе показывают publicMessage; диагностическую запись отправляют в закрытый журнал без секретов. Сообщение библиотеки может содержать путь, SQL или часть входных данных.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::getCodeПрочитать код, который выбрал источник исключения.
public function getCode(): intПрочитать код, который выбрал источник исключения.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Обычно int, по умолчанию 0. Для Exception/Throwable отдельные потомки, например PDOException, могут хранить строковый код. |
| Смысл | Это не автоматически HTTP-статус и не обязательно уровень E_WARNING. Значение определяется конкретным классом/операцией. |
- new Error("Поставщик", 1201)
- В поле code сохранено 1201
- getCode() → 1201
- Обработчик применяет договор именно этого источника
<?php
declare(strict_types=1);
$error = new Error('Поставщик не ответил', 1201);
echo 'Код отказа поставщика: ', $error->getCode(), "\n";
echo 'Без заданного кода: ', (new Error('Без кода'))->getCode(), "\n";
Ожидаемый результат
Код отказа поставщика: 1201
Без заданного кода: 0
getCode возвращает код объекта. 1201 имеет смысл только в нашем договоре; он не превращается автоматически в HTTP 1201 или разрешение повторить операцию. Для ветвления учитывайте также тип исключения и описание ошибки.
<?php
declare(strict_types=1);
$error = new Error('Повторите позже', 1201);
$retryable = get_class($error) === 'Error' && $error->getCode() === 1201;
echo $retryable ? "Можно запланировать повтор\n" : "Нужен разбор\n";
Ожидаемый результат
Можно запланировать повтор
В реальной библиотеке удобнее отдельный тип временного сбоя. Если используете код, проверяйте и происхождение. Не приводите любой code к int: строковый SQLSTATE может потерять смысл.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::getFileНайти файл создания объекта сбоя.
public function getFile(): stringНайти файл создания объекта сбоя.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Путь к файлу. |
| Момент | Место создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю. |
| Особый случай | ErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе. |
- Объект создан в example.php
- Позже тот же объект попадает в catch
- getFile() → путь исходного example.php
<?php
declare(strict_types=1);
$createdLine = __LINE__ + 1;
$error = new Error('Диагностика');
echo basename($error->getFile()), "\n";
Ожидаемый результат
example.php
__FILE__ и __LINE__ — магические константы текущего файла и строки. basename оставляет только имя файла для воспроизводимого вывода. Полный путь зависит от сервера.
<?php
declare(strict_types=1);
$error = new Error('Первичный отказ каталога');
try { throw $error; }
catch (Throwable $caught) {
echo 'Исходное место: ', basename($caught->getFile()), ':', $caught->getLine(), "\n";
echo 'Причина: ', $caught->getMessage(), "\n";
}
Ожидаемый результат
Исходное место: example.php:3
Причина: Первичный отказ каталога
Тот же объект создан на строке 3 и выброшен на следующей. Его file/line не переписались местом throw. Если добавляете новый уровень контекста другим объектом, передайте original как previous, чтобы сохранить обе причины.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::getLineНайти строку создания объекта сбоя.
public function getLine(): intНайти строку создания объекта сбоя.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Номер строки, начиная с 1. |
| Момент | Место создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю. |
| Особый случай | ErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе. |
- __LINE__ + 1 сохраняет ожидаемую строку
- На следующей строке создаётся объект
- getLine() возвращает сохранённое место
- Номер catch не заменяет его
<?php
declare(strict_types=1);
$error = new Error('Каталог не обновлён');
echo 'Диагностика: ', basename($error->getFile()), ':', $error->getLine(), "\n";
echo $error->getMessage(), "\n";
Ожидаемый результат
Диагностика: example.php:3
Каталог не обновлён
В самостоятельном файле строка 3 создаёт объект ошибки. getLine возвращает именно это место, а не номер catch или последнего throw. Имя example.php соответствует запуску учебного примера. В закрытом журнале можно хранить полный путь, посетителю его обычно не показывают.
<?php
declare(strict_types=1);
$error = new Error('Первичный отказ каталога');
try { throw $error; }
catch (Throwable $caught) {
echo 'Исходное место: ', basename($caught->getFile()), ':', $caught->getLine(), "\n";
echo 'Причина: ', $caught->getMessage(), "\n";
}
Ожидаемый результат
Исходное место: example.php:3
Причина: Первичный отказ каталога
Тот же объект создан на строке 3 и выброшен на следующей. Его file/line не переписались местом throw. Если добавляете новый уровень контекста другим объектом, передайте original как previous, чтобы сохранить обе причины.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::getPreviousПолучить непосредственную причину, сохранённую при обёртывании.
public function getPrevious(): ?ThrowableПолучить непосредственную причину, сохранённую при обёртывании.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Предыдущий Throwable или null. Вопросительный знак означает, что null допустим. |
| Глубина | Один шаг цепочки; более раннюю причину нужно читать следующим getPrevious(). |
| Версия | Exception: PHP 5.3+. Error и общий Throwable: PHP 7+. В современном PHP причиной может быть и Exception, и Error. |
- Сбой соединения
- Сбой загрузки каталога с previous
- Верхний обработчик видит задачу
- getPrevious() ведёт к исходному сбою
<?php
declare(strict_types=1);
$cause = new RuntimeException('Нет соединения');
$error = new Error('Каталог не загружен', 0, $cause);
echo $error->getMessage(), "\n";
echo $error->getPrevious()->getMessage(), "\n";
echo $error->getPrevious() === $cause ? "Исходный объект причины сохранён\n" : "Причина заменена\n";
Ожидаемый результат
Каталог не загружен
Нет соединения
Исходный объект причины сохранён
previous связывает новый контекст Каталог не загружен с первоначальным Нет соединения. Это тот же объект причины, вместе с её file/line/trace. Клонирование не нужно. Если причины нет, getPrevious возвращает null; обход заканчивается на нём.
<?php
declare(strict_types=1);
$first = new RuntimeException('Сеть');
$second = new Error('Каталог', 0, $first);
$current = new Error('Заказ', 0, $second);
while ($current !== null) {
echo $current->getMessage(), "\n";
$current = $current->getPrevious();
}
Ожидаемый результат
Заказ
Каталог
Сеть
while подходит, потому что длина цепочки заранее неизвестна. null заканчивает обход. Сохранение исходной причины помогает отличить ошибку сети от ошибки преобразования данных.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::getTraceПолучить стек вызовов как массив для диагностического инструмента.
public function getTrace(): arrayПолучить стек вызовов как массив для диагностического инструмента.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Массив кадров в формате debug_backtrace: могут быть file, line, function, class, type, args и другие предусмотренные поля. Не все поля обязаны присутствовать. |
| Порядок | Ближайший вызов идёт первым, затем вызывавшие его места. |
| Данные | Наличие args зависит от конфигурации zend.exception_ignore_args. SensitiveParameter может заменить чувствительный аргумент оболочкой. Трасса не гарантирует очищенный журнал. |
- refreshCatalog() вызывает fetchFeed()
- В fetchFeed() создан объект сбоя
- getTrace() → массив кадров
- Ближайший fetchFeed, затем refreshCatalog
<?php
declare(strict_types=1);
function loadProducts(): Throwable {
return new Error('Каталог');
}
$error = loadProducts();
$trace = $error->getTrace();
echo $trace[0]['function'], "\n";
Ожидаемый результат
loadProducts
Стек фиксируется при создании объекта. Вызов getTrace позже не снимает новый текущий стек. Для текущего стека без исключения используют debug_backtrace.
<?php
declare(strict_types=1);
function fetchFeed(): Throwable { return new Error('Источник'); }
function refreshCatalog(): Throwable { return fetchFeed(); }
$error = refreshCatalog();
foreach ($error->getTrace() as $frame) {
echo $frame['function'] ?? '[внутренний вызов]', "\n";
}
Ожидаемый результат
fetchFeed
refreshCatalog
Оператор ?? выбирает запасной текст, если ключ отсутствует/null. Здесь извлечены только имена: в args могут находиться пароли и персональные данные. Полные трассы и большие аргументы занимают память; не создавайте исключения как обычный способ перебирать данные.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::getTraceAsStringПолучить готовый текст стека вместо массива кадров.
public function getTraceAsString(): stringПолучить готовый текст стека вместо массива кадров.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строковое представление трассировки. |
| Отличие | Это стек, а не полное представление с классом/сообщением/местом как у __toString(). Формат не является протоколом для парсинга бизнес-данных. |
- Объект уже содержит стек вызовов
- getTraceAsString() форматирует этот стек
- Возвращается текст, путь зависит от сервера
- Текст включается в закрытую диагностику
<?php
declare(strict_types=1);
function fetchPrice(): Throwable { return new Error('Цена недоступна'); }
$error = fetchPrice();
$trace = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $error->getTraceAsString());
echo $trace, "\n";
Ожидаемый результат
#0 example.php(4): fetchPrice()
#1 {main}
getTraceAsString возвращает стек, а сообщение ошибки хранится отдельно. Здесь убрана только общая абсолютная папка, чтобы учебный вывод не зависел от машины; имя файла, номер строки и вызов сохранены. Это диагностика для разработчика, не текст для посетителя.
<?php
declare(strict_types=1);
$error = new Error('Каталог недоступен');
$record = ['message' => $error->getMessage(), 'trace' => $error->getTraceAsString()];
$json = json_encode($record, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
file_put_contents(__DIR__ . '/diagnostic.json', $json);
echo file_get_contents(__DIR__ . '/diagnostic.json'), "\n";
Ожидаемый результат
{"message":"Каталог недоступен","trace":"#0 {main}"}
Файл diagnostic.json действительно создаётся в каталоге примера. Сообщение и стек не смешиваются в одну неразбираемую строку. Для рабочего журнала проверяйте результат записи, храните его вне публичного каталога и не дампите аргументы с секретами.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::__toStringПолучить полное диагностическое строковое представление объекта.
public function __toString(): stringПолучить полное диагностическое строковое представление объекта.
| Часть договора | Что означает |
|---|---|
| Параметры | Нет. |
| Возврат | Строка с типом, сообщением, местом и трассировкой; при цепочке включает связанные причины. |
| Вызов | Явное $error->__toString() или приведение (string) $error. У Exception/Error метод можно переопределить, в отличие от final-геттеров. |
| Интерфейс | С PHP 8 Throwable расширяет Stringable. Строковое представление не означает, что strict-параметр string автоматически принимает объект. |
- Объект содержит тип, сообщение, место и стек
- (string) $error вызывает представление
- Возвращается полный текст с этими данными
- Код отдельно выбирает, куда его передать
<?php
declare(strict_types=1);
$error = new Error('Сбой каталога');
$text = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', (string) $error);
echo $text, "\n";
Ожидаемый результат
Error: Сбой каталога in example.php:3
Stack trace:
#0 {main}
__toString формирует тип, сообщение, место и трассировку. В показанном тексте убрана абсолютная папка только для переносимости учебного результата. getMessage вернул бы одну причину без стека. Полное представление пригодно закрытому журналу, а не публичной странице.
<?php
declare(strict_types=1);
$cause = new RuntimeException('Нет соединения');
$error = new Error('Каталог не загружен', 0, $cause);
$text = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $error->__toString());
echo $text, "\n";
Ожидаемый результат
RuntimeException: Нет соединения in example.php:3
Stack trace:
#0 {main}
Next Error: Каталог не загружен in example.php:4
Stack trace:
#0 {main}
Полный текст содержит сначала исходную причину, затем обёртку с пометкой Next. Обе имеют собственное место создания. Это полезно разработчику при восстановлении цепочки отказа; пользователю выберите отдельное понятное сообщение.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::__constructСоздать объект с сообщением, кодом и необязательной причиной.
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)Создать объект с сообщением, кодом и необязательной причиной.
| Часть договора | Что означает |
|---|---|
| message | Описание сбоя; по умолчанию пустая строка. Официальный договор предупреждает: сообщение не является binary-safe хранилищем произвольных байтов. |
| code | Код приложения, по умолчанию 0. |
| previous | Причина Throwable или null. |
| Возврат | У конструктора нет возвращаемого значения; new возвращает созданный объект. Создать и выбросить — отдельные действия. |
- message = "Не удалось сохранить", code = 25
- new Exception(...) создаёт объект
- $error хранит объект и его историю
- Выполнение продолжается, пока нет throw
<?php
declare(strict_types=1);
$error = new Exception('Не удалось сохранить', 25);
echo "Объект создан\n";
echo $error->getMessage(), ':', $error->getCode(), "\n";
Ожидаемый результат
Объект создан
Не удалось сохранить:25
Только throw передаёт управление подходящему catch. Не создавайте объект ошибки просто для хранения обычного статуса.
<?php
declare(strict_types=1);
class CatalogFailure extends Exception {
public function __construct(Throwable $cause) {
parent::__construct('Каталог не обновлён', 0, $cause);
}
}
$error = new CatalogFailure(new RuntimeException('Поставщик недоступен'));
echo $error->getMessage(), "\n", $error->getPrevious()->getMessage(), "\n";
Ожидаемый результат
Каталог не обновлён
Поставщик недоступен
parent::__construct заполняет унаследованные поля. Для ожидаемого операционного отказа приложения обычно выбирают Exception/RuntimeException, а не ветвь Error. Здесь обе ветви показаны для понимания одинакового API.
При собственном конструкторе вызывайте родительский явно. Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Exception::__cloneПонять, почему объект исключения нельзя клонировать.
private function __clone(): voidПонять, почему объект исключения нельзя клонировать.
| Часть договора | Что означает |
|---|---|
| Доступность | Служебный private-метод. Это ограничение, а не операция, которую следует вызывать. |
| Поведение | clone для Exception/Error и потомков недопустим и вызывает Error. |
| Версия | С PHP 8.1 метод больше не final; это не снимает запрет клонирования объектов исключений. |
- Есть исходный $error
- clone пытается копировать исключение
- Движок запрещает копию и выбрасывает Error
- Для контекста нужен новый объект с previous
<?php
declare(strict_types=1);
$error = new Exception('Причина');
try { $copy = clone $error; }
catch (Error $failure) { echo "Клонирование недоступно\n"; }
Ожидаемый результат
Клонирование недоступно
Для добавления смысла создайте новое исключение и сохраните исходное в previous. Для повторного выбрасывания используйте тот же объект.
<?php
declare(strict_types=1);
$original = new Exception('Транспорт недоступен');
$wrapped = new RuntimeException('Загрузка каталога остановлена', 0, $original);
echo $wrapped->getMessage(), "\n";
echo 'Причина: ', $wrapped->getPrevious()->getMessage(), "\n";
Ожидаемый результат
Загрузка каталога остановлена
Причина: Транспорт недоступен
Exception/Error не клонируются обычным clone. Новый объект с previous выражает другую потребность: добавить смысл уровня приложения и сохранить первоначальную диагностику. Исходный объект остаётся причиной без потери его трассы.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::__constructСоздать объект с сообщением, кодом и необязательной причиной.
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)Создать объект с сообщением, кодом и необязательной причиной.
| Часть договора | Что означает |
|---|---|
| message | Описание сбоя; по умолчанию пустая строка. Официальный договор предупреждает: сообщение не является binary-safe хранилищем произвольных байтов. |
| code | Код приложения, по умолчанию 0. |
| previous | Причина Throwable или null. |
| Возврат | У конструктора нет возвращаемого значения; new возвращает созданный объект. Создать и выбросить — отдельные действия. |
- message = "Не удалось сохранить", code = 25
- new Error(...) создаёт объект
- $error хранит объект и его историю
- Выполнение продолжается, пока нет throw
<?php
declare(strict_types=1);
$error = new Error('Не удалось сохранить', 25);
echo "Объект создан\n";
echo $error->getMessage(), ':', $error->getCode(), "\n";
Ожидаемый результат
Объект создан
Не удалось сохранить:25
Только throw передаёт управление подходящему catch. Не создавайте объект ошибки просто для хранения обычного статуса.
<?php
declare(strict_types=1);
class CatalogFailure extends Error {
public function __construct(Throwable $cause) {
parent::__construct('Каталог не обновлён', 0, $cause);
}
}
$error = new CatalogFailure(new RuntimeException('Поставщик недоступен'));
echo $error->getMessage(), "\n", $error->getPrevious()->getMessage(), "\n";
Ожидаемый результат
Каталог не обновлён
Поставщик недоступен
parent::__construct заполняет унаследованные поля. Для ожидаемого операционного отказа приложения обычно выбирают Exception/RuntimeException, а не ветвь Error. Здесь обе ветви показаны для понимания одинакового API.
При собственном конструкторе вызывайте родительский явно. Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Error::__cloneПонять, почему объект исключения нельзя клонировать.
private function __clone(): voidПонять, почему объект исключения нельзя клонировать.
| Часть договора | Что означает |
|---|---|
| Доступность | Служебный private-метод. Это ограничение, а не операция, которую следует вызывать. |
| Поведение | clone для Exception/Error и потомков недопустим и вызывает Error. |
| Версия | С PHP 8.1 метод больше не final; это не снимает запрет клонирования объектов исключений. |
- Есть исходный $error
- clone пытается копировать исключение
- Движок запрещает копию и выбрасывает Error
- Для контекста нужен новый объект с previous
<?php
declare(strict_types=1);
$error = new Error('Причина');
try { $copy = clone $error; }
catch (Error $failure) { echo "Клонирование недоступно\n"; }
Ожидаемый результат
Клонирование недоступно
Для добавления смысла создайте новое исключение и сохраните исходное в previous. Для повторного выбрасывания используйте тот же объект.
<?php
declare(strict_types=1);
$original = new Error('Транспорт недоступен');
$wrapped = new RuntimeException('Загрузка каталога остановлена', 0, $original);
echo $wrapped->getMessage(), "\n";
echo 'Причина: ', $wrapped->getPrevious()->getMessage(), "\n";
Ожидаемый результат
Загрузка каталога остановлена
Причина: Транспорт недоступен
Exception/Error не клонируются обычным clone. Новый объект с previous выражает другую потребность: добавить смысл уровня приложения и сохранить первоначальную диагностику. Исходный объект остаётся причиной без потери его трассы.
Как выбрать тип и сохранить причину. throw, try, catch и finally с самого начала.
Источники и обсуждения
- PHP: предопределённые исключения
- PHP: Throwable
- PHP: SPL exceptions
- PHP: class.argumentcounterror
- PHP: class.arithmeticerror
- PHP: class.assertionerror
- PHP: class.badfunctioncallexception
- PHP: class.badmethodcallexception
- PHP: class.closedgeneratorexception
- PHP: class.compileerror
- PHP: class.divisionbyzeroerror
- PHP: class.domainexception
- PHP: class.error
- PHP: class.errorexception
- PHP: class.exception
- PHP: class.fibererror
- PHP: class.invalidargumentexception
- PHP: class.lengthexception
- PHP: class.logicexception
- PHP: class.outofboundsexception
- PHP: class.outofrangeexception
- PHP: class.overflowexception
- PHP: class.parseerror
- PHP: class.rangeexception
- PHP: class.requestparsebodyexception
- PHP: class.runtimeexception
- PHP: class.typeerror
- PHP: class.underflowexception
- PHP: class.unexpectedvalueexception
- PHP: class.unhandledmatcherror
- PHP: class.valueerror
- PHP 8.5.11: заполнение свойств Exception
- Обсуждение: Почему catch принимает интерфейс Throwable — обсуждение проблемы
- Обсуждение: Для чего previous — обсуждение проблемы
- Обсуждение: Error и ErrorException — обсуждение проблемы
- Обсуждение: Когда возникает ClosedGeneratorException — обсуждение проблемы
- Обсуждение: Выбор LogicException/RuntimeException — обсуждение проблемы
- Обсуждение: Обёртывание вместо преобразования класса — обсуждение проблемы