Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Исключения и ошибки: весь API Throwable, Exception, Error и SPL
  2. Из операции не пришёл результат: что передать вызывающему коду
  3. Exception, Error и Throwable: два семейства одного договора
  4. Throwable: общий интерфейс без собственного new
  5. Что хранится внутри Exception и Error
  6. Exception: базовая карточка отказа приложения
  7. Error: увидеть дефект программы и сохранить диагностику
  8. ErrorException: превратить выбранное предупреждение в объект
  9. SPL: выбрать смысл ошибки, чтобы вызывающий код понимал отказ
  10. ArgumentCountError: Число аргументов не соответствует вызову
  11. TypeError: Тип значения нарушает объявленный договор
  12. ValueError: Тип подходит, но конкретное значение недопустимо
  13. ArithmeticError: Арифметическая операция не имеет допустимого результата в своём договоре
  14. DivisionByZeroError: Делитель оказался равен нулю
  15. AssertionError: Внутренняя проверка assert обнаружила нарушенное предположение
  16. CompileError: Некоторые ошибки компиляции представлены объектом
  17. ParseError: Синтаксис PHP не удалось разобрать
  18. UnhandledMatchError: Для значения не нашлась ветвь match
  19. ClosedGeneratorException: Делегирующий генератор потерял результат аварийно закрытого источника
  20. FiberError: Операция Fiber недопустима в текущем состоянии
  21. RequestParseBodyException: PHP не смог разобрать тело HTTP-запроса по его типу и ограничениям
  22. LogicException: Нарушен внутренний договор программы
  23. BadFunctionCallException: Запрошена недоступная операция-функция
  24. BadMethodCallException: Объект получил неподдерживаемый вызов метода
  25. DomainException: Значение вне допустимого набора предметной области
  26. InvalidArgumentException: Аргумент пользовательской операции имеет недопустимый тип или форму
  27. LengthException: Длина нарушает установленный договор
  28. OutOfRangeException: Индекс вне диапазона, разрешённого договором
  29. RuntimeException: Операция не выполнена из-за ситуации во время работы
  30. OutOfBoundsException: В текущей коллекции нет запрошенного ключа
  31. OverflowException: Нельзя добавить элемент в заполненный контейнер
  32. RangeException: Вычисленный результат вышел из допустимого диапазона
  33. UnderflowException: Нельзя извлечь элемент из пустого контейнера
  34. UnexpectedValueException: Зависимость вернула результат неожиданной формы
  35. Функции и методы этой темы
  36. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 79 · От основ к практике

Исключения и ошибки: весь API Throwable, Exception, Error и SPL

От первого throw до выбора типа, цепочек причин, стека и всех предопределённых классов.

Из операции не пришёл результат: что передать вызывающему коду

Наглядная модель

От сбоя к понятному решению

Исключение несёт данные и меняет маршрут выполнения.

  1. Загрузка каталога

    Операция начала работу, результат ещё не получен.

  2. throw new RuntimeException

    Создан объект с описанием и сохранённой причиной.

  3. Выход к catch

    Оставшаяся часть неудачного пути не выполняется.

  4. Проверка типа и причины

    Обработчик решает остановить операцию или запланировать допустимый повтор.

  5. Понятный ответ и закрытый журнал

    Посетителю не раскрывают стек, пути и секреты.

catch не делает автоматический rollback, повтор или восстановление состояния. Эти действия задаются явно согласно договору операции.

Покупатель указал ноль упаковок чая. Заказ пока нельзя оформлять, но нужно сохранить понятный путь исправления: сообщить об ошибке и продолжить работу формы. Начнём с одной проверки числа без собственного класса или сложного сервиса.

Объект исключения похож на карточку происшествия: тип, описание, место и путь вызовов. new InvalidArgumentException(...) создаёт карточку неверного входа; throw прерывает обычный путь. try отмечает участок, catch (InvalidArgumentException $error) получает подходящую ошибку, а ->getMessage() читает сообщение. Сам объект ничего не исправляет, не откатывает БД и не отправляет ответ.

Количество 0 меняет путь выполнения
  1. $quantity = 0 — вход в проверку
  2. Условие $quantity < 1 истинно → throw
  3. Строка Заказ готов к оформлению пропускается
  4. catch выводит Выберите хотя бы одну упаковку
  5. После 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: два семейства одного договора

Как причина доходит до вызывающего кода
  1. Операция обнаруживает невозможность выполнить договор
  2. Создаёт подходящий объект Exception/Error
  3. throw прекращает обычный путь
  4. Стек ищет ближайший совместимый catch
  5. 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 официального справочника показывает смысл типов; не копируйте объявления свойств из новой версии в старую без проверки совместимости наследования.

