Перейти к содержимому
Поиск
Php Разработка

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

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 180 · После знакомства с первым запросом и вызовом функции

Ошибки MongoDB в PHP: что поймать и что показать пользователю

Различаем пустую выдачу, неверное значение ObjectId, TypeError, ошибку курсора и повреждённый пакет. Строим обработчики на настоящих объектах расширения и сопоставляем их с результатом для пользователя.

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

В очереди склада ноль заказов. Что показать оператору — «очередь пуста» или «не удалось получить данные»? Для первого сообщения запрос должен успешно завершиться. Разберём путь от одной пустой выдачи до обработки неверного номера, сломанной накладной и отказа сервера.

Три возможных ответа страницы
  1. PHP получает исходный номер или фильтр
  2. Ошибку формата можно обнаружить до сетевого запроса
  3. Успешно выполненный поиск может вернуть 0 строк
  4. При исключении управление переходит в подходящий catch
try {
    $result = $manager->executeQuery($namespace, $query)->toArray();
} catch (MongoDB\Driver\Exception\Exception $error) {
    return 'Не удалось получить данные';
}
return $result === [] ? 'Очередь пуста' : 'Есть заказы';

Вызов executeQuery() отправляет запрос, toArray() дочитывает небольшую страницу. Фигурные скобки после try ограничивают участок работы. Если возникнет подходящее исключение, PHP пропустит оставшиеся инструкции этого участка, сохранит объект в $error и войдёт в catch. Строка return вернёт сообщение вызвавшему коду. После успешного чтения выполнение продолжится за блоком catch. Рабочая очередь из двух фильтров.

Общая ветвь в коротком фрагменте даёт публичное сообщение. Для настоящей интеграции надо также сохранить разрешённые диагностические сведения и выбрать реакцию согласно договорённости сторон. Один обработчик с пустым телом уничтожает информацию о причине. Для начала нужны основы исключений PHP и подключение драйвера.

В своём прикладном коде можно обработать ошибки аргументов, неверного порядка вызовов и испорченных значений отдельно. Если уже известно, что ошибка произошла при выборе сервера, аутентификации или выполнении команды, переходите к диагностике отказов соединения и сервера. Официальный раздел исключений расширения.

Читать иерархию исключений и выбирать catch

Как прочитать имя MongoDB\Driver\Exception\LogicException? Слева до последней обратной косой черты расположен namespace — пространство имён. Справа — имя класса, которое может совпасть с именем класса стандартной библиотеки PHP (SPL). Запись use ... as DriverLogic задаёт удобное короткое имя в данном файле. Глобальное \LogicException ссылается на класс PHP напрямую.

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

Тип в исключениях MongoDBНепосредственный родительКакое событие искать
ExceptionИнтерфейс, общий для исключений драйвераГраница обработки компонента
InvalidArgumentException\InvalidArgumentException (SPL)Недопустимое значение или параметры вызова
LogicException\LogicException (SPL)Объект вызван в недопустимом состоянии
UnexpectedValueException\UnexpectedValueException (SPL)Неожиданное представление значения, например повреждённый BSON
RuntimeException\RuntimeException (SPL)Родительская ветвь ошибок выполнения
ConnectionExceptionRuntimeExceptionУстановление или использование связи
AuthenticationException и ConnectionTimeoutExceptionConnectionExceptionПроверка учётных данных или предел ожидания выбора/соединения
EncryptionExceptionRuntimeExceptionОтказ операции шифрования
ServerExceptionRuntimeExceptionОшибки выполнения со стороны сервера
CommandException, ExecutionTimeoutExceptionServerExceptionОтказ команды или исчерпанный срок её выполнения
BulkWriteException и BulkWriteCommandExceptionServerExceptionВ 2.5.5 это разные виды исключений операций пакетной записи

Семейства InvalidArgumentException и LogicException внутри пространства имён драйвера — соседние ветви. В SPL класс \InvalidArgumentException сам наследует \LogicException. Названия сходны, но связь с глобальным SPL-классом не делает экземпляр потомком одноимённого драйверского класса. Это различие можно проверить через get_parent_class и instanceof в реальной программе для загруженной версии.

Официальное дерево также приводит исторические типы. SSLConnectionException объявлялся наследником ConnectionException, устарел с 1.5, не выбрасывался расширением и удалён в 2.0. WriteException не выбрасывался напрямую, устарел с 1.20 и удалён в 2.0. В 2.5.5 у BulkWriteException прямой родитель ServerException. Не размещайте исчезнувший тип как единственную ветвь для ошибок современной записи. Официальное дерево классов.

Решить, что сообщить интерфейсу

Если произошлоЧто будет видно пользователю в примерахЧто проверяет программа
Успешный фильтр без совпадений«Очередь пуста»Успешное чтение и ноль строк
Неверная строка для ObjectId«Неверный формат»Отказ до отправки поискового запроса
Верный ID, а записи нет«Не найден»Запрос выполнен, документ отсутствует
Невозможно перемотать курсорИсправленная накладная из трёх строкТип и положение курсора
Повреждён бинарный пакетОтдельная причина отклоненияОдин допустимый пакет сохранён, плохой не записан
Внутренняя ошибка реализацииКороткое сообщение и ID операции при необходимостиДиагностика по классу и коду, без отправки стека клиенту

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

