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

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

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

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

Глава 161 · После первого каталога и введения в события команд

MongoDB: отказы команд, безопасная диагностика и исправленный результат

Неверный фильтр, этап и hint; реальная связь Started/Failed, ошибки до отправки, writeErrors при Succeeded, разрешённый отчёт и миграция getServer.

От неоткрывающейся страницы к конкретному отказу

Покупатель выбрал CUP и LAMP, но страница каталога не открылась. Сообщения «что-то пошло не так» недостаточно разработчику; сырой ответ базы не подходит посетителю. В этой главе получим конкретный отказ, свяжем его с нужным запросом, исправим причину и покажем реальные товары. Затем разберём похожие ситуации, в которых CommandFailed вообще не появляется.

Предварительные шаги: товар, документ, коллекция и ограниченный запрос, начало и окончание команды, класс, объект и вызов метода. Здесь команда — документ с действием для сервера, например find для чтения или aggregate для отчёта. Событие — информация о конкретном этапе отправки/ответа. Подписчик — наш объект, метод которого драйвер вызывает при таком событии.

Минимальная прикладная задача — найти два артикула. PHP-список внутри $in содержит отдельные значения. Кавычки делают CUP строкой; запятая разделяет элементы массива; знак => связывает имя поля и его значение. Строка CUP,LAMP является одним значением и автоматически не делится.

$filter = ['sku' => ['$in' => ['CUP', 'LAMP']]];
От выбранных товаров к понятному результату
  1. Форма передала список CUP/LAMP
  2. Программа проверила форму и значения
  3. find отправлен с projection и limit
  4. Драйвер сообщает Started → Failed или Succeeded
  5. Catch/результат операции определяет прикладной исход
  6. На экране товары либо понятное сообщение; диагностика отдельно

Если драйвер вызвал commandFailed, параметр $event уже содержит объект события. Getter читает его значение: он не выполняет запрос заново. Переменная $name ниже получает имя реально отказавшей команды. Это короткая часть обработчика, а не отдельный исполняемый файл.

$name = $event->getCommandName(); // Например, find
$code = $event->getError()->getCode(); // Код наблюдаемого отказа

Метод commandFailed объявляется внутри класса, реализующего CommandSubscriber. Интерфейс задаёт три обязательных метода: commandStarted, commandSucceeded и commandFailed. У каждого есть параметр события, у результата тип void: метод сохраняет наблюдение и ничего не возвращает. Полный небольшой класс, регистрация в Manager и снятие в finally включены в helper каждого архива; вручную события не создаются.

Команда, соединение и результат записи — разные уровни

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

СитуацияЧто наблюдаемЧто должна делать программа
Неверный $in, неизвестный этап или командаStarted → Failed; серверный код и разрешённый replyПоймать исключение операции; исправить причину; не выдавать отказ за пустую выборку
Подбор сервера не удался до отправкиИсключение соединения; прикладных Started/Failed может не бытьОбработать catch независимо от счётчика; для heartbeat смотреть SDAM
Дубликат в insert с ответом ok=1Started → Succeeded и writeErrors11000; BulkWriteException у вызывающего кодаПроверить WriteResult и фактические изменения
Исправленный ограниченный findНовый Started → Succeeded и реальные строкиПоказать полученные sku/price; сохранить прежнюю ошибку как историю

Request ID связывает конкретные Started и Failed в контексте клиента. Operation ID группирует команды драйверной операции и не заменяет request ID: несколько команд могут разделять его, а значение "0" допустимо. Server connection ID относится к соединению и может отсутствовать; service ID относится к режиму load balancer и равен null на нашем Standalone. Ни одно из этих полей само по себе не является номером заказа.

GetError события возвращает Exception, но точный подкласс не обязан совпасть с исключением исходного вызова. На нашем стенде событие серверного отказа содержит ServerException, а executeReadCommand выбрасывает CommandException. Мы сохраняем класс и код каждого наблюдения, связываем события и не проверяем тождественность объектов. Полный errmsg и стек в публичную таблицу не копируются.

Времена сохраняются как фактические микросекунды. Они включают отправку команды и получение ответа; это не измерение всего HTTP-запроса и не обещание производительности. Callback не обращается к базе, не отправляет HTTP-запросы и не хранит объект исключения: он только добавляет разрешённые скалярные поля в ограниченный буфер. Ограничение в helper — 32 записи каждого вида, с отдельным счётчиком отброшенных.

Запустить текущие примеры и историческую миграцию

Для восьми текущих программ нужны PHP 8.4+ CLI x64, расширение mongodb 2.5+ и отдельный локальный MongoDB Community 8.0+. Проверяемая связка — PHP 8.5.11, mongodb 2.5.5, MongoDB 8.0.34. Composer не нужен. На сайте показаны сохранённые результаты; браузер посетителя не отправляет учебные ошибки в базу.

php --ri mongodb
$env:PHPMAN_MONGODB_URI='mongodb://127.0.0.1:27075/?directConnection=true&retryWrites=false'
$env:PHPMAN_MONGODB_DATABASE='php_manual_lab'
php example.php

Каждый ZIP распаковывается в свою папку вместе с mf124-helper.php и README.txt. README содержит установку подходящей DLL, проверку CLI php.ini, запуск mongod с отдельным dbpath и bind_ip=127.0.0.1. После запуска откройте result.html: там таблица и реальные обезличенные наблюдения. result.json содержит строки; metrics.json — фактические версии, проверки и хеши файлов.

Каждая программа создаёт ровно свою случайную коллекцию mongodb124_failures_… после проверки коллизии. Только успешный create даёт право на её удаление в finally; затем listCollections проверяет отсутствие. Никакие рабочие коллекции, серверные настройки или процессы не меняются. Для отказа соединения резервируется собственный порт без LISTEN; MongoDB не останавливается.

Исправить список артикулов: строка не является аргументом $in

Пользователь выбрал два товара, а вместо карточек сервис получил ошибку. Нужно увидеть, какая команда отказала, и исправить форму входных данных, сохранив полезный результат: CUP за 500 и LAMP за 1200.

Исходные данные и понятия

В учебной коллекции есть CUP, LAMP и архивный MAT. Неверный фильтр передаёт в $in строку CUP,LAMP. Правильный передаёт PHP-список из двух строк: при кодировании он становится массивом BSON. Название поля sku и оператор $in задаёт программа.

Путь данных, события и результата
  1. Строка CUP,LAMP попала в $in
  2. Сервер получил find и вернул код 2
  3. Callback сохранил requestId и разрешённый ответ
  4. Catch обработал CommandException отдельно
  5. Исправленный список отправлен новым find
  6. На странице показаны CUP 500 и LAMP 1200

Короткая запись

$filter = ['sku' => ['$in' => ['CUP', 'LAMP']]];
// $in получает список значений; строка 'CUP,LAMP' сама в список не превращается.

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

Что получится

РезультатКоманда / артикулКод / цена
Ошибка фильтраfind2
Полученный товарCUP500
Полученный товарLAMP1200

Как понимать результат

Квадратные скобки создают массив PHP; две строки через запятую — два элемента списка. MongoDB не разбирает запятые внутри одной строки как список. В журнале одно Started связано с одним Failed по requestId и контексту клиента. Новое успешное чтение имеет своё событие; ошибка не переписывается задним числом. Код 2 здесь наблюдается для конкретного BadValue, а не означает любую возможную ошибку фильтра.

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

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