ПолеДоступ и смыслКак прочитать
messageprotected; текст описания, исходно пустой.Exception::getMessage / Error::getMessage
codeprotected; код источника, обычно 0. У некоторых потомков Exception возможен строковый SQLSTATE.Exception::getCode / Error::getCode
file, lineprotected; файл и строка появления объекта. ErrorException может хранить переданное место предупреждения.Exception::getFile, Exception::getLine
traceprivate; кадры вызовов.Exception::getTrace / Error::getTrace
previousprivate; предыдущий Throwable или null.Exception::getPrevious / Error::getPrevious
stringprivate; внутреннее строковое представление. Не меняйте его вручную.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 — не исправление.

Exception не ловит соседнюю ветвьСамостоятельный пример
<?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 гарантирует восстановление обработчика при обычном выходе/исключении из блока.

Выбранное предупреждение становится исключением
  1. trigger_error → E_USER_WARNING
  2. set_error_handler получает четыре поля
  3. throw new ErrorException с исходным file/line
  4. catch ErrorException читает severity
  5. finally восстанавливает прежний обработчик
Сохранить уровень выбранного предупреждения в ErrorExceptionСамостоятельный пример
<?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, но в пределах библиотеки выбор должен быть последовательным.

ВетвьДочерние типыПрактический вопрос
LogicExceptionBadFunctionCallException → BadMethodCallException; DomainException; InvalidArgumentException; LengthException; OutOfRangeException.Нарушил ли вызывающий код известные правила или последовательность действий?
RuntimeExceptionOutOfBoundsException; 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.

Аргумент — значение между скобками при вызове. Обязательный параметр без значения по умолчанию требует аргумента. Этот тип возникает при нехватке аргументов пользовательской функции и при лишних аргументах встроенной невариативной функции. Не переносите правило о лишних аргументах на все пользовательские функции: они могут принимать дополнительные позиционные аргументы без этой ошибки.

Как возникает ArgumentCountError
  1. greeting() без значения
  2. Параметру $name не хватает аргумента
  3. ArgumentCountError передан в catch
  4. Исправляем вызов 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 или формы. В нестрогом режиме некоторые скаляры могут преобразоваться.

Как возникает TypeError
  1. doubleQuantity("4") в строгом файле
  2. Параметр требует int, пришла string
  3. TypeError до выполнения расчёта
  4. Проверяем и нормализуем вход отдельно
Строка попала в строгий числовой параметрСамостоятельный пример
<?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 ряд операций предупреждал и возвращал специальное значение.

Как возникает ValueError
  1. explode("", "A-42")
  2. Тип string подходит, пустой разделитель запрещён
  3. ValueError
  4. Выбираем осмысленный разделитель или другую операцию
Пустой разделитель нельзя использоватьСамостоятельный пример
<?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 требует целочисленного результата; отрицательный сдвиг битов тоже недопустим.

Как возникает ArithmeticError
  1. intdiv(PHP_INT_MIN, -1)
  2. Положительный результат не помещается в int
  3. ArithmeticError
  4. Выбираем подходящий диапазон/арифметику
Отрицательный сдвиг запрещёнСамостоятельный пример
<?php
declare(strict_types=1);