Запустить самостоятельные примеры

Архив каждого урока содержит example.php, er134-helper.php и README. Программы запускаются с PHP CLI и ext-mongodb на отдельной одноузловой MongoDB Community replica set phpmanRs. Для текущего слоя проверяется поведение ext-mongodb 2.5.5, PHP 8.5.11 и MongoDB 8.0.34. В каждом запуске есть собственная случайно именованная коллекция, которая удаляется в finally. До и после этого подсчитываются только точные собственные имена.

php --ri mongodb
$env:PHPMAN_MONGODB_URI='mongodb://127.0.0.1:27076/?replicaSet=phpmanRs&retryWrites=false'
$env:PHPMAN_MONGODB_DATABASE='php_manual_txn_lab'
php example.php

После запуска откройте result.html. result.json содержит ту же таблицу, metrics.json — проверки. Технические сообщения, URI и стеки вызовов не включаются в публичный текст результата. В последнем примере сравниваются прикладные статусы и отдельно проверяется сохранённый остаток.

Показать пустую очередь после успешного чтения

У оператора отдела доставки в разделе «Ожидают» сейчас ноль заявок, хотя соседний раздел «Готовы» содержит один заказ. Интерфейс должен показать нормальную пустую очередь.

Путь выполнения
  1. Записать S-10 в статусе «готов»
  2. Выполнить запрос «ожидает» и дочитать курсор
  3. Получить 0 строк, обработчик ошибки не вызывался
  4. Проверить соседний фильтр и показать S-10

Короткий фрагмент

try {
    $rows = $manager->executeQuery($namespace, $query)->toArray();
} catch (MongoDB\Driver\Exception\Exception $error) {
    // Передаём ошибку внешнему обработчику.
    throw $error;
}
echo $rows === [] ? 'Очередь пуста' : 'Есть заказы';

Результат

Что проверилиРезультат
Ожидают0
Готовы1
Открываем заказS-10
СообщениеОчередь пуста

Почему управление пошло так

try отмечает участок, где ожидаем вызов, способный выбросить исключение. Если он успешно вернул пустой массив, выполнение идёт после catch. В полном примере неожиданная ошибка пробрасывается наружу через throw; перенос её в пустой массив исказил бы результат.

Границы и типичные ошибки

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