Полная программа, helper, инструкция и архив
Исправить список артикулов: строка не является аргументом $inСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$failure=mf124Failure($manager,$database,['find'=>$collection,'filter'=>['sku'=>['$in'=>'CUP,LAMP']],'limit'=>2],$journal,2);
$products=mf124Screen($manager,$database,$collection,['sku'=>['$in'=>['CUP','LAMP']]]);
$rows=[['Ошибка фильтра',$failure['event']['command'],$failure['event']['error_code']]];
foreach($products as $product){$rows[]=['Полученный товар',$product['sku'],$product['price']];}
$checks=['failure_request_paired'=>$failure['paired'],'corrected_products'=>$products===[['sku'=>'CUP','price'=>500],['sku'=>'LAMP','price'=>1200]],
    'failed_then_success'=>count($journal->failed)===1 && count($journal->succeeded)===1];
$details=['Исключение вызывающего кода'=>$failure['caught'],'Смысл'=>'CommandFailed связан со своим Started; исправленный find — новая команда'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['Ошибка фильтра', 'find', 2], ['Полученный товар', 'CUP', 500], ['Полученный товар', 'LAMP', 1200]];
mf124Report('mongodb124-failures-invalid-filter',['Результат', 'Команда / артикул', 'Код / цена'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-invalid-filter: OK',"\n";
Ожидаемый результат
mongodb124-failures-invalid-filter: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. Нужно получить только LAMP. Передайте список ['LAMP']; ожидается одна карточка с ценой 1200. Не подменяйте ошибку сервера пустым массивом: иначе пользователь не отличит поломанный поиск от отсутствия товаров.

Вопрос из практики: Doctrine MongoDb — ReferenceMany loading says Cannot canonicalize query. При загрузке связанных данных разработчик получил $in needs an array. Старая ODM-среда не переносится в урок: используем отдельный текущий пример с явным неправильным значением и исправленным списком. Обсуждение использовано для постановки задачи, не как источник современного API; решения и данные этой главы составлены отдельно.

Найти опечатку этапа отчёта и получить итог активного каталога

Отчёт по активному каталогу перестал открываться после изменения этапов агрегации. Покажем отказ неизвестного этапа и исправленный результат: два товара с суммой цен 1700.

Исходные данные и понятия

Pipeline — упорядоченный список этапов обработки документов. Каждый элемент здесь является документом с именем этапа. Неверное имя $aenMissingStage124 нужно только для воспроизведения отказа. В исправлении $match оставляет active, $group считает товары и сумму, $project выбирает поля результата.

Путь данных, события и результата
  1. Отправить aggregate с неизвестным этапом
  2. Сохранить команду aggregate и код 40324
  3. Связать Failed с соответствующим Started
  4. Составить известные этапы match → group → project
  5. Прочитать один документ итога
  6. Вывести products=2 и total=1700

Короткая запись

$pipeline = [
    ['$match' => ['status' => 'active']],
    ['$group' => ['_id' => null, 'products' => ['$sum' => 1]]],
]; // Имена этапов задаёт программа; неизвестный $aenMissingStage124 сервер отклонит.

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

Что получится

НаблюдениеКоманда / показательКод / результат
Ошибка этапаaggregate40324
Исправленный отчётактивных товаров2
Исправленный отчётсумма цен1700

Как понимать результат

CommandFailed сообщает уровень команды: сервер не выполнил такую pipeline. После исправления возвращается один документ отчёта, а не весь каталог для подсчёта в PHP. $sum: 1 увеличивает счётчик на каждый подходящий документ; $sum: $price складывает цены. Отсутствующий _id в выводе задаётся проекцией, это не изменение исходных товаров. Первая партия задана batchSize=2: единственный итог помещается в неё, поэтому здесь проверяются ровно два aggregate — отказавший и исправленный. При batchSize=1 первая партия заполнялась целиком и для завершения чтения драйвер выполнял дополнительный getMore. Размер партии не является общим лимитом отчёта.

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

40324 — код неизвестного этапа на используемом MongoDB 8.0. Другие ошибки агрегации имеют другие коды. Названия этапов и выражения нельзя брать из произвольного пользовательского JSON без собственного контракта. Успешность на трёх товарах не является измерением стоимости большого отчёта.

Полная программа, helper, инструкция и архив
Найти опечатку этапа отчёта и получить итог активного каталогаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$failure=mf124Failure($manager,$database,['aggregate'=>$collection,'pipeline'=>[['$aenMissingStage124'=>new stdClass]],'cursor'=>new stdClass],$journal,40324);
$summary=mf124Command($manager,$database,['aggregate'=>$collection,'pipeline'=>[
    ['$match'=>['status'=>'active']],['$group'=>['_id'=>null,'products'=>['$sum'=>1],'total'=>['$sum'=>'$price']]],
    ['$project'=>['_id'=>0,'products'=>1,'total'=>1]]],'cursor'=>['batchSize'=>2],'maxTimeMS'=>2000]);
$rows=[['Ошибка этапа',$failure['event']['command'],$failure['event']['error_code']],
    ['Исправленный отчёт','активных товаров',$summary[0]['products']],['Исправленный отчёт','сумма цен',$summary[0]['total']]];
$checks=['actual_pipeline_failure'=>$failure['paired'],'actual_report'=>$summary===[['products'=>2,'total'=>1700]],
    'distinct_requests'=>array_column($journal->started,'command')===['aggregate','aggregate']
        && array_column($journal->failed,'command')===['aggregate'] && array_column($journal->succeeded,'command')===['aggregate']];
$details=['Отказ'=>$failure['caught'],'Результат отчёта'=>$summary];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['Ошибка этапа', 'aggregate', 40324], ['Исправленный отчёт', 'активных товаров', 2], ['Исправленный отчёт', 'сумма цен', 1700]];
mf124Report('mongodb124-failures-invalid-pipeline',['Наблюдение', 'Команда / показатель', 'Код / результат'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-invalid-pipeline: OK',"\n";
Ожидаемый результат
mongodb124-failures-invalid-pipeline: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. Нужен только счётчик, без суммы. Уберите total из $group и итоговой проекции; результат должен остаться products=2. Это сокращает контракт ответа, а не требует сначала загрузить все товары.

Отличить ошибочный hint от пустого результата поиска

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

Исходные данные и понятия

В новой собственной коллекции нет индекса aen_missing_price_index124. Запрос принудительно указывает его в hint и получает серверный отказ. Затем выполняется такой же полезный фильтр active без этого ошибочного указания, с сортировкой price/sku и limit=2.

Путь данных, события и результата
  1. Запрос требует отсутствующий индекс
  2. MongoDB отклоняет find
  3. Сохранить код, имя команды и пару requestId
  4. Убрать ошибочную привязку в учебном запросе
  5. Оставить filter, projection, sort и limit
  6. Получить CUP и LAMP

Короткая запись

$command = ['find' => $collection, 'filter' => ['status' => 'active'],
    'projection' => ['_id' => 0, 'sku' => 1, 'price' => 1], 'limit' => 2];
// hint отсутствующего индекса не является просьбой «использовать индекс, если он есть».

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

Что получится

ЗапросКод / количествоЧто увидели
find с отсутствующим индексом2отказ команды
Запрос без этого hint2CUP, LAMP

Как понимать результат

Hint задаёт выбор индекса; это не необязательная подсказка «если найдётся». В примере отказ имеет код 2. Исправленный запрос возвращает две реальные строки. Ни код ошибки, ни число CommandFailed не подменяют сам список товаров: он показан отдельно. Для рабочего релиза нужно выяснить причину отсутствия индекса и согласовать миграцию схемы/запроса.

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

Автоматически снимать hint при любой ошибке нельзя: он может быть частью осознанного эксплуатационного контракта. Здесь снятие явно написано как учебное исправление, а не скрытый retry. Никакого обещания о выборе индекса, отсутствии сортировки или ускорении этот запуск не делает.

Полная программа, helper, инструкция и архив
Отличить ошибочный hint от пустого результата поискаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$failure=mf124Failure($manager,$database,['find'=>$collection,'filter'=>['status'=>'active'],
    'hint'=>'aen_missing_price_index124','limit'=>2,'maxTimeMS'=>2000],$journal,2);
$products=mf124Screen($manager,$database,$collection,['status'=>'active']);
$rows=[['find с отсутствующим индексом',$failure['event']['error_code'],'отказ команды'],
    ['Запрос без этого hint',count($products),implode(', ',array_column($products,'sku'))]];
$checks=['real_bad_hint'=>$failure['paired'],'actual_bounded_screen'=>$products===[['sku'=>'CUP','price'=>500],['sku'=>'LAMP','price'=>1200]],
    'failure_not_empty_result'=>count($journal->failed)===1 && count($products)===2];
$details=['Ошибка'=>$failure['caught'],'После исправления'=>$products,
    'Граница'=>'Снятие ошибочного hint исправляет этот запрос; эффективность плана здесь не измеряется'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['find с отсутствующим индексом', 2, 'отказ команды'], ['Запрос без этого hint', 2, 'CUP, LAMP']];
mf124Report('mongodb124-failures-missing-hint',['Запрос', 'Код / количество', 'Что увидели'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-missing-hint: OK',"\n";
Ожидаемый результат
mongodb124-failures-missing-hint: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. На стенде появился нужный индекс с другим именем. Сначала сверяйте listIndexes и соответствие запросу; затем меняйте конфигурацию. Не преобразуйте исключение в «найдено 0 товаров». Подробнее устройство индексов разобрано в связанной главе.

Вопрос из практики: MongoDB indexes unexpectedly getting removed; hint fails. Запрос с hint отказал при отсутствии ожидаемого индекса. Не принимаем предположение об автоматическом удалении как установленный факт; показываем диагностируемый отказ и явное исправление собственной тестовой команды. Обсуждение использовано для постановки задачи, не как источник современного API; решения и данные этой главы составлены отдельно.

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

Диагностическая утилита посылает произвольное имя команды и сообщает «база недоступна». Проверим эту ошибочную интерпретацию: сервер способен ответить, но не знает запрошенное действие.

Исходные данные и понятия

Документ команды начинается с aenManualUnknown124: 1. Такой команды нет. После его отказа программа отправляет штатный ping и получает ok=1. Данные коллекции не меняются.

Путь данных, события и результата
  1. Отправить неизвестное имя действия
  2. Получить серверный код 59
  3. Прочитать getCommandName и разрешённый reply
  4. Отдельно обработать исключение операции
  5. Отправить ping как новую команду
  6. Показать отказ действия рядом с успешным ответом сервера

Короткая запись

$reply = $manager->executeReadCommand($database,
    new MongoDB\Driver\Command(['ping' => 1]));
// Имя команды — ключ первого поля документа; произвольное имя сервер не исполнит.

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

Что получится

ПроверкаКомандаКод / ok
Неподдерживаемая командаaenManualUnknown12459
Исправленная проверкаping1

Как понимать результат

Имя команды находится в первом поле её BSON-документа. 59 обозначает CommandNotFound на данном сервере. Наличие такого ответа уже отличается от невозможности найти сервер: ответ пришёл по сети. Успешный ping подтверждает только эту проверку, а не права пользователя на все коллекции или корректность любого последующего запроса.

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

Не используйте произвольный список неизвестных команд для разведки производственного сервера. Здесь одна заранее известная учебная ошибка. В журнале сохраняется codeName, когда сервер его прислал; этот необязательный текст не используется вместо числового кода и реального исключения.

Полная программа, helper, инструкция и архив
Показать различие доступного сервера и неподдерживаемой командыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$failure=mf124Failure($manager,$database,['aenManualUnknown124'=>1],$journal,59);
$reply=mf124Command($manager,$database,['ping'=>1]);
$rows=[['Неподдерживаемая команда',$failure['event']['command'],$failure['event']['error_code']],
    ['Исправленная проверка','ping',(int)$reply[0]['ok']]];
$checks=['real_unknown_command'=>$failure['paired'],'server_answered_ping'=>(float)$reply[0]['ok']===1.0,
    'two_distinct_outcomes'=>count($journal->failed)===1 && count($journal->succeeded)===1];
$details=['Исключение операции'=>$failure['caught'],'Причина'=>'Сервер ответил кодом 59; это не доказательство сетевого отказа'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['Неподдерживаемая команда', 'aenManualUnknown124', 59], ['Исправленная проверка', 'ping', 1]];
mf124Report('mongodb124-failures-unknown-command',['Проверка', 'Команда', 'Код / ok'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-unknown-command: OK',"\n";
Ожидаемый результат
mongodb124-failures-unknown-command: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. Какая надпись точнее: «сервер не отвечает» или «сервер отклонил неизвестную команду»? В этом опыте верна вторая. Первая требует самостоятельных сведений об ошибке соединения.

Не считать отсутствие CommandFailed успешным подключением

Счётчик CommandFailed равен нулю, но клиент не смог получить даже ping. Покажем отсутствие события как ограничение наблюдения, затем отдельно прочитаем товары с здорового стенда.

Исходные данные и понятия

Программа занимает свой случайный loopback-порт без слушателя. Для отдельного Manager подбор ограничен serverSelectionTimeoutMS=500, соединение connectTimeoutMS=300. Это не сервер MongoDB. Исходный настоящий mongod не останавливается.

Путь данных, события и результата
  1. Зарезервировать свой endpoint без LISTEN
  2. Попытаться выбрать сервер для ping
  3. Поймать реальное исключение соединения
  4. Зафиксировать Started=0, Failed=0, Succeeded=0
  5. Освободить сокет и диагностический клиент
  6. Отдельным здоровым клиентом получить CUP и LAMP

Короткая запись

try {
    $manager->executeReadCommand($database, new MongoDB\Driver\Command(['ping' => 1]));
} catch (MongoDB\Driver\Exception\ConnectionException $error) {
    $status = 'Не удалось соединиться'; // Отдельно от счётчика CommandFailed.
}

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

Что получится

ДействиеФактический итогCommandStartedCommandFailed
Подбор недоступного сервераошибка соединения00
Чтение с известного стендаCUP, LAMP10

Как понимать результат

CommandSubscriber наблюдает прикладные команды. Если до выбора подходящего сервера дело не дошло, отправленной команды и её пары Started/Failed нет. Служебный heartbeat относится к другому интерфейсу SDAMSubscriber. Catch операции поэтому обязателен независимо от журнала. Вторая строка — отдельное чтение с известного стенда, а не автоматическое восстановление первого endpoint.

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

500 мс ограничивает соответствующую стадию драйвера, а не обещает точное внешнее время процесса. Нельзя делать вывод «все сетевые ошибки не дают CommandFailed»: отказ после начала команды — другой случай. Нельзя автоматически переносить запись на другую базу после неопределённого исхода.

Полная программа, helper, инструкция и архив
Не считать отсутствие CommandFailed успешным подключениемСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$manager->removeSubscriber($journal);$bad=null;$socket=null;$connectionError=null;
[$socket,$failureUri]=mf124BoundEndpoint();
try{
    $bad=mf124Manager($failureUri,true);$bad->addSubscriber($journal);
    try{mf124Command($bad,$database,['ping'=>1]);}
    catch(MongoDB\Driver\Exception\ConnectionException $error){$connectionError=get_class($error);unset($error);}
    $before=['started'=>count($journal->started),'failed'=>count($journal->failed),'succeeded'=>count($journal->succeeded)];
}finally{
    if($bad!==null){$bad->removeSubscriber($journal);}unset($bad);
    if(is_resource($socket)){fclose($socket);}
}
$manager->addSubscriber($journal);
$products=mf124Screen($manager,$database,$collection,['status'=>'active']);
$rows=[['Подбор недоступного сервера',$connectionError!==null?'ошибка соединения':'нет ошибки',$before['started'],$before['failed']],
    ['Чтение с известного стенда',implode(', ',array_column($products,'sku')),count($journal->started),count($journal->failed)]];
$checks=['actual_selection_error'=>$connectionError!==null,'no_command_sent'=>$before===['started'=>0,'failed'=>0,'succeeded'=>0],
    'healthy_result'=>$products===[['sku'=>'CUP','price'=>500],['sku'=>'LAMP','price'=>1200]],'healthy_success_recorded'=>count($journal->succeeded)===1];
$details=['Класс отказа до команды'=>$connectionError,'Отказавший endpoint'=>$failureUri,
    'Граница'=>'Не было выбранного MongoDB-сервера и отправленной прикладной команды. Heartbeat относится к SDAM, а не этому CommandSubscriber'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['Подбор недоступного сервера', 'ошибка соединения', 0, 0], ['Чтение с известного стенда', 'CUP, LAMP', 1, 0]];
mf124Report('mongodb124-failures-connection-selection',['Действие', 'Фактический итог', 'CommandStarted', 'CommandFailed'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-connection-selection: OK',"\n";
Ожидаемый результат
mongodb124-failures-connection-selection: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. В панели ноль событий и одновременно ConnectionTimeoutException. Покажите отдельный статус «соединение не установлено», а не зелёный статус из отсутствия ошибок команд. Для исследования hello используйте журнал SDAM.

Не принять CommandSucceeded за успешную вставку товара

Импорт показал зелёное событие CommandSucceeded, но новая запись не появилась. Разделим три уровня: MongoDB ответила на insert, одна вставка получила writeErrors, приложение получило BulkWriteException.

Исходные данные и понятия

У товара CUP уже есть _id=1 и цена 500. Пример пытается вставить другую запись с тем же _id. Уникальный индекс _id существует автоматически. После отказа исходный CUP читается отдельным ограниченным запросом.

Путь данных, события и результата
  1. Попытаться вставить существующий _id=1
  2. Сервер возвращает ok=1 и writeErrors11000
  3. Мониторинг сообщает CommandSucceeded insert
  4. ExecuteBulkWrite выбрасывает BulkWriteException
  5. Проверить WriteResult: insertedCount=0
  6. Прочитать прежний CUP с ценой 500

Короткая запись

catch (MongoDB\Driver\Exception\BulkWriteException $error) {
    $result = $error->getWriteResult();
    $errors = $result->getWriteErrors();
} // Даже если мониторинг сообщил CommandSucceeded, результат записи проверяется отдельно.

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

Что получится

УровеньНаблюдениеЗначение
Событие командыCommandSucceeded1
Результат записидубликат _id11000
Вставлено документовinsertedCount0
Исходный товарCUP500

Как понимать результат

На уровне протокола команда ответила успешно; ошибка конкретной записи находится внутри ответа. Поэтому CommandFailed здесь равен нулю. Программа проверяет writeErrors, отсутствие отдельной writeConcernError и количество реально вставленных документов. Само слово Succeeded не является квитанцией «товар сохранён».

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

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

Полная программа, helper, инструкция и архив
Не принять CommandSucceeded за успешную вставку товараСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$bulk=new MongoDB\Driver\BulkWrite;$bulk->insert(['_id'=>1,'sku'=>'DUPLICATE','price'=>999]);$caught=null;$writeResult=null;
try{$manager->executeBulkWrite($database.'.'.$collection,$bulk,['writeConcern'=>new MongoDB\Driver\WriteConcern(1,2000)]);}
catch(MongoDB\Driver\Exception\BulkWriteException $error){
    $writeResult=$error->getWriteResult();$errors=$writeResult->getWriteErrors();
    if(count($errors)!==1 || $errors[0]->getCode()!==11000 || $writeResult->getWriteConcernError()!==null){throw $error;}
    $caught=['class'=>get_class($error),'code'=>$errors[0]->getCode()];unset($error);
}
if($caught===null){throw new RuntimeException('Expected duplicate _id');}
$insert=null;foreach($journal->succeeded as $event){if($event['command']==='insert'){$insert=$event;break;}}
if($insert===null){throw new RuntimeException('Actual CommandSucceeded insert was not observed');}
$original=mf124Screen($manager,$database,$collection,['sku'=>'CUP']);
$rows=[['Событие команды','CommandSucceeded',(int)$insert['reply']['ok']],
    ['Результат записи','дубликат _id',$caught['code']],['Вставлено документов','insertedCount',$writeResult->getInsertedCount()],
    ['Исходный товар',$original[0]['sku'],$original[0]['price']]];
$checks=['write_error_in_success_reply'=>$insert['write_error_codes']===[11000],
    'no_failed_command'=>count($journal->failed)===0,'no_inserted_duplicate'=>$writeResult->getInsertedCount()===0,
    'original_unchanged'=>$original===[['sku'=>'CUP','price'=>500]],
    'insert_request_paired'=>isset($journal->started[$insert['request_id']])];
$details=['Исключение записи'=>$caught,'Полученный исходный товар'=>$original,
    'Разделение'=>'Команда insert получила ответ ok=1; отказ одной записи находится в writeErrors и WriteResult'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['Событие команды', 'CommandSucceeded', 1], ['Результат записи', 'дубликат _id', 11000], ['Вставлено документов', 'insertedCount', 0], ['Исходный товар', 'CUP', 500]];
mf124Report('mongodb124-failures-write-errors-success',['Уровень', 'Наблюдение', 'Значение'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-write-errors-success: OK',"\n";
Ожидаемый результат
mongodb124-failures-write-errors-success: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. Что показывать пользователю импорта? «Запись с таким ID уже существует; добавлено 0». Для операторского журнала рядом оставить CommandSucceeded с writeErrors. Эти сообщения дополняют друг друга, а не противоречат.

Собрать две понятные диагностические записи без данных покупателя

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

Исходные данные и понятия

Две реальные ошибочные команды содержат учебный private-customer@example.invalid. Callback получает событие, но сохраняет лишь разрешённые поля: имя команды, базу, endpoint, requestId/operationId, длительность, тип/код ошибки и ok/code/codeName из ответа. Прикладная метка действия добавляется отдельно.

Путь данных, события и результата
  1. Отправить неверный find и неверный aggregate
  2. Связать каждый Failed со своим Started
  3. Взять только разрешённые поля ответа
  4. Добавить назначение: экран или отчёт
  5. Проверить отсутствие значения private-customer и errmsg
  6. Показать две короткие диагностические строки

Короткая запись

$reply = $event->getReply();
$record = ['command' => $event->getCommandName(), 'code' => $reply->code ?? null,
    'request_id' => $event->getRequestId()];
// Не добавляем весь reply/errmsg: текст ошибки может содержать значения запроса.

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

Что получится

ДействиеКомандаКодStarted найденserviceId отсутствует
экран каталогаfind2truetrue
отчёт остатковaggregate40324truetrue

Как понимать результат

Allowlist — явный перечень разрешённых полей. Он устойчивее, чем попытка удалить одно известное поле из целого сырого ответа: завтра сервер может добавить другие подробности. В таблице serviceId отсутствует, потому что стенд прямой, без load balancer. Реальные connectionId и длительности остаются в подробностях, а не подставляются константами.

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

Даже имя базы, адрес узла и служебные ID являются эксплуатационными данными; в публичном сервисе доступ к ним тоже ограничивают. Эта небольшая политика не заменяет аудит конфиденциальности всей системы. Отсутствующий serviceId нельзя заменять придуманным ObjectId или трактовать как отказ.

Полная программа, helper, инструкция и архив
Собрать две понятные диагностические записи без данных покупателяСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$secret='private-customer@example.invalid';
$one=mf124Failure($manager,$database,['find'=>$collection,'filter'=>['sku'=>['$in'=>$secret]],'limit'=>2],$journal,2);
$two=mf124Failure($manager,$database,['aggregate'=>$collection,'pipeline'=>[['$aenMissingStage124'=>['private_note'=>$secret]]],'cursor'=>new stdClass],$journal,40324);
$rows=[];$sanitized=[];
foreach([['экран каталога',$one],['отчёт остатков',$two]] as [$label,$failure]){
    $event=$failure['event'];$sanitized[]=['action'=>$label]+$event;
    $rows[]=[$label,$event['command'],$event['error_code'],$failure['paired'],$event['service_id']===null];
}
$encoded=json_encode($sanitized,JSON_THROW_ON_ERROR);
$checks=['both_real_failures'=>count($journal->failed)===2,'private_value_excluded'=>!str_contains($encoded,$secret),
    'unapproved_reply_excluded'=>!str_contains($encoded,'errmsg') && !str_contains($encoded,'private_note'),
    'requests_distinct'=>$one['event']['request_id']!==$two['event']['request_id']];
$details=['Диагностические записи с назначением'=>$sanitized,
    'Порядок'=>'action задана приложением; IDs и code берутся из реального события. Имя базы/endpoint тоже требуют контроля доступа к журналу'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['экран каталога', 'find', 2, true, true], ['отчёт остатков', 'aggregate', 40324, true, true]];
mf124Report('mongodb124-failures-incident-report',['Действие', 'Команда', 'Код', 'Started найден', 'serviceId отсутствует'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-incident-report: OK',"\n";
Ожидаемый результат
mongodb124-failures-incident-report: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. Нужен номер действия пользователя. Добавьте собственный applicationRequestId, не используя requestId драйвера как идентификатор заказа. Сопоставление команд по driver requestId оставьте внутри контекста клиента/запуска.

Превратить вход формы в ограниченный запрос и различить пустой список

Форма присылает строку с выбранными артикулами. Построим путь до реального экрана: разбить строку, проверить значения, отправить ограниченный запрос, а также честно определить поведение пустого выбора.

Исходные данные и понятия

Исходная строка — пробел, CUP, запятая, пробел, LAMP. explode делит по запятой, trim удаляет крайние пробелы, array_filter убирает пустые элементы, array_unique исключает повторы, array_values восстанавливает последовательные индексы списка. Допускаются не более десяти артикулов из известного набора.

Путь данных, события и результата
  1. Показать реальный отказ строки в $in
  2. Преобразовать и проверить список CUP/LAMP
  3. Добавить условие status=active
  4. Запросить только sku/price, порядок и limit=2
  5. Показать две карточки
  6. Отдельно проверить $in: [] — совпадений нет

Короткая запись

$input = ' CUP, LAMP ';
$skus = array_map('trim', explode(',', $input)); // ['CUP', 'LAMP']
$filter = ['status' => 'active', 'sku' => ['$in' => $skus]];
// Полная программа проверяет допустимые SKU/количество, задаёт projection, sort, limit.

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

Что получится

ЭтапИтогКод / цена / количество
До преобразованияошибка find2
После проверки спискаCUP500
После проверки спискаLAMP1200
Пустой список $inсовпадений0

Как понимать результат

Фильтрация, порядок и предел результата заданы MongoDB до переноса данных в PHP. Пустой массив $in корректен, но не содержит ни одного допустимого значения, поэтому не означает «любой товар». Если по правилам интерфейса отсутствие выбора должно означать все активные товары, программа должна осознанно убрать условие sku, сохранив limit и другие ограничения.

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

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

Полная программа, helper, инструкция и архив
Превратить вход формы в ограниченный запрос и различить пустой списокСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(false);
$owned=[];$journal=new Mf124Journal(false);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$input=' CUP, LAMP ';
$failure=mf124Failure($manager,$database,['find'=>$collection,'filter'=>['sku'=>['$in'=>$input]],'limit'=>2],$journal,2);
$skus=array_values(array_unique(array_filter(array_map('trim',explode(',',$input)),fn($v)=>$v!=='')));
if(count($skus)>10 || array_filter($skus,fn($v)=>!in_array($v,['CUP','LAMP','MAT'],true))){throw new InvalidArgumentException('Invalid SKU selection');}
$products=mf124Screen($manager,$database,$collection,['status'=>'active','sku'=>['$in'=>$skus]]);
$empty=mf124Screen($manager,$database,$collection,['status'=>'active','sku'=>['$in'=>[]]]);
$rows=[['До преобразования','ошибка find',$failure['event']['error_code']]];
foreach($products as $product){$rows[]=['После проверки списка',$product['sku'],$product['price']];}
$rows[]=['Пустой список $in','совпадений',count($empty)];
$checks=['actual_initial_error'=>$failure['paired'],'normalized_list'=>$skus===['CUP','LAMP'],
    'projected_limited_result'=>$products===[['sku'=>'CUP','price'=>500],['sku'=>'LAMP','price'=>1200]],
    'empty_is_no_matches'=>$empty===[],'one_error_two_successes'=>count($journal->failed)===1 && count($journal->succeeded)===2];
$details=['Нормализованные артикулы'=>$skus,'Реальная выборка'=>$products,'Пустой список'=>$empty,
    'Граница'=>'Условия и операторы создаёт программа. Пользовательские массивы с $-ключами не передаются в запрос как готовый фильтр'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['До преобразования', 'ошибка find', 2], ['После проверки списка', 'CUP', 500], ['После проверки списка', 'LAMP', 1200], ['Пустой список $in', 'совпадений', 0]];
mf124Report('mongodb124-failures-corrected-screen',['Этап', 'Итог', 'Код / цена / количество'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-corrected-screen: OK',"\n";
Ожидаемый результат
mongodb124-failures-corrected-screen: OK

Реальный локальный MongoDB 8.0.34 / mongodb 2.5.5. Преднамеренный отказ проверяется по коду и настоящему событию; результат и очищенная диагностика сохранены в HTML/JSON.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. Пустой выбор должен показывать первые два активных товара. После нормализации сформируйте базовый фильтр status=active и добавляйте sku/$in только для непустого списка. Не убирайте limit вместе с условием.

Вопрос из практики: $in operator and an empty list. Разработчик смешал отсутствующее значение, пустой список и желание показать все документы. Урок на PHP различает неправильную строку, проверенный список и корректный пустой $in без совпадений. Обсуждение использовано для постановки задачи, не как источник современного API; решения и данные этой главы составлены отдельно.

Перенести два старых отчёта об ошибке с getServer на host и port

В старом обработчике отказов адрес сервера получался через getServer. После перехода на расширение 2.x этот метод удалён. Воспроизведём два старых случая и проверим, что прямые getHost/getPort дают тот же endpoint.

Исходные данные и понятия

Изолированный PHP 8.4.27 с mongodb 1.20.1 подключается к тому же локальному MongoDB 8.0.34. Ошибки find и aggregate настоящие. Только в их callbacks вызывается getServer и записывается ожидаемое предупреждение E_DEPRECATED.

Путь данных, события и результата
  1. Запустить отдельный старый PHP и свою коллекцию
  2. Получить два настоящих CommandFailed
  3. В каждом вызвать исторический getServer
  4. Считать адрес Server и прямые host/port события
  5. Проверить равенство адресов и два предупреждения
  6. Перенести основной код на getHost/getPort

Короткая запись

// Только ext-mongodb 1.20: этот вызов deprecated.
$server = $event->getServer();
$oldAddress = [$server->getHost(), $server->getPort()];
// С 1.20 доступна замена; на 2.x используйте только её:
$address = [$event->getHost(), $event->getPort()];

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

Что получится

Старая диагностикаКод отказаEndpoint совпалСовременная замена
find2truegetHost() + getPort()
aggregate40324truegetHost() + getPort()

Как понимать результат

Начиная с mongodb 1.20 getHost/getPort позволяют взять адрес без отдельного Server. getServer тогда уже deprecated; в 2.0 он удалён. Две строки результата соответствуют двум реальным применениям старого обработчика, а не имитации метода на новой версии. Обработчик предупреждения принимает ровно известный E_DEPRECATED и не скрывает остальные проблемы.

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

Не устанавливайте старую DLL поверх рабочего PHP ради этой главы. Старый отдельный runtime нужен только для миграционного опыта. Server из события может удерживать внутренний клиент; здесь сохраняется адрес, а объект освобождается. Повторный вызов MongoDB из callback не нужен.

Полная программа, helper, инструкция и архив
Перенести два старых отчёта об ошибке с getServer на host и portСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mf124-helper.php';
[$manager,$database,$runtime,$settings]=mf124Connect(true);
$owned=[];$journal=new Mf124Journal(true);$details=[];
try{
$collection=mf124Create($manager,$database,$owned);
$manager->addSubscriber($journal);
$one=mf124Failure($manager,$database,['find'=>$collection,'filter'=>['sku'=>['$in'=>'CUP']],'limit'=>1],$journal,2);
$two=mf124Failure($manager,$database,['aggregate'=>$collection,'pipeline'=>[['$aenMissingStage124'=>new stdClass]],'cursor'=>new stdClass],$journal,40324);
$rows=[];
foreach([$one,$two] as $failure){
    $event=$failure['event'];$same=$event['legacy_endpoint']===[$event['host'],$event['port']];
    $rows[]=[$event['command'],$event['error_code'],$same,'getHost() + getPort()'];
}
$checks=['two_real_failures'=>count($journal->failed)===2,'two_expected_deprecations'=>count($journal->deprecations)===2,
    'legacy_matches_current'=>$rows[0][2] && $rows[1][2],
    'legacy_method_exists'=>method_exists(MongoDB\Driver\Monitoring\CommandFailedEvent::class,'getServer')];
$details=['Предупреждения'=>$journal->deprecations,'Миграция'=>'Старая версия нужна только для воспроизведения; основной код не требует Server из события'];
}finally{
    $manager->removeSubscriber($journal);
    foreach(array_reverse($owned) as $name){mf124Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;foreach($owned as $name){if(mf124Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collection_removed']=$removed;
$checks['journal_types_and_endpoint']=mf124JournalValid($journal,$settings,$database);
$details['started']=array_values($journal->started);$details['failed']=array_values($journal->failed);
$details['succeeded']=array_values($journal->succeeded);$details['expected_deprecations']=$journal->deprecations;
$checks['expected_rows']=$rows===[['find', 2, true, 'getHost() + getPort()'], ['aggregate', 40324, true, 'getHost() + getPort()']];
mf124Report('mongodb124-failures-server-legacy',['Старая диагностика', 'Код отказа', 'Endpoint совпал', 'Современная замена'],$rows,$checks,$runtime,$details);
echo 'mongodb124-failures-server-legacy: OK',"\n";
Ожидаемый результат
mongodb124-failures-server-legacy: OK

Реальный запуск под отдельным mongodb 1.20.1; две настоящие ошибки и два ожидаемых E_DEPRECATED.

Открыть настоящую таблицу и наблюдения · Строки результата JSON

Упражнение с направлением решения. Найдите в старом проекте цепочку $event->getServer()->getHost(). Замените её на $event->getHost(), порт получите аналогично. Проверьте все типы событий, которые использует приложение, и удалите зависимость основного кода от старого метода.

Все getter отказа и обработчик commandFailed

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

МетодДля чего нужен
MongoDB\Driver\Monitoring\CommandFailedEvent::getCommandNameОпределить, какое протокольное действие отказало: find, aggregate или другое имя.
MongoDB\Driver\Monitoring\CommandFailedEvent::getDatabaseNameПривязать отказ команды к базе, в которой она выполнялась.
MongoDB\Driver\Monitoring\CommandFailedEvent::getDurationMicrosЗаписать фактическую длительность отказавшей команды и не спутать её со временем всей страницы.
MongoDB\Driver\Monitoring\CommandFailedEvent::getErrorПрочитать исключение, связанное с отказом команды, для диагностического кода и типа.
MongoDB\Driver\Monitoring\CommandFailedEvent::getHostУзнать hostname узла, на котором выполнялась отказавшая команда.
MongoDB\Driver\Monitoring\CommandFailedEvent::getPortДополнить hostname реальным портом узла, вернувшего отказ.
MongoDB\Driver\Monitoring\CommandFailedEvent::getRequestIdСоединить окончание Failed с тем Started, которому оно соответствует.
MongoDB\Driver\Monitoring\CommandFailedEvent::getOperationIdСохранить принадлежность команды к операции драйвера, не подменяя requestId.
MongoDB\Driver\Monitoring\CommandFailedEvent::getReplyПрочитать серверный ответ об отказе и выбрать поля, нужные для объяснения проблемы.
MongoDB\Driver\Monitoring\CommandFailedEvent::getServerConnectionIdСохранить номер соединения, известный серверу, для сопоставления с серверной диагностикой.
MongoDB\Driver\Monitoring\CommandFailedEvent::getServiceIdУточнить backend-сервис при работе через load balancer и честно обработать его отсутствие.
MongoDB\Driver\Monitoring\CommandFailedEvent::getServerПонять старый код обработчика и заменить исторический Server прямыми адресными getter.
MongoDB\Driver\Monitoring\CommandSubscriber::commandFailedПолучить настоящее уведомление об отказе команды и сохранить короткое наблюдение, не подменяя обработку результата операции.

Связать диагностику с прикладным решением

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

Дальше: пары Started/Succeeded и чтение ответа, SDAM и heartbeat, индексы и ограниченный каталог, агрегационные отчёты, частичные результаты записи и восстановление. Для формы входа полезны передача полей формы и различие списка PHP и массива BSON.

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

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

MongoDB\Driver\Monitoring\CommandFailedEvent::getCommandNameОпределить, какое протокольное действие отказало: find, aggregate или другое имя.

С ext-mongodb 1.3.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getCommandName(): string

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Строка с именем команды. Это не имя коллекции, не имя PHP-функции и не прикладное название страницы.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.3.0.

Один пользовательский шаг может отправить несколько команд. Имя find без requestId не различает два запроса одного клиента; для отчёта добавляйте собственное назначение действия.

Найти отказ поиска товара

$name = $event->getCommandName(); // find

Неверный список артикулов действительно отправлен командой find; рядом показан исправленный список товаров.

Полная программа, настоящий результат и архив

Отделить отчёт от поиска

$name = $event->getCommandName(); // aggregate

Опечатка этапа относится к aggregate; исправленный отчёт возвращает два активных товара и сумму 1700.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getDatabaseNameПривязать отказ команды к базе, в которой она выполнялась.

С ext-mongodb 1.19.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getDatabaseName(): string

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Имя базы данных строкой. В учебном запуске это явно настроенная php_manual_lab.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.19.0.

Имя базы не содержит имя коллекции и не доказывает права на все её данные. Это служебная информация: не публикуйте её автоматически в сообщении посетителю сайта.

Проверить базу поиска

$database = $event->getDatabaseName();

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

Полная программа, настоящий результат и архив

Подготовить адресный отчёт разработчику

$record['database'] = $event->getDatabaseName();

Две диагностические записи содержат базу вместе с назначением операции; фильтры и значения покупателя исключены.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getDurationMicrosЗаписать фактическую длительность отказавшей команды и не спутать её со временем всей страницы.

С ext-mongodb 1.3.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getDurationMicros(): int

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Целое число микросекунд. Измерение включает отправку сообщения и получение ответа сервера; 1000 микросекунд равны одной миллисекунде.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.3.0.

Не обещает постоянное время. Это не чистое CPU-время MongoDB, не стоимость рендеринга и не полное время подготовки/выбора сервера. Таймаут соединения до команды не получает выдуманную duration из отсутствующего события.

Оценить конкретный отказ отчёта

$microseconds = $event->getDurationMicros();

Фактическое значение сохраняется в HTML и metrics, а ожидаемый результат проверяет только корректный неотрицательный тип.

Полная программа, настоящий результат и архив

Передать единицы вместе с измерением

$record['duration_us'] = $event->getDurationMicros();

Поле явно названо duration_us; при сравнении инцидентов не приходится угадывать, были ли это секунды или миллисекунды.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getErrorПрочитать исключение, связанное с отказом команды, для диагностического кода и типа.

С ext-mongodb 1.3.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getError(): Exception

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Объект Exception, связанный с событием. В наблюдаемом запуске server-side отказ даёт в событии ServerException; вызов executeReadCommand отдельно выбрасывает CommandException.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.3.0.

Не сравнивайте объект или точный подкласс с исключением catch. Из getError берём разрешённые class/code, а не весь stack/сообщение. Хранение исключения может удерживать объекты из стека и их ресурсы.

Разобрать неизвестное действие

$error = $event->getError();
$code = $error->getCode(); // 59

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

Полная программа, настоящий результат и архив

Не перепутать код с полезными данными

$record = ['class'=>get_class($event->getError()), 'code'=>$event->getError()->getCode()];

В диагностике сохраняется код 2. Значения товара приходят только из нового исправленного запроса.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getHostУзнать hostname узла, на котором выполнялась отказавшая команда.

С ext-mongodb 1.20.0; замена адресной части удалённого getServer(). · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getHost(): string

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Имя хоста строкой. На стенде прямого подключения — 127.0.0.1; в другой топологии это фактически выбранный узел, не обязательно просто первая строка URI.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.20.0; замена адресной части удалённого getServer().

Host без порта не является полным endpoint. Не называйте его универсальным идентификатором кластера и не добавляйте к нему секреты из URI.

Связать ошибку индекса с узлом

$host = $event->getHost();

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

Полная программа, настоящий результат и архив

Убрать зависимость от Server

$sameHost = $event->getHost() === $server->getHost();

Старый обработчик двух отказов подтверждает совпадение прямого host и адреса исторического Server.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getPortДополнить hostname реальным портом узла, вернувшего отказ.

С ext-mongodb 1.20.0; используется вместе с getHost(). · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getPort(): int

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Целочисленный номер порта сервера. Учебная конфигурация по умолчанию использует 27075, но программа сверяется с явно переданным URI.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.20.0; используется вместе с getHost().

Не зашивайте 27017 или 27075 как вывод любого события. Одна машина может содержать разные экземпляры MongoDB; host и port читаются вместе.

Указать нужный экземпляр базы

$record['endpoint'] = [$event->getHost(), $event->getPort()];

Реальные порт и host помогают отделить учебный экземпляр от другого процесса на том же компьютере.

Полная программа, настоящий результат и архив

Перенести диагностику порта

$samePort = $event->getPort() === $server->getPort();

В двух исторических отказах порт нового getter совпадает с портом старого Server.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getRequestIdСоединить окончание Failed с тем Started, которому оно соответствует.

С ext-mongodb 1.3.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getRequestId(): string

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Строковый request ID, созданный расширением. Он используется для сопоставления этого события с CommandStartedEvent.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.3.0.

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

Найти начало отказавшего find

$start = $journal->started[$event->getRequestId()] ?? null;

Проверяется существование Started той же команды, того же endpoint и operationId до вывода кода ошибки.

Полная программа, настоящий результат и архив

Не смешать две ошибки одной страницы

$record['request_id'] = $event->getRequestId();

Два отдельных find/aggregate получают разные наблюдаемые requestId и сохраняются с собственными метками действий.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getOperationIdСохранить принадлежность команды к операции драйвера, не подменяя requestId.

С ext-mongodb 1.3.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getOperationId(): string

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Строковый operation ID, созданный расширением. Несколько протокольных команд одной операции, например частей bulk, могут иметь одинаковое значение.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.3.0.

Официальный контракт прямо запрещает считать operationId надёжной заменой requestId для попарного сопоставления событий. Значение может быть "0"; не требуйте положительности или глобальной уникальности. Это не application request ID.

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

$sameOperation = $start['operation_id'] === $event->getOperationId();

После нахождения Started по requestId сверяется дополнительное поле операции; только им пару не ищут.

Полная программа, настоящий результат и архив

Оставить контекст для расследования

$record['operation_id'] = $event->getOperationId();

Строка сохраняется рядом с requestId и меткой действия, а не используется как единственный ключ двух инцидентов.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getReplyПрочитать серверный ответ об отказе и выбрать поля, нужные для объяснения проблемы.

С ext-mongodb 1.5.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getReply(): object

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Документ ответа в виде stdClass, полученный из BSON по стандартным правилам десериализации. В серверных отказах здесь наблюдаются ok, errmsg, code и codeName; произвольная ошибка не обязана содержать весь этот набор.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.5.0.

Это объект, поэтому поле читается как $reply->code. Не печатайте весь документ: errmsg может включать данные запроса. Проверяйте наличие необязательных полей. Ответ события не является новым успешным результатом каталога.

Объяснить причину отсутствия карточек

$reply = $event->getReply();
$code = $reply->code ?? null;

Неверный $in даёт ok=0/code=2. Helper сверяет код с исключением, а карточки получает только после исправления.

Полная программа, настоящий результат и архив

Выдать только разрешённые подробности

$safe = mf124SafeReply($event->getReply());

В helper разрешены ok/code/codeName; поля errmsg и пользовательские значения не попадают в отчёт.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getServerConnectionIdСохранить номер соединения, известный серверу, для сопоставления с серверной диагностикой.

С ext-mongodb 1.14.0. Серверный connectionId появляется в hello MongoDB 4.2+. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getServerConnectionId(): ?int

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

Целочисленный server connection ID либо null, если он недоступен. Это поле connectionId ответа hello, отличное от объекта Server и requestId команды.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.14.0. Серверный connectionId появляется в hello MongoDB 4.2+.

Не делайте nullable-контракт обязательным положительным числом для всех серверов. Несколько команд могут использовать одно соединение. В примере фактическое int/null сохраняется в подробностях; постоянное значение не задаётся.

Дополнить отказ индексного запроса

$record['connection_id'] = $event->getServerConnectionId();

Код 2, endpoint и фактический connection ID помогают искать связанный серверный контекст без копирования запроса.

Полная программа, настоящий результат и архив

Отделить соединение от запроса

$requestId = $event->getRequestId();
$connectionId = $event->getServerConnectionId();

Отчёт сохраняет оба поля с разными именами: команда определяется не одним номером соединения.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getServiceIdУточнить backend-сервис при работе через load balancer и честно обработать его отсутствие.

С ext-mongodb 1.11.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getServiceId(): ?MongoDB\BSON\ObjectId

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

ObjectId из поля serviceId ответа hello при подключении через load balancer; null, когда драйвер не подключён в таком режиме.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.11.0.

Это не _id товара, не topologyId и не адрес узла. На прямом Standalone наш настоящий результат — null. Этот опыт не моделирует балансировщик; нельзя придумывать ObjectId для заполнения пустого поля.

Не объявить null дополнительной ошибкой

$service = $event->getServiceId(); // null на этом прямом стенде

Неподдерживаемая команда отвергнута кодом 59; отсутствие serviceId имеет отдельную причину — режим подключения.

Полная программа, настоящий результат и архив

Сохранить nullable-поле корректно

$serviceId = $event->getServiceId();
$record['service_id'] = $serviceId === null ? null : (string)$serviceId;

Таблица прямо показывает отсутствие serviceId у двух настоящих отказов, сохраняя контракт для другого режима.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandFailedEvent::getServerПонять старый код обработчика и заменить исторический Server прямыми адресными getter.

С ext-mongodb 1.3.0; deprecated с 1.20.0; удалён в 2.0.0. · mongodb

final public function MongoDB\Driver\Monitoring\CommandFailedEvent::getServer(): MongoDB\Driver\Server

Параметры

Параметров нет. Метод вызывается у объекта события, переданного настоящим драйвером в commandFailed.

Результат

В старом расширении возвращает объект MongoDB\Driver\Server узла, на котором выполнялась команда. На 2.x метода нет, и вызов удалённого метода не является допустимой стратегией миграции.

Ошибки

PHP.net указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение исходной операции обрабатывается отдельно; чтение getter не повторяет команду.

Версии и границы

С ext-mongodb 1.3.0; deprecated с 1.20.0; удалён в 2.0.0.

Реальные применения выполняются только в отдельном 1.20.1. Ожидается E_DEPRECATED. Для адреса используйте getHost/getPort с 1.20. Не храните Server без необходимости и не вызывайте через него новые команды внутри callback.

Мигрировать ошибку поиска товара

$server = $event->getServer(); // legacy: failed find
$endpoint = [$server->getHost(), $server->getPort()];

Первый настоящий отказ find получает Server; его endpoint сравнивается с прямыми getter события.

Полная программа, настоящий результат и архив

Мигрировать ошибку отчёта

$old = $event->getServer(); // legacy: failed aggregate
$same = [$old->getHost(), $old->getPort()] === [$event->getHost(), $event->getPort()];

Второй настоящий отказ aggregate повторяет миграционную проверку для обработчика отчётов; в журнале есть второе ожидаемое предупреждение.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

MongoDB\Driver\Monitoring\CommandSubscriber::commandFailedПолучить настоящее уведомление об отказе команды и сохранить короткое наблюдение, не подменяя обработку результата операции.

С ext-mongodb 1.3.0. С 1.15.0 на PHP 8+ возвращаемый тип интерфейса tentative; с 2.0.0 соблюдение объявленного void обязательно. · mongodb

abstract public function MongoDB\Driver\Monitoring\CommandSubscriber::commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event): void

Параметры

$event — MongoDB\Driver\Monitoring\CommandFailedEvent с информацией об отказавшей команде. Когда объект-подписчик зарегистрирован, драйвер вызывает этот метод; прикладной код не создаёт искусственное событие и не вызывает callback для имитации отказа.

Результат

Ничего не возвращает: void. Полная реализация CommandSubscriber также содержит commandStarted и commandSucceeded; в helper они сохраняют начало и успешное окончание.

Ошибки

PHP.net перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Исключение вызова MongoDB по-прежнему нужно обрабатывать вокруг исходной операции. Здесь callback не выполняет запросы, не бросает свои исключения и не печатает пользователю сырой ответ.

Версии и границы

С ext-mongodb 1.3.0. С 1.15.0 на PHP 8+ возвращаемый тип интерфейса tentative; с 2.0.0 соблюдение объявленного void обязательно.

Не является общим обработчиком любых проблем приложения. До отправки команды callback может отсутствовать; writeErrors внутри успешного ответа могут сопровождаться CommandSucceeded. Подписка снимается в finally, буфер ограничивается; секреты и тяжёлая работа в callback не нужны.

Зафиксировать реальный отказ отчёта

public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event): void {
    $this->codes[] = $event->getError()->getCode();
} // Фрагмент класса; в полном helper буфер и поля ограничены.

Отправленный неизвестный этап действительно вызывает уведомление; отдельный catch и исправленный отчёт показаны рядом.

Полная программа, настоящий результат и архив

Вести ограниченный разрешённый журнал

$row = ['request_id'=>$event->getRequestId(), 'reply'=>mf124SafeReply($event->getReply())];

Две реальные ошибки разнесены по назначению и requestId; журнал сохраняет нужные сведения без значений покупателя.

Полная программа, настоящий результат и архив

Контекст: начала и успешные окончания команд, события обнаружения и heartbeat, результаты и частичные отказы записи.

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

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

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

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

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