try { $mask = 1 << -1; }
catch (ArithmeticError $error) { echo "Нельзя сдвинуть на отрицательное число битов
"; }
Ожидаемый результат
Нельзя сдвинуть на отрицательное число битов

Оператор << сдвигает биты влево. Для выбора направления нужен другой оператор >> с неотрицательным количеством, а не отрицательная настройка.

Результат intdiv должен помещаться в intСамостоятельный пример
<?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. Выбор функции должен соответствовать смыслу данных.

Как возникает DivisionByZeroError
  1. $sum = 120, $count = 0
  2. $sum / $count не определяет среднее
  3. DivisionByZeroError
  4. Для пустой выборки показываем «нет данных»
Нельзя посчитать среднее без наблюденийСамостоятельный пример
<?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.

Как возникает AssertionError
  1. Разработчик утверждает: набор заполнен
  2. При включённом assert условие false
  3. AssertionError описывает нарушенный инвариант
  4. Исправляем внутреннюю логику
Явный объект не зависит от настроек assertСамостоятельный пример
<?php
declare(strict_types=1);

try { throw new AssertionError('Нарушен внутренний инвариант'); }
catch (AssertionError $error) { echo $error->getMessage(), "
"; }
Ожидаемый результат
Нарушен внутренний инвариант

Пример учит типу и catch; он не доказывает, что assert включён на вашем сервере.

Наблюдать включённый 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 нельзя.

Как возникает CompileError
  1. Выполняющийся код разбирает дополнительный фрагмент
  2. Часть ошибок представлена Throwable
  3. CompileError может принять дочерний ParseError
  4. Другие фатальные ошибки этим не становятся перехватываемыми
Общий обработчик принимает дочерний 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.

Поймать дефект подготовки кода общей ветвью ErrorСамостоятельный пример
<?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. Это повод исправить код, а не продолжать важную операцию с частично загруженной логикой.

Как возникает ParseError
  1. Загрузчик вызывает require broken.php
  2. PHP разбирает синтаксис нового файла
  3. return (1 + ); не завершает выражение
  4. 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 или проверьте вход раньше.

Как возникает UnhandledMatchError
  1. $method = "drone"
  2. match знает только pickup и courier
  3. Совпадения и default нет
  4. 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 затем пытается продолжить работу с ним.

Как возникает ClosedGeneratorException
  1. $outer делегирует $source через yield from
  2. Чужой вызов $source->next() закрывает источник ошибкой
  3. $outer->next() пытается продолжить делегирование
  4. 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.

Как возникает FiberError
  1. Основной поток выполняет Fiber::suspend()
  2. Активной Fiber сейчас нет
  3. Движок создаёт FiberError
  4. catch показывает неверный переход состояния
Приостановка вне Fiber невозможнаСамостоятельный пример
<?php
declare(strict_types=1);

try { Fiber::suspend(); }
catch (FiberError $error) { echo "Сейчас нет выполняющейся Fiber
"; }
Ожидаемый результат
Сейчас нет выполняющейся Fiber

Fiber::suspend возвращает управление тому, кто запустил Fiber. В основном потоке такого владельца нет.

Запускать одну 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.

Как возникает RequestParseBodyException
  1. request_parse_body читает тип тела
  2. В CLI Content-Type отсутствует
  3. RequestParseBodyException
  4. У реального 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 и проверяется серверным запуском.

CLI без Content-Type воспроизводит отдельную границуСамостоятельный пример
<?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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. validated = false, вызвана публикация
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new LogicException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. Запрошен compress, в реестре есть только upper
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new BadFunctionCallException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. ReportApi получил неподдерживаемый eraseAll()
  2. __call отклоняет недоступный метод
  3. Код явно выполняет throw new BadMethodCallException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. delivery = "teleport", разрешены pickup/courier
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new DomainException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. Вместо массива настроек передана строка
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new InvalidArgumentException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. Код AB занимает 2 байта вместо 6
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new LengthException
  4. catch получает сообщение: ASCII-код должен содержать 6 байтов
Проверить длину ASCII-кода наклейкиСамостоятельный пример
<?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; допустимые символы проверяются отдельно. Для пользовательского русского названия нельзя называть этот результат числом букв или графем.

Проверить длину 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. month = 13 при договоре 1…12
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new OutOfRangeException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. Поставщик оказался недоступен во время операции
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new RuntimeException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. Запрошен SKU-2, в коллекции есть только SKU-1
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new OutOfBoundsException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. В очереди 2 задания при вместимости 2
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new OverflowException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. 12 / 10 → 1.2, допустима доля 0…1
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new RangeException
  4. catch получает сообщение: Доля выполнения вне 0..1
Не показывать прогресс выше 100 процентовСамостоятельный пример
<?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%: сначала надо выяснить несогласованность счётчиков.

Не показывать прогресс выше 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. Из пустого списка пытаются извлечь задание
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new UnderflowException
  4. 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. Собственных новых полей/методов этот класс не объявляет.
Проверка договора в примере
  1. В response.items пришла строка вместо массива
  2. if обнаруживает нарушение
  3. Код явно выполняет throw new UnexpectedValueException
  4. 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-предупреждения.

PHP 5.1+ · Core

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.
severityint уровня ошибки, по умолчанию E_ERROR; intended значения E_*, хотя конструктор принимает int.
filename/lineМесто исходного предупреждения; null оставляет место создания. С PHP 8.0 параметры явно nullable.
previousПредыдущий Throwable или null.
ВозвратКонструктор не возвращает значение. new создаёт ErrorException.
Маршрут вызова
  1. message, E_WARNING, supplier.php, строка 80
  2. new ErrorException сохраняет поля отдельно
  3. getFile/getLine ведут к supplier.php:80
  4. 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.

PHP 5.1+ · Core

final public function getSeverity(): int
Часть вызоваДоговор
НазначениеПрочитать уровень PHP-ошибки, записанный в ErrorException.
ПараметрыНет.
Возвратint severity; не code приложения.
Наследованиеfinal, не переопределяется.
Маршрут вызова
  1. Создан ErrorException с E_WARNING
  2. getSeverity() читает этот уровень
  3. match выбирает метку warning
  4. Закрытая диагностика получает подходящую категорию
Уровень по умолчаниюСамостоятельный пример
<?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.

PHP 8.1+ · Core

public function __construct()
Часть вызоваДоговор
НазначениеПоказать запрет ручного создания внутреннего FiberError.
АргументыНет.
РезультатПрямой new FiberError() выбрасывает Error. Несмотря на public-сигнатуру, рабочий объект так не создаётся.
Маршрут вызова
  1. Код выполняет new FiberError()
  2. Конструктор запрещает ручное создание
  3. Выбрасывается Error
  4. Для своей ошибки задачи нужен другой тип
Конструктор сам запрещает созданиеСамостоятельный пример
<?php
try { new FiberError(); }
catch (Error $error) { echo get_class($error), "\n"; }
Ожидаемый результат
Error

Это именно Error, а не созданный пользователем FiberError. В свой код задач вводят собственный RuntimeException.

Настоящий FiberError создаёт движокСамостоятельный пример
<?php
try { Fiber::suspend(); }
catch (FiberError $error) { var_dump($error instanceof Error, $error instanceof Throwable); }
Ожидаемый результат
bool(true)
bool(true)

В основном потоке отсутствует активная Fiber. Здесь подтверждён реальный внутренний отказ, а не ручной объект.

Предупреждения и исключения. Общая иерархия.

Throwable::getMessageПрочитать описание сбоя, не печатая его автоматически.

PHP 7+ · Core

public function getMessage(): string

Прочитать описание сбоя, не печатая его автоматически.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтрока message, переданная при создании объекта; по умолчанию пустая.
НаследованиеУ интерфейса Throwable это договор; выполнить его может объект Exception, Error или их потомок. Создать new Throwable нельзя.
Сообщение — часть объекта
  1. new RuntimeException("Сервис недоступен")
  2. $error хранит объект
  3. $error->getMessage() возвращает строку
  4. 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Прочитать код, который выбрал источник исключения.

PHP 7+ · Core

public function getCode(): int

Прочитать код, который выбрал источник исключения.

Часть договораЧто означает
ПараметрыНет.
ВозвратОбычно int, по умолчанию 0. Для Exception/Throwable отдельные потомки, например PDOException, могут хранить строковый код.
СмыслЭто не автоматически HTTP-статус и не обязательно уровень E_WARNING. Значение определяется конкретным классом/операцией.
Что происходит при Throwable::getCode
  1. new RuntimeException("Поставщик", 1201)
  2. В поле code сохранено 1201
  3. getCode() → 1201
  4. Обработчик применяет договор именно этого источника
Прочитать код отказа в пределах договора приложенияСамостоятельный пример
<?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Найти файл создания объекта сбоя.

PHP 7+ · Core

public function getFile(): string

Найти файл создания объекта сбоя.

Часть договораЧто означает
ПараметрыНет.
ВозвратПуть к файлу.
МоментМесто создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю.
Особый случайErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе.
Что происходит при Throwable::getFile
  1. Объект создан в example.php
  2. Позже тот же объект попадает в catch
  3. getFile() → путь исходного example.php
Зафиксировать место созданияСамостоятельный пример
<?php
declare(strict_types=1);

$createdLine = __LINE__ + 1;
$error = new RuntimeException('Диагностика');
echo basename($error->getFile()), "\n";
Ожидаемый результат
example.php

__FILE__ и __LINE__ — магические константы текущего файла и строки. basename оставляет только имя файла для воспроизводимого вывода. Полный путь зависит от сервера.

Повторный throw сохраняет исходное место и причинуСамостоятельный пример
<?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Найти строку создания объекта сбоя.

PHP 7+ · Core

public function getLine(): int

Найти строку создания объекта сбоя.

Часть договораЧто означает
ПараметрыНет.
ВозвратНомер строки, начиная с 1.
МоментМесто создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю.
Особый случайErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе.
Что происходит при Throwable::getLine
  1. __LINE__ + 1 сохраняет ожидаемую строку
  2. На следующей строке создаётся объект
  3. getLine() возвращает сохранённое место
  4. Номер 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 соответствует запуску учебного примера. В закрытом журнале можно хранить полный путь, посетителю его обычно не показывают.

Повторный throw сохраняет исходное место и причинуСамостоятельный пример
<?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Получить непосредственную причину, сохранённую при обёртывании.

PHP 7+ · Core

public function getPrevious(): ?Throwable

Получить непосредственную причину, сохранённую при обёртывании.

Часть договораЧто означает
ПараметрыНет.
ВозвратПредыдущий Throwable или null. Вопросительный знак означает, что null допустим.
ГлубинаОдин шаг цепочки; более раннюю причину нужно читать следующим getPrevious().
ВерсияException: PHP 5.3+. Error и общий Throwable: PHP 7+. В современном PHP причиной может быть и Exception, и Error.
Сохранённая причина
  1. Сбой соединения
  2. Сбой загрузки каталога с previous
  3. Верхний обработчик видит задачу
  4. 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Получить стек вызовов как массив для диагностического инструмента.

PHP 7+ · Core

public function getTrace(): array

Получить стек вызовов как массив для диагностического инструмента.

Часть договораЧто означает
ПараметрыНет.
ВозвратМассив кадров в формате debug_backtrace: могут быть file, line, function, class, type, args и другие предусмотренные поля. Не все поля обязаны присутствовать.
ПорядокБлижайший вызов идёт первым, затем вызывавшие его места.
ДанныеНаличие args зависит от конфигурации zend.exception_ignore_args. SensitiveParameter может заменить чувствительный аргумент оболочкой. Трасса не гарантирует очищенный журнал.
Что происходит при Throwable::getTrace
  1. refreshCatalog() вызывает fetchFeed()
  2. В fetchFeed() создан объект сбоя
  3. getTrace() → массив кадров
  4. Ближайший 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Получить готовый текст стека вместо массива кадров.

PHP 7+ · Core

public function getTraceAsString(): string

Получить готовый текст стека вместо массива кадров.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтроковое представление трассировки.
ОтличиеЭто стек, а не полное представление с классом/сообщением/местом как у __toString(). Формат не является протоколом для парсинга бизнес-данных.
Что происходит при Throwable::getTraceAsString
  1. Объект уже содержит стек вызовов
  2. getTraceAsString() форматирует этот стек
  3. Возвращается текст, путь зависит от сервера
  4. Текст включается в закрытую диагностику
Прочитать настоящий стек вызова без абсолютного путиСамостоятельный пример
<?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Получить полное диагностическое строковое представление объекта.

PHP 7+ · Core

public function __toString(): string

Получить полное диагностическое строковое представление объекта.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтрока с типом, сообщением, местом и трассировкой; при цепочке включает связанные причины.
ВызовЯвное $error->__toString() или приведение (string) $error. У Exception/Error метод можно переопределить, в отличие от final-геттеров.
ИнтерфейсС PHP 8 Throwable расширяет Stringable. Строковое представление не означает, что strict-параметр string автоматически принимает объект.
Что происходит при Throwable::__toString
  1. Объект содержит тип, сообщение, место и стек
  2. (string) $error вызывает представление
  3. Возвращается полный текст с этими данными
  4. Код отдельно выбирает, куда его передать
Увидеть полное техническое представление ошибкиСамостоятельный пример
<?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Прочитать описание сбоя, не печатая его автоматически.

PHP 5+ · Core

public function getMessage(): string

Прочитать описание сбоя, не печатая его автоматически.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтрока message, переданная при создании объекта; по умолчанию пустая.
НаследованиеМетод final: потомок не может подменить его реализацию.
Сообщение — часть объекта
  1. new Exception("Сервис недоступен")
  2. $error хранит объект
  3. $error->getMessage() возвращает строку
  4. 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Прочитать код, который выбрал источник исключения.

PHP 5+ · Core

public function getCode(): int

Прочитать код, который выбрал источник исключения.

Часть договораЧто означает
ПараметрыНет.
ВозвратОбычно int, по умолчанию 0. Для Exception/Throwable отдельные потомки, например PDOException, могут хранить строковый код.
СмыслЭто не автоматически HTTP-статус и не обязательно уровень E_WARNING. Значение определяется конкретным классом/операцией.
Что происходит при Exception::getCode
  1. new Exception("Поставщик", 1201)
  2. В поле code сохранено 1201
  3. getCode() → 1201
  4. Обработчик применяет договор именно этого источника
Прочитать код отказа в пределах договора приложенияСамостоятельный пример
<?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Найти файл создания объекта сбоя.

PHP 5+ · Core

public function getFile(): string

Найти файл создания объекта сбоя.

Часть договораЧто означает
ПараметрыНет.
ВозвратПуть к файлу.
МоментМесто создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю.
Особый случайErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе.
Что происходит при Exception::getFile
  1. Объект создан в example.php
  2. Позже тот же объект попадает в catch
  3. getFile() → путь исходного example.php
Зафиксировать место созданияСамостоятельный пример
<?php
declare(strict_types=1);

$createdLine = __LINE__ + 1;
$error = new Exception('Диагностика');
echo basename($error->getFile()), "\n";
Ожидаемый результат
example.php

__FILE__ и __LINE__ — магические константы текущего файла и строки. basename оставляет только имя файла для воспроизводимого вывода. Полный путь зависит от сервера.

Повторный throw сохраняет исходное место и причинуСамостоятельный пример
<?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Найти строку создания объекта сбоя.

PHP 5+ · Core

public function getLine(): int

Найти строку создания объекта сбоя.

Часть договораЧто означает
ПараметрыНет.
ВозвратНомер строки, начиная с 1.
МоментМесто создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю.
Особый случайErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе.
Что происходит при Exception::getLine
  1. __LINE__ + 1 сохраняет ожидаемую строку
  2. На следующей строке создаётся объект
  3. getLine() возвращает сохранённое место
  4. Номер 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 соответствует запуску учебного примера. В закрытом журнале можно хранить полный путь, посетителю его обычно не показывают.

Повторный throw сохраняет исходное место и причинуСамостоятельный пример
<?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Получить непосредственную причину, сохранённую при обёртывании.

PHP 5.3+ · Core

public function getPrevious(): ?Throwable

Получить непосредственную причину, сохранённую при обёртывании.

Часть договораЧто означает
ПараметрыНет.
ВозвратПредыдущий Throwable или null. Вопросительный знак означает, что null допустим.
ГлубинаОдин шаг цепочки; более раннюю причину нужно читать следующим getPrevious().
ВерсияException: PHP 5.3+. Error и общий Throwable: PHP 7+. В современном PHP причиной может быть и Exception, и Error.
Сохранённая причина
  1. Сбой соединения
  2. Сбой загрузки каталога с previous
  3. Верхний обработчик видит задачу
  4. 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Получить стек вызовов как массив для диагностического инструмента.

PHP 5+ · Core

public function getTrace(): array

Получить стек вызовов как массив для диагностического инструмента.

Часть договораЧто означает
ПараметрыНет.
ВозвратМассив кадров в формате debug_backtrace: могут быть file, line, function, class, type, args и другие предусмотренные поля. Не все поля обязаны присутствовать.
ПорядокБлижайший вызов идёт первым, затем вызывавшие его места.
ДанныеНаличие args зависит от конфигурации zend.exception_ignore_args. SensitiveParameter может заменить чувствительный аргумент оболочкой. Трасса не гарантирует очищенный журнал.
Что происходит при Exception::getTrace
  1. refreshCatalog() вызывает fetchFeed()
  2. В fetchFeed() создан объект сбоя
  3. getTrace() → массив кадров
  4. Ближайший 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Получить готовый текст стека вместо массива кадров.

PHP 5+ · Core

public function getTraceAsString(): string

Получить готовый текст стека вместо массива кадров.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтроковое представление трассировки.
ОтличиеЭто стек, а не полное представление с классом/сообщением/местом как у __toString(). Формат не является протоколом для парсинга бизнес-данных.
Что происходит при Exception::getTraceAsString
  1. Объект уже содержит стек вызовов
  2. getTraceAsString() форматирует этот стек
  3. Возвращается текст, путь зависит от сервера
  4. Текст включается в закрытую диагностику
Прочитать настоящий стек вызова без абсолютного путиСамостоятельный пример
<?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Получить полное диагностическое строковое представление объекта.

PHP 5+ · Core

public function __toString(): string

Получить полное диагностическое строковое представление объекта.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтрока с типом, сообщением, местом и трассировкой; при цепочке включает связанные причины.
ВызовЯвное $error->__toString() или приведение (string) $error. У Exception/Error метод можно переопределить, в отличие от final-геттеров.
ИнтерфейсС PHP 8 Throwable расширяет Stringable. Строковое представление не означает, что strict-параметр string автоматически принимает объект.
Что происходит при Exception::__toString
  1. Объект содержит тип, сообщение, место и стек
  2. (string) $error вызывает представление
  3. Возвращается полный текст с этими данными
  4. Код отдельно выбирает, куда его передать
Увидеть полное техническое представление ошибкиСамостоятельный пример
<?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Прочитать описание сбоя, не печатая его автоматически.

PHP 7+ · Core

public function getMessage(): string

Прочитать описание сбоя, не печатая его автоматически.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтрока message, переданная при создании объекта; по умолчанию пустая.
НаследованиеМетод final: потомок не может подменить его реализацию.
Сообщение — часть объекта
  1. new Error("Сервис недоступен")
  2. $error хранит объект
  3. $error->getMessage() возвращает строку
  4. 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Прочитать код, который выбрал источник исключения.

PHP 7+ · Core

public function getCode(): int

Прочитать код, который выбрал источник исключения.

Часть договораЧто означает
ПараметрыНет.
ВозвратОбычно int, по умолчанию 0. Для Exception/Throwable отдельные потомки, например PDOException, могут хранить строковый код.
СмыслЭто не автоматически HTTP-статус и не обязательно уровень E_WARNING. Значение определяется конкретным классом/операцией.
Что происходит при Error::getCode
  1. new Error("Поставщик", 1201)
  2. В поле code сохранено 1201
  3. getCode() → 1201
  4. Обработчик применяет договор именно этого источника
Прочитать код отказа в пределах договора приложенияСамостоятельный пример
<?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Найти файл создания объекта сбоя.

PHP 7+ · Core

public function getFile(): string

Найти файл создания объекта сбоя.

Часть договораЧто означает
ПараметрыНет.
ВозвратПуть к файлу.
МоментМесто создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю.
Особый случайErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе.
Что происходит при Error::getFile
  1. Объект создан в example.php
  2. Позже тот же объект попадает в catch
  3. getFile() → путь исходного example.php
Зафиксировать место созданияСамостоятельный пример
<?php
declare(strict_types=1);

$createdLine = __LINE__ + 1;
$error = new Error('Диагностика');
echo basename($error->getFile()), "\n";
Ожидаемый результат
example.php

__FILE__ и __LINE__ — магические константы текущего файла и строки. basename оставляет только имя файла для воспроизводимого вывода. Полный путь зависит от сервера.

Повторный throw сохраняет исходное место и причинуСамостоятельный пример
<?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Найти строку создания объекта сбоя.

PHP 7+ · Core

public function getLine(): int

Найти строку создания объекта сбоя.

Часть договораЧто означает
ПараметрыНет.
ВозвратНомер строки, начиная с 1.
МоментМесто создания объекта/возникновения ошибки, а не расположение catch. Повторный throw того же объекта не переписывает его историю.
Особый случайErrorException позволяет явно указать исходный файл/строку предупреждения в конструкторе.
Что происходит при Error::getLine
  1. __LINE__ + 1 сохраняет ожидаемую строку
  2. На следующей строке создаётся объект
  3. getLine() возвращает сохранённое место
  4. Номер 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 соответствует запуску учебного примера. В закрытом журнале можно хранить полный путь, посетителю его обычно не показывают.

Повторный throw сохраняет исходное место и причинуСамостоятельный пример
<?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Получить непосредственную причину, сохранённую при обёртывании.

PHP 7+ · Core

public function getPrevious(): ?Throwable

Получить непосредственную причину, сохранённую при обёртывании.

Часть договораЧто означает
ПараметрыНет.
ВозвратПредыдущий Throwable или null. Вопросительный знак означает, что null допустим.
ГлубинаОдин шаг цепочки; более раннюю причину нужно читать следующим getPrevious().
ВерсияException: PHP 5.3+. Error и общий Throwable: PHP 7+. В современном PHP причиной может быть и Exception, и Error.
Сохранённая причина
  1. Сбой соединения
  2. Сбой загрузки каталога с previous
  3. Верхний обработчик видит задачу
  4. 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Получить стек вызовов как массив для диагностического инструмента.

PHP 7+ · Core

public function getTrace(): array

Получить стек вызовов как массив для диагностического инструмента.

Часть договораЧто означает
ПараметрыНет.
ВозвратМассив кадров в формате debug_backtrace: могут быть file, line, function, class, type, args и другие предусмотренные поля. Не все поля обязаны присутствовать.
ПорядокБлижайший вызов идёт первым, затем вызывавшие его места.
ДанныеНаличие args зависит от конфигурации zend.exception_ignore_args. SensitiveParameter может заменить чувствительный аргумент оболочкой. Трасса не гарантирует очищенный журнал.
Что происходит при Error::getTrace
  1. refreshCatalog() вызывает fetchFeed()
  2. В fetchFeed() создан объект сбоя
  3. getTrace() → массив кадров
  4. Ближайший 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Получить готовый текст стека вместо массива кадров.

PHP 7+ · Core

public function getTraceAsString(): string

Получить готовый текст стека вместо массива кадров.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтроковое представление трассировки.
ОтличиеЭто стек, а не полное представление с классом/сообщением/местом как у __toString(). Формат не является протоколом для парсинга бизнес-данных.
Что происходит при Error::getTraceAsString
  1. Объект уже содержит стек вызовов
  2. getTraceAsString() форматирует этот стек
  3. Возвращается текст, путь зависит от сервера
  4. Текст включается в закрытую диагностику
Прочитать настоящий стек вызова без абсолютного путиСамостоятельный пример
<?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Получить полное диагностическое строковое представление объекта.

PHP 7+ · Core

public function __toString(): string

Получить полное диагностическое строковое представление объекта.

Часть договораЧто означает
ПараметрыНет.
ВозвратСтрока с типом, сообщением, местом и трассировкой; при цепочке включает связанные причины.
ВызовЯвное $error->__toString() или приведение (string) $error. У Exception/Error метод можно переопределить, в отличие от final-геттеров.
ИнтерфейсС PHP 8 Throwable расширяет Stringable. Строковое представление не означает, что strict-параметр string автоматически принимает объект.
Что происходит при Error::__toString
  1. Объект содержит тип, сообщение, место и стек
  2. (string) $error вызывает представление
  3. Возвращается полный текст с этими данными
  4. Код отдельно выбирает, куда его передать
Увидеть полное техническое представление ошибкиСамостоятельный пример
<?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Создать объект с сообщением, кодом и необязательной причиной.

PHP 5+ · Core

public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)

Создать объект с сообщением, кодом и необязательной причиной.

Часть договораЧто означает
messageОписание сбоя; по умолчанию пустая строка. Официальный договор предупреждает: сообщение не является binary-safe хранилищем произвольных байтов.
codeКод приложения, по умолчанию 0.
previousПричина Throwable или null.
ВозвратУ конструктора нет возвращаемого значения; new возвращает созданный объект. Создать и выбросить — отдельные действия.
Что происходит при Exception::__construct
  1. message = "Не удалось сохранить", code = 25
  2. new Exception(...) создаёт объект
  3. $error хранит объект и его историю
  4. Выполнение продолжается, пока нет 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Понять, почему объект исключения нельзя клонировать.

PHP 5+ · Core

private function __clone(): void

Понять, почему объект исключения нельзя клонировать.

Часть договораЧто означает
ДоступностьСлужебный private-метод. Это ограничение, а не операция, которую следует вызывать.
Поведениеclone для Exception/Error и потомков недопустим и вызывает Error.
ВерсияС PHP 8.1 метод больше не final; это не снимает запрет клонирования объектов исключений.
Что происходит при Exception::__clone
  1. Есть исходный $error
  2. clone пытается копировать исключение
  3. Движок запрещает копию и выбрасывает Error
  4. Для контекста нужен новый объект с previous
Попытка клонировать заканчивается ErrorСамостоятельный пример
<?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Создать объект с сообщением, кодом и необязательной причиной.

PHP 7+ · Core

public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)

Создать объект с сообщением, кодом и необязательной причиной.

Часть договораЧто означает
messageОписание сбоя; по умолчанию пустая строка. Официальный договор предупреждает: сообщение не является binary-safe хранилищем произвольных байтов.
codeКод приложения, по умолчанию 0.
previousПричина Throwable или null.
ВозвратУ конструктора нет возвращаемого значения; new возвращает созданный объект. Создать и выбросить — отдельные действия.
Что происходит при Error::__construct
  1. message = "Не удалось сохранить", code = 25
  2. new Error(...) создаёт объект
  3. $error хранит объект и его историю
  4. Выполнение продолжается, пока нет 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Понять, почему объект исключения нельзя клонировать.

PHP 7+ · Core

private function __clone(): void

Понять, почему объект исключения нельзя клонировать.

Часть договораЧто означает
ДоступностьСлужебный private-метод. Это ограничение, а не операция, которую следует вызывать.
Поведениеclone для Exception/Error и потомков недопустим и вызывает Error.
ВерсияС PHP 8.1 метод больше не final; это не снимает запрет клонирования объектов исключений.
Что происходит при Error::__clone
  1. Есть исходный $error
  2. clone пытается копировать исключение
  3. Движок запрещает копию и выбрасывает Error
  4. Для контекста нужен новый объект с previous
Попытка клонировать заканчивается ErrorСамостоятельный пример
<?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 с самого начала.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.