Полная самостоятельная программа и архив
Показать пустую очередь после успешного чтенияСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    $inserted = er134Put($manager,$ns,[['_id'=>'S-10','state'=>'готов']]);
    $caught = false;
    try {
        $waiting = er134Cursor($manager,$ns,['state'=>'ожидает'],['_id'=>1])->toArray();
        $ready = er134Cursor($manager,$ns,['state'=>'готов'],['_id'=>1])->toArray();
    } catch (DriverFailure $error) {
        $caught = true;
        throw $error;
    }
    $screen = $waiting === [] ? 'Очередь пуста' : 'Есть заказы';
    $checks = ['one_fixture'=>$inserted === 1,'no_throw'=>$caught === false,'empty_list'=>$waiting === [],
        'found_ready'=>count($ready) === 1,'known_order'=>$ready[0]['_id'] === 'S-10','correct_screen'=>$screen === 'Очередь пуста'];
    $rows = [['Ожидают',count($waiting)],['Готовы',count($ready)],['Открываем заказ',$ready[0]['_id']],['Сообщение',$screen]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-empty-is-result', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-empty-is-result: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-empty-is-result: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

Упражнение. Поменяйте статус S-10 на «ожидает». В первом разделе теперь одна строка. catch при этом не нужен так же, как и при пустой выборке.

Исторический вопрос о пустой выдаче на старом MongoClient помогает поставить современный опыт: успешный запрос и ноль строк проверяются отдельно от реально возникшего исключения. Empty aggregation cursor in an older PHP driver на Stack Overflow.

Отличить ошибку в номере от корректного отсутствующего заказа

Открывая заказ по ссылке, покупатель может передать корректный номер другого заказа или испорченное значение. Сначала проверим формат, затем по корректному ObjectId действительно обратимся к базе.

Путь выполнения
  1. Для известной строки создать ObjectId и прочитать R-1
  2. Для второго корректного ObjectId получить 0 строк
  3. Конструктор отвергает not-an-id
  4. Сообщить об ошибке формата до поиска

Короткий фрагмент

try {
    $id = new MongoDB\BSON\ObjectId($text);
} catch (MongoDB\Driver\Exception\InvalidArgumentException $error) {
    return 'Неверный формат';
}

Результат

Что проверилиРезультат
Существующий IDR-1
Верный, но неизвестный IDНе найден
Строка not-an-idНеверный формат
Отправлено поисков2

Почему управление пошло так

Вход уже является PHP-строкой, но для ObjectId должны быть ровно 24 шестнадцатеричных символа. Конструктор типа BSON выбрасывает DriverInvalid, когда значение не подходит. Узкий try покрывает только это преобразование, поэтому сбой соединения нельзя ошибочно назвать опечаткой.

Границы и типичные ошибки

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

Полная самостоятельная программа и архив
Отличить ошибку в номере от корректного отсутствующего заказаСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    $known = '664000000000000000000001';
    $unknown = '664000000000000000000002';
    er134Put($manager,$ns,[['_id'=>new ObjectId($known),'receipt'=>'R-1']]);
    $states = [];
    $queries = 0;
    foreach ([$known,$unknown,'not-an-id'] as $input) {
        try {
            $id = new ObjectId($input);
        } catch (DriverInvalid $error) {
            $states[] = 'Неверный формат';
            continue;
        }
        $queries++;
        $items = er134Cursor($manager,$ns,['_id'=>$id],['_id'=>0,'receipt'=>1])->toArray();
        $states[] = $items ? $items[0]['receipt'] : 'Не найден';
    }
    $checks = ['known_loaded'=>$states[0] === 'R-1','missing_normal'=>$states[1] === 'Не найден','invalid_rejected'=>$states[2] === 'Неверный формат',
        'only_two_queries'=>$queries === 2,'three_answers'=>count($states) === 3,'fixed_id_is_hex'=>strlen($known) === 24 && ctype_xdigit($known)];
    $rows = [['Существующий ID',$states[0]],['Верный, но неизвестный ID',$states[1]],['Строка not-an-id',$states[2]],['Отправлено поисков',$queries]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-invalid-id', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-invalid-id: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-invalid-id: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

Упражнение. Замените not-an-id строкой из 24 букв z. Она подходит по длине, но не содержит допустимого шестнадцатеричного номера.

Автор рассчитывал проверить результат конструктора условием, но при неверном значении вызов выбрасывает исключение. Checking whether an ID is a valid BSON ObjectId in PHP на Stack Overflow.

Отделить TypeError контракта PHP от аргумента драйвера

Форма иногда отправляет id[] вместо id. Две внешне похожие ошибки возникают на разных шагах. Счётчик входа в функцию покажет, дошло ли дело до проверки драйвера.

Путь выполнения
  1. Вызвать типизированную PHP-функцию с массивом
  2. Получить TypeError до выполнения её тела
  3. Передать строку xyz, дойти до конструктора и получить DriverInvalid
  4. Вызвать с корректной строкой и открыть талон C

Короткий фрагмент

function asOrderId(string $text): MongoDB\BSON\ObjectId {
    return new MongoDB\BSON\ObjectId($text);
}
// При массиве PHP отвергнет обращение ещё до входа в тело.

Результат

Что проверилиРезультат
Вход в функцию при массиве0
TypeError реализует интерфейс драйвераfalse
Ошибка xyz реализует интерфейс драйвераtrue
Корректная строкаталон C

Почему управление пошло так

Двоеточие в сигнатуре функции указывает возвращаемый тип, string перед $text задаёт тип аргумента. Проверка типа приложения и проверка значения внутри конструктора BSON имеют разных владельцев. В примере они ловятся отдельно.

Границы и типичные ошибки

Для данных из формы первую проверку делайте явным is_string на границе ввода. TypeError в неожиданном месте внутри приложения обычно означает дефект программы. Нельзя превращать все ошибки типов сайта в «неверный номер заказа».

Полная самостоятельная программа и архив
Отделить TypeError контракта PHP от аргумента драйвераСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    $known = '664000000000000000000003';
    er134Put($manager,$ns,[['_id'=>new ObjectId($known),'label'=>'талон C']]);
    $entered = 0;
    $lookup = static function(string $text) use($manager,$ns,&$entered): array {
        $entered++;
        $id = new ObjectId($text);
        return er134Cursor($manager,$ns,['_id'=>$id],['_id'=>0,'label'=>1])->toArray();
    };
    $typeIsDriver = null;
    $invalidIsDriver = null;
    try {
        $lookup(['id'=>$known]);
    } catch (TypeError $error) {
        $typeIsDriver = $error instanceof DriverFailure;
    }
    $beforeString = $entered;
    try {
        $lookup('xyz');
    } catch (DriverInvalid $error) {
        $invalidIsDriver = $error instanceof DriverFailure;
    }
    $result = $lookup($known);
    $checks = ['php_type_outside_driver'=>$typeIsDriver === false,'first_call_not_entered'=>$beforeString === 0,
        'invalid_string_driver'=>$invalidIsDriver === true,'two_string_calls'=>$entered === 2,'one_valid_doc'=>count($result) === 1,
        'expected_label'=>$result[0]['label'] === 'талон C'];
    $rows = [['Вход в функцию при массиве',$beforeString],['TypeError реализует интерфейс драйвера',$typeIsDriver],
        ['Ошибка xyz реализует интерфейс драйвера',$invalidIsDriver],['Корректная строка',$result[0]['label']]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-php-type', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-php-type: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-php-type: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

Упражнение. Добавьте is_string перед $lookup. Для массива верните понятное сообщение. Для неизвестной, но корректной строки оставьте обычный поиск в базе.

Исправить печать накладной после преждевременной перемотки

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

Путь выполнения
  1. Курсор стоит на позиции 1
  2. next переводит его на позицию 2
  3. Повторный rewind вызывает LogicException
  4. В исправленном проходе сразу собираем строку и количество

Короткий фрагмент

$cursor->rewind();
$first = $cursor->current();
$cursor->next();
// С этого места rewind не возвращает уже пройденную строку.

Результат

Что проверилиРезультат
До next1
После next2
Перемотка отклоненаtrue
Строк в накладной3
Единиц всего6

Почему управление пошло так

Это нарушение порядка вызовов. После первой позиции курсор больше нельзя перемотать. Исправленный опыт выполняет новый запрос для демонстрации правильного первого обхода, за который получаем и список строк, и итог 6.

Границы и типичные ошибки

Новый запрос видит состояние данных на момент своего выполнения и может дать другой результат при параллельных изменениях. Для повторного показа ровно одной уже прочитанной ограниченной страницы сохраните данные этой страницы.

Полная самостоятельная программа и архив
Исправить печать накладной после преждевременной перемоткиСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    er134Put($manager,$ns,[['_id'=>'1','qty'=>2],['_id'=>'2','qty'=>3],['_id'=>'3','qty'=>1]]);
    $projection = ['_id'=>1,'qty'=>1];
    $cursor = er134Cursor($manager,$ns,[],$projection);
    $cursor->rewind();
    $first = $cursor->current()['_id'];
    $cursor->next();
    $second = $cursor->current()['_id'];
    $rejected = false;
    try {
        $cursor->rewind();
    } catch (DriverLogic $error) {
        $rejected = $error instanceof DriverFailure && $error instanceof \LogicException;
    }
    $ids = [];
    $total = 0;
    foreach (er134Cursor($manager,$ns,[],$projection) as $row) {
        $ids[] = $row['_id'];
        $total += $row['qty'];
    }
    $checks = ['first'=>$first === '1','second'=>$second === '2','rewind_blocked'=>$rejected,
        'three_lines'=>count($ids) === 3,'ordered_lines'=>$ids === ['1','2','3'],'one_pass_total'=>$total === 6];
    $rows = [['До next',$first],['После next',$second],['Перемотка отклонена',$rejected],['Строк в накладной',count($ids)],['Единиц всего',$total]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-cursor-state', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-cursor-state: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-cursor-state: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

Упражнение. Добавьте четвёртую строку и вычислите не только сумму, но и число строк с qty больше 1 в том же проходе.

Читатель пытается использовать один результат в двух проходах. В нашем примере ошибка получена после явно показанного перехода на следующую позицию. Trying to iterate over a MongoDB cursor twice на Stack Overflow.

Принять исправный пакет и отклонить повреждённый BSON

Складской шлюз присылает два BSON-документа. Один исправен, второй содержит завершающий байт, нарушающий формат. Программа должна сохранить один документ и отдельно посчитать отказ.

Путь выполнения
  1. Получить байты одного пакета
  2. Разобрать полностью один BSON-документ
  3. Передавать в запись только успешно разобранное содержимое
  4. Отдельно показать сохранённые и отклонённые пакеты

Короткий фрагмент

try {
    $doc = MongoDB\BSON\Document::fromBSON($bytes);
} catch (MongoDB\Driver\Exception\UnexpectedValueException $error) {
    return 'Повреждённый пакет';
}

Результат

Что проверилиРезультат
Байт в повреждённом пакете5
Отклонено1
Сохранено1
ID сохранённой записиbatch-17

Почему управление пошло так

В бинарном BSON префикс длины и допустимое завершение относятся к формату документа. Функция принимает строку байтов именно как BSON. Неподходящая последовательность вызвала DriverUnexpected, у которой прямой родитель — SPL UnexpectedValueException. В базе осталась одна карточка.

Границы и типичные ошибки

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

Полная самостоятельная программа и архив
Принять исправный пакет и отклонить повреждённый BSONСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    $goodBytes = (string)Document::fromPHP(['_id'=>'batch-17','state'=>'получен']);
    $damaged = "\x05\x00\x00\x00\x01";
    $inserted = 0;
    $rejected = 0;
    $parentMatches = false;
    foreach ([$goodBytes,$damaged] as $bytes) {
        try {
            $parsed = Document::fromBSON($bytes);
        } catch (DriverUnexpected $error) {
            $rejected++;
            $parentMatches = get_parent_class($error) === \UnexpectedValueException::class;
            continue;
        }
        $inserted += er134Put($manager,$ns,[$parsed->toPHP(['root'=>'array','document'=>'array'])]);
    }
    $saved = er134Cursor($manager,$ns,[],['_id'=>1,'state'=>1])->toArray();
    $checks = ['bad_five_bytes'=>strlen($damaged) === 5,'only_valid_insert'=>$inserted === 1,'one_rejected'=>$rejected === 1,
        'spl_parent'=>$parentMatches,'one_record'=>count($saved) === 1,'good_content'=>$saved[0] === ['_id'=>'batch-17','state'=>'получен']];
    $rows = [['Байт в повреждённом пакете',strlen($damaged)],['Отклонено',$rejected],['Сохранено',$inserted],['ID сохранённой записи',$saved[0]['_id']]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-damaged-bson', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-damaged-bson: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-damaged-bson: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

Упражнение. Сформируйте корректный BSON без поля state. Он пройдёт декодирование. Отдельной предметной проверкой отклоните такое сообщение до вставки.

Отмечать причины отказа отдельных строк в импорте

В отчёте импорта мало счётчика «ошибок: 2». Исправлять ID и запрашивать повторную передачу испорченного пакета должны разные люди. Для трёх учебных строк сохраним две причины отказа и одну принятую квитанцию.

Путь выполнения
  1. Прочитать ID и BSON для каждой строки
  2. Перехватить исключение по общему интерфейсу библиотеки
  3. По конкретному типу определить разрешённую причину
  4. Записать корректную строку отдельно от разбора

Короткий фрагмент

catch (MongoDB\Driver\Exception\Exception $error) {
    if ($error instanceof MongoDB\Driver\Exception\UnexpectedValueException) {
        // Эту строку источник должен прислать заново.
    } else {
        throw $error;
    }
}

Результат

Что проверилиРезультат
Принят документQ-18
Вторая строканеверный ID
Третья строкаиспорчен BSON
Записей в коллекции1

Почему управление пошло так

DriverFailure — псевдоним MongoDB\Driver\Exception\Exception. Это интерфейс, поэтому один catch перехватывает обе показанные ветви. Внутри блока instanceof выясняет, к какой ветви относится экземпляр. Все случаи вне заранее выбранного набора передаются внешнему обработчику.

Границы и типичные ошибки

В этом импорте ожидаемые отказы относятся к одной входной строке. Запись сделана за пределами блока разбора: потерю связи во время записи нельзя считать поводом объявить входной документ испорченным и незаметно продолжить.

Полная самостоятельная программа и архив
Отмечать причины отказа отдельных строк в импортеСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    $accepted = [];
    $reasons = [];
    foreach (['664000000000000000000004','bad','664000000000000000000005'] as $i=>$value) {
        try {
            $id = new ObjectId($value);
            $wire = $i === 2 ? "\x05\x00\x00\x00\x01" : (string)Document::fromPHP(['ref'=>'Q-18']);
            $data = Document::fromBSON($wire)->toPHP(['root'=>'array']);
        } catch (DriverFailure $error) {
            if ($error instanceof DriverInvalid) {$reasons[] = 'неверный ID';}
            elseif ($error instanceof DriverUnexpected) {$reasons[] = 'испорчен BSON';}
            else {throw $error;}
            continue;
        }
        er134Put($manager,$ns,[['_id'=>$id,'ref'=>$data['ref']]]);
        $accepted[] = $data['ref'];
    }
    $stored = er134Cursor($manager,$ns,[],['_id'=>0,'ref'=>1])->toArray();
    $checks = ['one_accepted'=>$accepted === ['Q-18'],'two_rejections'=>count($reasons) === 2,
        'bad_id'=>$reasons[0] === 'неверный ID','bad_wire'=>$reasons[1] === 'испорчен BSON',
        'stored_accepted'=>$stored === [['ref'=>'Q-18']],'three_outcomes'=>count($accepted)+count($reasons) === 3];
    $rows = [['Принят документ',$accepted[0]],['Вторая строка',$reasons[0]],['Третья строка',$reasons[1]],['Записей в коллекции',count($stored)]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-driver-boundary', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-driver-boundary: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-driver-boundary: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

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

Проверить семейство реального отказа и дерево своей версии

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

Путь выполнения
  1. Запомнить версию установленного расширения
  2. Получить CommandException от сервера и перехватить как DriverRuntime
  3. Посмотреть прямых SPL-родителей и общий интерфейс
  4. Проверить отсутствие двух исторических классов и сохранность контрольной записи

Короткий фрагмент

$isMarker = (new ReflectionClass(MongoDB\Driver\Exception\Exception::class))->isInterface();
$parent = get_parent_class(MongoDB\Driver\Exception\InvalidArgumentException::class);

Результат

Что проверилиРезультат
Версия расширения2.5.5
Драйверный Exception — интерфейсtrue
Код неизвестной команды59
Родитель DriverRuntimeRuntimeException
Родитель DriverInvalidInvalidArgumentException
Есть исторический WriteExceptionfalse
Есть исторический SSLConnectionExceptionfalse

Почему управление пошло так

Команда вернула код 59 (CommandNotFound). В 2.5.5 CommandException относится к ServerException и DriverRuntime. Отдельный класс DriverInvalid расширяет одноимённый SPL-класс; проверка is_subclass_of для драйверского RuntimeException ложна. Общее у обеих ветвей — интерфейс драйвера.

Границы и типичные ошибки

Код 59 принадлежит данному ответу сервера. Структура ветви PHP-классов описывает допустимый catch и не является таблицей номеров для всех ошибок. На другом выпуске сверьте установленную версию и журнал изменений.

Полная самостоятельная программа и архив
Проверить семейство реального отказа и дерево своей версииСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    er134Put($manager,$ns,[['_id'=>'version-test','state'=>'сохранён']]);
    $event = null;
    try {
        er134Command($manager,$db,['phpmanUnknownContractCommand134'=>1]);
    } catch (DriverRuntime $error) {
        $event = $error;
    }
    if (!$event instanceof CommandException) {throw new RuntimeException('Expected a real command failure');}
    $invalidParent = get_parent_class(DriverInvalid::class);
    $runtimeParent = get_parent_class(DriverRuntime::class);
    $driverInterface = (new ReflectionClass(DriverFailure::class))->isInterface();
    $legacyWrite = class_exists('MongoDB\\Driver\\Exception\\WriteException');
    $oldTls = class_exists('MongoDB\\Driver\\Exception\\SSLConnectionException');
    $record = er134Cursor($manager,$ns,['_id'=>'version-test'],['_id'=>0,'state'=>1])->toArray()[0];
    $checks = ['is_interface'=>$driverInterface,'runtime_parent'=>$runtimeParent === \RuntimeException::class,
        'invalid_parent'=>$invalidParent === \InvalidArgumentException::class,'invalid_separate'=>!is_subclass_of(DriverInvalid::class,DriverRuntime::class),
        'real_code'=>$event->getCode() === 59,'marker'=>$event instanceof DriverFailure,'historical_types_absent'=>!$legacyWrite && !$oldTls,
        'record_intact'=>$record['state'] === 'сохранён'];
    $rows = [['Версия расширения',phpversion('mongodb')],['Драйверный Exception — интерфейс',$driverInterface],
        ['Код неизвестной команды',$event->getCode()],['Родитель DriverRuntime',$runtimeParent],
        ['Родитель DriverInvalid',$invalidParent],['Есть исторический WriteException',$legacyWrite],['Есть исторический SSLConnectionException',$oldTls]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-class-tree', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-class-tree: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-class-tree: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

Упражнение. Дополните таблицу родителем BulkWriteException в вашей версии. Для отсутствующего исторического имени сначала вызовите class_exists и только потом запрашивайте его родителя.

Выбрать узкий catch для конфликта и составить ответ сервиса

Клиенту API важно, почему резерв не создан. Если занят номер операции, интерфейс может предложить сверить уже оформленный резерв. Если сервер не знает команду самой программы, сообщение для покупателя должно быть другим.

Путь выполнения
  1. Прочитать существующий резерв op-19
  2. Испытать конфликт _id и обработать узкий тип до широкого
  3. Неизвестную команду разобрать в следующем catch
  4. Вернуть короткую модель ответа и перечитать исходный резерв

Короткий фрагмент

catch (MongoDB\Driver\Exception\BulkWriteException $error) {
    // Проверить коды отдельных операций.
} catch (MongoDB\Driver\Exception\RuntimeException $error) {
    // Разобрать остальные причины выполнения.
}

Результат

Что проверилиРезультат
Обычное чтение200 — Резерв: 4
Повторный номер409 — Номер операции уже занят
Неподдержанная серверная команда500 — Не удалось обработать запрос
В резерве после отказов4

Почему управление пошло так

PHP выбирает первый подходящий catch. BulkWriteException является потомком DriverRuntime: широкий обработчик, поставленный первым, забрал бы и конфликт. Успех, конфликт номера и ошибка реализации здесь получают выбранные приложением статусы 200, 409 и 500. После обоих отказов подтверждено сохранённое reserved=4.

Границы и типичные ошибки

Это модель контрактов сервиса в CLI. Назначение статуса зависит от операции и происхождения входа. Для конфликта уникальности проверяется конкретный WriteError, неожиданная ошибка пробрасывается наверх. Широкая ветвь не должна безусловно повторять запись.

Для этого же сюжета в ZIP есть локальный HTTP-вариант. Откройте web-README.md и запустите его на учебном стенде. Кнопками страницы можно вызвать конфликт номера и отказ команды. Таблица выше относится к CLI-программе. В отдельном HTTP-прогоне получены четыре ответа: GET чтения — 200, POST с повторным номером — 409, POST с неизвестной командой — 500, заключительное GET-чтение — 200. После каждого действия в ответе показан заново прочитанный резерв 4. Порядок очистки стенда описан в web-README.md.

Полная самостоятельная программа и архив
Выбрать узкий catch для конфликта и составить ответ сервисаСамостоятельный пример
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager, Command};
use MongoDB\BSON\{Document,ObjectId};
use MongoDB\Driver\Exception\{
    Exception as DriverFailure, RuntimeException as DriverRuntime, InvalidArgumentException as DriverInvalid,
    LogicException as DriverLogic, UnexpectedValueException as DriverUnexpected, CommandException
};

require __DIR__.'/er134-helper.php';
[$rows,$checks,$runtime,$counts] = er134Lab(static function(Manager $manager,string $db,string $ns): array {
    $quantity = 4;
    er134Put($manager,$ns,[['_id'=>'op-19','reserved'=>$quantity]]);
    $respond = static function(string $case) use($manager,$db,$ns): array {
        try {
            if ($case === 'duplicate') {
                er134Put($manager,$ns,[['_id'=>'op-19','reserved'=>99]]);
            } elseif ($case === 'server-contract') {
                er134Command($manager,$db,['phpmanUnknownResponse134'=>1]);
            }
            $doc = er134Cursor($manager,$ns,['_id'=>'op-19'],['_id'=>0,'reserved'=>1])->toArray()[0];
            return ['status'=>200,'outcome'=>'Резерв: '.$doc['reserved']];
        } catch (MongoDB\Driver\Exception\BulkWriteException $error) {
            $errors = $error->getWriteResult()->getWriteErrors();
            if (count($errors) !== 1 || $errors[0]->getCode() !== 11000) {throw $error;}
            return ['status'=>409,'outcome'=>'Номер операции уже занят'];
        } catch (DriverRuntime $error) {
            if (!$error instanceof CommandException || $error->getCode() !== 59) {throw $error;}
            return ['status'=>500,'outcome'=>'Не удалось обработать запрос'];
        }
    };
    $ok = $respond('read');
    $duplicate = $respond('duplicate');
    $badCommand = $respond('server-contract');
    $actual = er134Cursor($manager,$ns,[],['_id'=>1,'reserved'=>1])->toArray();
    $checks = ['success_code'=>$ok['status'] === 200,'conflict_handled_first'=>$duplicate['status'] === 409,
        'bug_maps_500'=>$badCommand['status'] === 500,'one_original'=>count($actual) === 1,
        'quantity_unchanged'=>$actual[0]['reserved'] === 4,'read_agrees'=>$ok['outcome'] === 'Резерв: 4',
        'public_contract'=>array_keys($badCommand) === ['status','outcome']];
    $rows = [['Обычное чтение',$ok['status'].' — '.$ok['outcome']],['Повторный номер',$duplicate['status'].' — '.$duplicate['outcome']],
        ['Неподдержанная серверная команда',$badCommand['status'].' — '.$badCommand['outcome']],['В резерве после отказов',$actual[0]['reserved']]];
    return [$rows,$checks];
});
er134Report('mongodb134-contract-api-response', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb134-contract-api-response: OK', PHP_EOL;
Ожидаемый результат
mongodb134-contract-api-response: OK

Самостоятельный CLI-запуск на реальной MongoDB. Проверяется результат сценария и очистка собственной коллекции.

Открыть таблицу запуска · Получить JSON результата

Упражнение. Добавьте отдельный маршрут «номер правильно сформирован, но резерва нет». Решите, какому ресурсу соответствует 404, и проверьте, что при ошибке команды результат не превращается в 404.

Перейти от типа ошибки к восстановлению

При спорном результате записи разберите случай применённого изменения при отказе подтверждения. Для конфликтов пакета пригодятся частичный результат после первой ошибки и состояние сессии при дубликате. Ошибки расшифрования иллюстрирует чтение с неподходящим мастер-ключом.

Дальше разберите тип сбоя при соединении, аутентификации, исчерпании срока исполнения и записи. Для основных операций этой главы также доступны карточки ObjectId::__construct, Document::fromBSON и Cursor::rewind.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

MongoDB\Driver\Exception\ExceptionПерехватить исключение, принадлежащее семейству MongoDB PHP.

ext-mongodb 1.0.0 и новее · mongodb

interface MongoDB\Driver\Exception\Exception

Когда возникает и что получает обработчик

Это интерфейс-маркер. В catch указывают его полное имя либо псевдоним из use. Этот интерфейс реализуют исключения расширения, его также использует библиотека MongoDB для PHP.

PHP передаёт переменной catch конкретный выброшенный объект. У интерфейса нет собственного метода для расшифровки произвольной ошибки. Выбор действия зависит от вида исключения и этапа обработки.

Границы

Маркер не охватывает TypeError от контракта пользовательской функции или все посторонние Throwable. Перехватывайте только там, где обработчик может вернуть корректный результат или передать отказ выше.

Занести ошибку источника в отчёт импорта

catch (MongoDB\Driver\Exception\Exception $error) {
    if ($error instanceof MongoDB\Driver\Exception\UnexpectedValueException) {
        $reason = 'Испорчен BSON';
    } else {
        throw $error;
    }
}

Маркер позволяет принять несколько ветвей, а проверка типа выбирает конкретное действие. Непредусмотренная причина выходит из обработчика.

Полная программа и результат

Отделить отказ PHP от ошибок драйвера

catch (TypeError $error) {
    $belongsToDriver = $error instanceof MongoDB\Driver\Exception\Exception;
}

В опыте PHP отверг массив до входа в функцию: принадлежность этому интерфейсу равна false.

Полная программа и результат

Сопоставить SPL-класс, интерфейс и соседние ветви.

MongoDB\Driver\Exception\InvalidArgumentExceptionРаспознать недопустимое значение параметра метода драйвера.

ext-mongodb 1.0.0 и новее · mongodb

class MongoDB\Driver\Exception\InvalidArgumentException extends \InvalidArgumentException implements MongoDB\Driver\Exception\Exception

Когда возникает и что получает обработчик

Обработчик получает исключение от конкретного вызова, которому передали неподходящее значение или недопустимую настройку. В примерах аргументом конструктора BSON ObjectId служит неправильная строка.

Прямой родитель — глобальный SPL-класс InvalidArgumentException. У объекта доступны унаследованные getCode(), getMessage() и другие средства Exception. У его вызова нет нормального возвращаемого значения.

Границы

Одинаковая короткая запись имени в разных пространствах имён обозначает разные классы. Внешняя опечатка может быть ошибкой ввода, а ошибку конфигурации самого приложения должен исправлять его разработчик.

Остановить поиск по неразбираемому идентификатору

try {
    $id = new MongoDB\BSON\ObjectId($input);
} catch (MongoDB\Driver\Exception\InvalidArgumentException $error) {
    return 'Неверный формат';
}

Исключение возникает при построении ObjectId. Нулевой результат поиска — другой исход, возможный лишь после создания корректного id.

Полная программа и результат

Отправить строку импорта на исправление

if ($error instanceof MongoDB\Driver\Exception\InvalidArgumentException) {
    $reasons[] = 'неверный ID';
}

Применяется внутри узкой границы разбора. В полном примере для разных типов сообщения формируются отдельно.

Полная программа и результат

Сопоставить SPL-класс, интерфейс и соседние ветви.

MongoDB\Driver\Exception\LogicExceptionОбнаружить неправильный порядок использования объекта драйвера.

ext-mongodb 1.0.0 и новее · mongodb

class MongoDB\Driver\Exception\LogicException extends \LogicException implements MongoDB\Driver\Exception\Exception

Когда возникает и что получает обработчик

Это исключение возникает при обращении к объекту в неподходящем состоянии. Пример без параметров: Cursor::rewind(), вызванный после того, как курсор прошёл первую позицию.

Тип сочетает SPL LogicException и интерфейс MongoDB. Исправление находится в последовательности вызовов приложения. Курсор сам не воспроизводит уже пройденные строки.

Границы

Широкий catch по глобальному LogicException охватит и другие библиотеки. При повторном выполнении запроса в другой момент данные могут измениться.

Определить, почему печать не вернулась на первую строку

$cursor->rewind();
$cursor->next();
try {
    $cursor->rewind();
} catch (MongoDB\Driver\Exception\LogicException $error) {
    $canReplay = false;
}

После перехода к строке 2 обработчик отмечает невозможность повторного rewind.

Полная программа и результат

Собрать две части накладной за один проход

foreach ($cursor as $line) {
    $ids[] = $line['_id'];
    $quantity += $line['qty'];
}

Начните с нового, ещё не пройденного курсора. На учебных данных получите три строки и шесть единиц товара. Такой порядок устраняет причину исключения.

Полная программа и результат

Сопоставить SPL-класс, интерфейс и соседние ветви.

MongoDB\Driver\Exception\UnexpectedValueExceptionПоместить неподходящий BSON-пакет в список отклонённых.

ext-mongodb 1.0.0 и новее · mongodb

class MongoDB\Driver\Exception\UnexpectedValueException extends \UnexpectedValueException implements MongoDB\Driver\Exception\Exception

Когда возникает и что получает обработчик

Например, Document::fromBSON принимает строку байтов для одного BSON-документа. При нарушении формата расширение может выбросить это исключение при десериализации. Оно также относится к неожиданным значениям при сериализации.

Прямой родитель — SPL UnexpectedValueException. Тип входит в интерфейс ошибок драйвера. SPL UnexpectedValueException сам наследует глобальный RuntimeException, но здесь нет наследования от драйверского RuntimeException.

Границы

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

Отделить повреждённые байты от пригодного документа

try {
    $document = MongoDB\BSON\Document::fromBSON($bytes);
} catch (MongoDB\Driver\Exception\UnexpectedValueException $error) {
    $rejected++;
}

В полном примере в ветке отказа исполняется continue, поэтому отклонённый пакет не попадает в запись.

Полная программа и результат

Выбрать инструкцию отправителю

if ($error instanceof MongoDB\Driver\Exception\UnexpectedValueException) {
    $reasons[] = 'испорчен BSON';
}

В отчёте появляется другая причина, чем при неверном идентификаторе. Корректный пакет получает собственную квитанцию.

Полная программа и результат

Сопоставить SPL-класс, интерфейс и соседние ветви.

MongoDB\Driver\Exception\RuntimeExceptionОбработать связанные ошибки выполнения и сохранить более точные ветви.

ext-mongodb 1.0.0 и новее · mongodb

class MongoDB\Driver\Exception\RuntimeException extends \RuntimeException implements MongoDB\Driver\Exception\Exception

Когда возникает и что получает обработчик

Широкая ветвь включает ошибки соединения, сервера и шифрования. Проверяйте конкретное действие, вид исключения и специализированные сведения, прежде чем принимать решение.

Выброшенный объект сохраняет конкретный подтип. Унаследованные getMessage() и getCode() дают диагностические сведения. Дополнительные метки ошибок (protected errorLabels) проверяют методом hasErrorLabel() с версии 1.6, а не прямым чтением внутреннего свойства.

Границы

Драйверские InvalidArgumentException, LogicException и UnexpectedValueException имеют собственные SPL-ветви. Широкий catch по DriverRuntime не ловит их. Метка с произвольным именем не служит основанием повторять изменение.

Узнать тип ответа на неизвестную команду

catch (MongoDB\Driver\Exception\RuntimeException $error) {
    $isCommand = $error instanceof MongoDB\Driver\Exception\CommandException;
    $code = $error->getCode();
}

Для специально неизвестной команды реальный сервер вернул 59. Данные контрольного документа сохранились.

Полная программа и результат

Оставить конфликт ключа отдельной ветвью

catch (MongoDB\Driver\Exception\BulkWriteException $error) {
    // Сверить отдельный WriteError и запись.
} catch (MongoDB\Driver\Exception\RuntimeException $error) {
    // Обработать предусмотренные причины этой операции.
}

В примере только подтверждённый конфликт уникальности получает прикладной статус 409. Обработчик ошибки команд расположен после него.

Полная программа и результат

Проверить метку через hasErrorLabel.

Сопоставить SPL-класс, интерфейс и соседние ветви.

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

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

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

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

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