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

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

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

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

Глава 160 · После Manager, Query, Cursor и основ объектов

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

От filter/projection/limit до requestId, getMore и реальных длительностей;23 карточки методов и 9 самостоятельных сценариев, включая изолированный перенос getServer.

Увидеть запрос, который действительно наполнил экран

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

От нужного экрана к наблюдаемым действиям
  1. Query: active=true, stock>0, limit=2
  2. commandStarted: документ find
  3. MongoDB: фильтрация и сортировка
  4. commandSucceeded: reply и duration
  5. Экран: Кабель 900 коп., Чехол 2500 коп.

Событие — объект со сведениями о произошедшем действии. Подписчик — ваш объект с заранее определёнными методами; драйвер вызывает нужный метод автоматически. Эти методы называются callbacks. Мы не создаём событие вручную и не запускаем новый запрос при чтении его полей.

public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event): void {
    $command = $event->getCommand();
    $this->started[] = $event;
}

Это метод внутри класса подписчика. Параметр $event заполняет драйвер; -> вызывает метод объекта. : void означает отсутствие возвращаемого значения. Реальный observer ниже реализует все три обязательных callbacks и хранит ограниченное число событий. Сначала освоить Manager, Query и документы, ограниченную выборку и курсор и регистрацию наблюдателя.

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

СведенияКакой вопрос решаютС чем не путать
commandNamefind, aggregate или продолжение getMoreИмя коллекции / уникальный ID
getCommandЧто действительно отправил драйверPHP-массив исходного Query без служебных полей
requestIdКакие начало и завершение составляют паруID заказа / постоянный UUID
operationIdКакие команды связаны одной операцией драйвераУникальный ненулевой ID каждой команды
getReplyЧто ответила эта командаВсегда весь набор товаров / успех каждой записи
durationMicrosОтправка и получение ответа, микросекундыЧистое серверное время / RTT hello
host/port/databaseNameГде выполнялась командаСтрока подключения с credentials
serverConnectionId / serviceIdКонтекст hello соединения / сервиса балансировщикаcursor.id / _id товара

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

Запустить лабораторию и прочитать фактические результаты

Девять архивов самостоятельны. Восемь используют PHP 8.5.11 / ext-mongodb 2.5.5, исторический server-legacy — отдельные PHP 8.4.27 / ext-mongodb 1.20.1. Сервер общий для лаборатории: MongoDB 8.0.34 standalone на явном loopback-порту. Минимальные требования текущих программ PHP 8.4+ CLI x64, mongodb 2.5+, MongoDB 8.0+. Composer не требуется.

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

Команды выполняются в PowerShell из распакованного каталога. Каждый README содержит установку расширения, требования DLL/CLI ini, запуск отдельного mongod и использование отдельного PHP для legacy. Не меняйте расширение работающего сайта ради исторического примера. В архиве example.php, ct124-helper.php и README.txt.

Создаётся только собственная случайная коллекция mongodb124_trace_…_catalog с семью товарами. После успешного create её точное имя записывается в ownership. finally снимает подписчика и удаляет только эту коллекцию, затем проверяет отсутствие. URI принимается только для локальной учебной базы; это не инструкция ослабить защиту production.

Observer включается после подготовки фикстуры и снимается до уборки: служебные create/drop не мешают объяснению целевых чтений. В каждом callback не более 48 сохранённых событий, сетевых вызовов нет. Подписка привязана к Manager; новый непереиспользуемый клиент изолирует опыт, не требуется для каждого рабочего запроса.

result.html показывает полезную таблицу и реальные события. result.json содержит повторяемый результат, observations.json — фактические ID, адреса, длительности и разрешённые детали. metrics.json сохраняет версии, проверки и SHA256. Значения длительности и ID меняются при повторе; проверяются их типы и связи, а не придуманные точные числа. Браузер открывает сохранённый результат, не исполняет запросы к MongoDB.

Увидеть фильтр и поля настоящей команды страницы каталога

Страница должна показать два самых дешёвых доступных товара. Чтобы найти ошибку после изменения кода, посмотрим, какие filter, projection, sort и limit действительно отправил драйвер, и сопоставим их с товарами экрана.

Данные и подготовка

Семь товаров; Блокнот имеет нулевой остаток, Лампа выключена. Условие active=true и stock>0 выполняется в MongoDB. Сервер сортирует по цене и _id, отдаёт только три нужных поля и не более двух документов.

Как получается результат
  1. 7 товаров → серверный filter
  2. projection + sort + limit=2
  3. Настоящее CommandStartedEvent
  4. Кабель 900 коп.; Чехол 2500 коп.

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

$command = $event->getCommand();
echo $command->filter->stock->{'$gt'}; // 0
echo $command->limit; // 2

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

Что получит читатель

ТоварНазваниеЦена, коп.
107Кабель900
106Чехол2500

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

getCommand() возвращает stdClass: поля читаются через ->. В ->{'$gt'} фигурные скобки позволяют обратиться к буквальному имени поля со знаком $. Это не PHP-переменная $gt.

Наблюдение включается после подготовки коллекции. Проверяются отдельно отправленные параметры и полученные товары. Пара find выделяется по имени команды; реальная дополнительная getMore закрывает курсор пустым nextBatch и остаётся в полной трассе.

Драйвер может добавить $db, lsid и другие служебные поля. Мы сохраняем только проверенную часть команды. Копирование getCommand в другой объект не меняет уже отправленный Query.

Сортировку выполняет MongoDB до передачи результата. PHP получает две строки для вывода, а не весь каталог для поиска и сортировки. batchSize управляет партией транспорта; limit ограничивает число нужных строк.

Границы применения

Started ещё не подтверждает успех. Для обычного пользовательского фильтра нельзя публиковать полный дамп: используйте отбор полей из safe-log.

Полная самостоятельная программа и архив
Увидеть фильтр и поля настоящей команды страницы каталогаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$cursor=ct124Page($manager,$namespace);$products=$cursor->toArray();
$pairs=ct124TargetPairs($observer,$name,$transport);[$start,$end]=$pairs[0];$command=$start->getCommand();
$actual=['active'=>$command->filter->active,'stock_gt'=>$command->filter->stock->{'$gt'},
    'projection'=>(array)$command->projection,'sort'=>(array)$command->sort,'limit'=>(string)$command->limit];
$observations['details']=['checked_command'=>$actual,'returned_ids'=>array_column($products,'_id')];
$rows=[];foreach($products as $product){$rows[]=[$product['_id'],$product['title'],$product['price_kopecks']];}
$checks=['two_needed_products'=>array_column($products,'_id')===[107,106],
    'filter_sent'=>$actual['active']===true && $actual['stock_gt']===0,
    'projection_sent'=>$actual['projection']===['_id'=>1,'title'=>1,'price_kopecks'=>1],
    'sort_limit_sent'=>$actual['sort']===['price_kopecks'=>1,'_id'=>1] && $actual['limit']==='2',
    'one_find_pair'=>count($pairs)===1 && $start->getCommandName()==='find',
    'command_is_stdclass'=>$command instanceof stdClass];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-catalog-command', ['Товар', 'Название', 'Цена, коп.'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-catalog-command: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-catalog-command: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

Попробуйте изменить. Измените limit на 1 вместе с ожиданиями: останется Кабель. При сортировке цены по убыванию первым станет Рюкзак за 18000 коп. Проверьте и команду, и строку результата.

Практический вопрос разработчика: PHP: CommandSubscriber отсутствует в приложении при установленном расширении. Автор пытается включить мониторинг в веб-приложении, хотя CLI показывает mongodb. Берём потребность проверить именно используемый php.exe/ini и наличие интерфейса, не переносим чужой диагноз как доказанный результат нашей лаборатории. Обсуждение задаёт реальную проблему; контракт PHPAPI подтверждается первичными источниками, решения примеров написаны для этой лаборатории.

Связать страницу и складской отчёт с их ответами

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

Данные и подготовка

find возвращает два товара; aggregate возвращает один документ products=5, stock=21. Данные помещаются в первую партию каждой команды, но при limit=2 и batchSize=2 после find наблюдается ещё пустая getMore. Бизнес-таблица выделяет пары find/aggregate; полный журнал сохраняет и продолжение.

Как получается результат
  1. Страница find + сводка aggregate
  2. started.requestId → pending
  3. succeeded.requestId → нужное начало
  4. Две проверенные пары

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

$pending['request:'.$started->getRequestId()] = $started;
$start = $pending['request:'.$succeeded->getRequestId()];

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

Что получит читатель

КомандаДокументов в ответеСопоставление
find2начало связано с ответом
aggregate1начало связано с ответом

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

requestId — строка, созданная расширением. Префикс request: в ключе массива предотвращает превращение числовой строки ключа в integer. Идентификаторы не используются для арифметики.

ct124Pairs сравнивает requestId, имя команды, operationId, адрес и базу, удаляет использованное начало и отвергает непарный ответ. Сопоставление относится к одному ограниченному захвату, а не обещает глобальную уникальность ID навсегда.

operationId группирует связанные команды и не заменяет requestId: нескольким getMore может соответствовать один operationId. Строка "0" также допустима и не является ID заказа.

getReply даёт оболочку ответа. firstBatch у find содержит два товара, а у aggregate — один итоговый документ. Количество документов ответа не равно числу товаров, посчитанных внутри сводки.

Границы применения

Ошибка сервера требует CommandFailedEvent и обработки исключения вызывающим кодом. Здесь успешность наблюдается, а не угадывается по расположению строк журнала.

Полная самостоятельная программа и архив
Связать страницу и складской отчёт с их ответамиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$cursor=ct124Page($manager,$namespace);$products=$cursor->toArray();
$summary=ct124Summary($manager,$database,$name);$pairs=ct124TargetPairs($observer,$name,$transport);
$rows=[];foreach($pairs as [$start,$end]){
    $reply=$end->getReply();$batch=$reply->cursor->firstBatch??[];
    $rows[]=[$start->getCommandName(),count($batch),'начало связано с ответом'];
}
$requestIds=array_map(fn($pair)=>$pair[0]->getRequestId(),$pairs);
$observations['details']=['page_ids'=>array_column($products,'_id'),'summary'=>$summary,
    'pair_count'=>count($pairs),'request_ids'=>$requestIds];
$checks=['two_different_actions'=>array_column($rows,0)===['find','aggregate'],
    'request_pairs_unique'=>count($requestIds)===2 && count(array_unique($requestIds))===2,
    'page_and_summary'=>array_column($products,'_id')===[107,106] && $summary===[['products'=>5,'stock'=>21]],
    'reply_documents'=>array_column($rows,1)===[2,1]];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-request-pairs', ['Команда', 'Документов в ответе', 'Сопоставление'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-request-pairs: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-request-pairs: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

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

Разобрать чтение пяти товаров на find и getMore

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

Данные и подготовка

Активны и доступны товары 101,103,105,106,107. limit=7 ограничивает чтение, batchSize=1 делает партии заметными на маленьком наборе. Проекция оставляет ID/название, сортировка по _id сохраняет порядок.

Как получается результат
  1. find → firstBatch
  2. foreach читает продолжение
  3. getMore → nextBatch
  4. 5 товаров; сумма партий=5

Минимальное действие

$query = new MongoDB\Driver\Query(['active'=>true, 'stock'=>['$gt'=>0]],
    ['projection'=>['_id'=>1,'title'=>1], 'sort'=>['_id'=>1], 'limit'=>7, 'batchSize'=>1]);

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

Что получит читатель

IDНазвание
101Кружка
103Термос
105Рюкзак
106Чехол
107Кабель

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

Первая команда find возвращает cursor.firstBatch. Продолжение вызывает getMore с cursor.nextBatch в ответе. В observations видны настоящие размеры партий и ID курсора.

Последняя партия может оказаться пустой. Поэтому число getMore не зашито в ожидания: проверяются их наличие, сумма размеров 5, максимум один документ в партии и финальный cursor.id="0".

В наблюдаемом чтении find/getMore имеют общий operationId, каждой команде соответствует собственная пара requestId. Это полезная группировка данного пути, не гарантия ненулевых или уникальных operationId для любого API.

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

Границы применения

batchSize=1 нужен этому опыту, не является рекомендацией для производительности. getMore не означает ошибку. Change stream/tailable — другие сценарии ожидания, здесь обычный конечный find.

Полная самостоятельная программа и архив
Разобрать чтение пяти товаров на find и getMoreСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
$cursor=$manager->executeQuery($namespace,new MongoDB\Driver\Query(['active'=>true,'stock'=>['$gt'=>0]],[
    'projection'=>['_id'=>1,'title'=>1],'sort'=>['_id'=>1],'limit'=>7,'batchSize'=>1,'maxTimeMS'=>2000]));
$cursor->setTypeMap(['root'=>'array','document'=>'array','array'=>'array']);$products=[];
foreach($cursor as $product){$products[]=$product;}
$pairs=ct124Pairs($observer);$batches=[];$total=0;$getMore=0;$operationIds=[];
foreach($pairs as [$start,$end]){
    $reply=$end->getReply();$batch=$reply->cursor->firstBatch??$reply->cursor->nextBatch??[];
    $size=count($batch);$total+=$size;$getMore+=(int)($start->getCommandName()==='getMore');
    $operationIds[]=$start->getOperationId();
    $batches[]=['command'=>$start->getCommandName(),'request_id'=>$start->getRequestId(),
        'documents'=>$size,'cursor_id'=>(string)$reply->cursor->id];
}
$observations['details']=['batches'=>$batches,'getmore_commands'=>$getMore,'total_received'=>$total];
$rows=[];foreach($products as $product){$rows[]=[$product['_id'],$product['title']];}
$checks=['five_rows_once'=>array_column($products,'_id')===[101,103,105,106,107],
    'real_getmore'=>$getMore>=1 && $pairs[0][0]->getCommandName()==='find',
    'batch_totals'=>$total===5 && max(array_column($batches,'documents'))<=1,
    'one_observed_operation'=>count(array_unique($operationIds))===1,
    'final_cursor_closed'=>$batches[count($batches)-1]['cursor_id']==='0'];
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-getmore-batches', ['ID', 'Название'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-getmore-batches: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-getmore-batches: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

Попробуйте изменить. Поставьте batchSize=2 и измените проверку размера партии. Пять товаров останутся теми же; изучите фактические партии, не требуя их количества по формуле.

Практический вопрос разработчика: Как выяснить, какое действие порождает getMore. Рост getMore непонятен без связи с исходным чтением. Оригинальный опыт собирает реальные find/getMore и их requestId/operationId; не включает глобальный profiler и не копирует серверные настройки из старого ответа. Обсуждение задаёт реальную проблему; контракт PHPAPI подтверждается первичными источниками, решения примеров написаны для этой лаборатории.

Сопоставить время двух команд с бюджетом экрана

Экран каталога стал медленным. Измерим команды страницы и сводки отдельно от всего прикладного участка, чтобы не выдать общее время PHP за чистое время работы MongoDB.

Данные и подготовка

find и aggregate получают прежние данные. В таблице бюджета выделены эти две целевые команды. Полная трасса дополнительно показывает длительность пустой getMore после страницы; hrtime охватывает всё чтение, включая это продолжение. Порог 10000 мкс=10 мс — только диагностическая метка.

Как получается результат
  1. hrtime: начало
  2. Две команды и чтение строк
  3. durationMicros /1000 → мс
  4. Реальные измерения и сравнение с бюджетом

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

$microseconds = $event->getDurationMicros();
$milliseconds = $microseconds / 1000;
$overBudget = $microseconds > 10000;

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

Что получит читатель

Часть экранаКоличество / порогКак читать
Страница каталога2измерение find в observations.json
Складская сводка1измерение aggregate в observations.json
Порог одной команды10000микросекунды, отметка без отмены запроса

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

Длительность включает отправку сообщения и получение ответа, а не только серверное выполнение. Это другая величина, чем executionTimeMillis в explain или RTT служебного hello.

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

Прикладной интервал включает дополнительную работу между командами. Нельзя без иных измерений вычесть несопоставимые величины и объявить остаток сетевой задержкой. Сам наблюдатель также добавляет работу.

Сравнение $us>$budgetUs лишь помечает запись. Оно не отменяет команду и не задаёт серверный timeout. maxTimeMS в Query — отдельный механизм.

Границы применения

Две команды семи товаров на localhost не доказывают производительность production. Для расследования нужны распределение задержек, форма запроса, индексы и объём результата.

Полная самостоятельная программа и архив
Сопоставить время двух команд с бюджетом экранаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$before=hrtime(true);
$cursor=ct124Page($manager,$namespace);$products=$cursor->toArray();
$summary=ct124Summary($manager,$database,$name);
$applicationUs=intdiv(hrtime(true)-$before,1000);$budgetUs=10000;$measurements=[];
foreach(ct124TargetPairs($observer,$name,$transport) as [$start,$end]){
    $us=$end->getDurationMicros();
    $measurements[]=['command'=>$end->getCommandName(),'duration_us'=>$us,'duration_ms'=>$us/1000,
        'budget_us'=>$budgetUs,'over_budget'=>$us>$budgetUs];
}
$observations['details']=['command_measurements'=>$measurements,'application_interval_us'=>$applicationUs,
    'application_interval_scope'=>'запрос страницы, чтение строк и запрос сводки'];
$rows=[['Страница каталога',count($products),'измерение find в observations.json'],
    ['Складская сводка',count($summary),'измерение aggregate в observations.json'],
    ['Порог одной команды',$budgetUs,'микросекунды, отметка без отмены запроса']];
$checks=['two_actual_durations'=>count($measurements)===2 && min(array_column($measurements,'duration_us'))>=0,
    'units_and_budget'=>count(array_filter($measurements,fn($m)=>$m['duration_ms']===$m['duration_us']/1000 && $m['over_budget']===($m['duration_us']>$budgetUs)))===2,
    'application_interval_nonnegative'=>$applicationUs>=0,
    'screen_data'=>array_column($products,'_id')===[107,106] && $summary===[['products'=>5,'stock'=>21]]];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-duration-budget', ['Часть экрана', 'Количество / порог', 'Как читать'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-duration-budget: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-duration-budget: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

Попробуйте изменить. Уменьшите бюджет до 1000 мкс. Пример должен пройти при любом результате сравнения, если реальные измерения валидны и данные верны.

Практический вопрос разработчика: Почему чтение через драйвер выглядит медленнее оболочки. Вопрос сравнивает полное прикладное чтение и время продолжения курсора. Наш пример отделяет измерения команд от участка PHP и не утверждает диагноз для C# или превосходство одного драйвера. Обсуждение задаёт реальную проблему; контракт PHPAPI подтверждается первичными источниками, решения примеров написаны для этой лаборатории.

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

Для сообщения поддержки надо указать базу и адрес именно тех команд, которые наполнили экран. Формулировка "MongoDB работает" не даёт такого контекста.

Данные и подготовка

Выделяем целевые find и aggregate на standalone. Для каждой пары читаются host, port, databaseName, serverConnectionId и serviceId. Пустая завершающая getMore сохраняется в полной трассе, но не создаёт третью строку компонентов экрана.

Как получается результат
  1. find и aggregate
  2. Метаданные начала и ответа
  3. Сверка адреса и базы
  4. Endpoint и nullable ID без URI

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

$endpoint = $event->getHost().':'.$event->getPort();
$database = $event->getDatabaseName();
$connection = $event->getServerConnectionId(); // ?int

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

Что получит читатель

КомандаСвязь адресовКонтекстserviceId
findадрес начала и ответа совпалучебная базаNULL: не load-balanced
aggregateадрес начала и ответа совпалучебная базаNULL: не load-balanced

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

getHost/getPort — части адреса сервера команды; getDatabaseName — база выполнения. Их не надо извлекать из текста исключения или угадывать по названию коллекции.

serverConnectionId приходит из hello сервера MongoDB 4.2+. Это не requestId и не число подключений. API допускает NULL; наш MongoDB 8.0 стенд возвращает известное целое значение.

serviceId имеет тип ObjectId в load-balanced режиме, иначе NULL. На standalone NULL ожидаем: балансировщик и переключение узлов этот опыт не проверяет.

Одинаковые host:port означают одинаковый наблюдаемый адрес, а не криптографически доказанную личность машины. Один connectionId не обещает то же соединение для всех будущих команд.

Границы применения

Метаданные события не изменяют Manager и не заменяют read preference. Отчёт не содержит строки URI или пароля.

Полная самостоятельная программа и архив
Показать адрес ответа страницы и складской сводкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$cursor=ct124Page($manager,$namespace);$products=$cursor->toArray();$summary=ct124Summary($manager,$database,$name);
$rows=[];$connections=[];$allMatch=true;$allServiceNull=true;
foreach(ct124TargetPairs($observer,$name,$transport) as [$start,$end]){
    $allMatch=$allMatch && $start->getHost()===$end->getHost() && $start->getPort()===$end->getPort()
        && $start->getDatabaseName()===$database && $end->getDatabaseName()===$database;
    $connections[]=$start->getServerConnectionId();$connections[]=$end->getServerConnectionId();
    $allServiceNull=$allServiceNull && $start->getServiceId()===null && $end->getServiceId()===null;
    $rows[]=[$end->getCommandName(),'адрес начала и ответа совпал','учебная база',
        $end->getServiceId()===null?'NULL: не load-balanced':'есть serviceId'];
}
$observations['details']=['actual_database'=>$database,'actual_endpoint'=>$settings['host'].':'.$settings['port'],
    'server_connection_ids'=>$connections,'page_ids'=>array_column($products,'_id'),'summary'=>$summary];
$checks=['same_endpoint_and_database'=>$allMatch && count($rows)===2,
    'standalone_service_ids_null'=>$allServiceNull,
    'connection_ids_from_hello'=>count(array_filter($connections,'is_int'))===4,
    'useful_results'=>count($products)===2 && $summary===[['products'=>5,'stock'=>21]]];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-endpoint-context', ['Команда', 'Связь адресов', 'Контекст', 'serviceId'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-endpoint-context: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-endpoint-context: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

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

Сохранить полезный журнал без контакта из фильтра

В фильтре есть контакт покупателя, но журналу нужны только команда, адрес, ID и длительность. Полный getCommand/getReply может раскрыть пользовательские данные.

Данные и подготовка

Демонстрационные buyer@example.invalid и DEMO_PRIVATE_NOTE_124 присутствуют в фикстуре. Контакт реально входит в filter. Query возвращает только поля товара; публичный файл получает отобранные метаданные.

Как получается результат
  1. Команда с контактным фильтром
  2. Реальные события и пустая getMore
  3. Allowlist для всей трассы
  4. 2 товара и 2 целевые записи find

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

$safe = ['command'=>$event->getCommandName(), 'request_id'=>$event->getRequestId(),
    'host'=>$event->getHost(), 'port'=>$event->getPort()];
// Не добавляем $event->getCommand() целиком.

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

Что получит читатель

Что сохраняемКоличествоПравило
Страница каталога2нужные поля товара
Начало и ответ2только разрешённые метаданные
Контакт и служебная заметка0в публичном журнале отсутствуют

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

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

Разрешены фаза, имя команды, база, requestId, operationId, host, port, serverConnectionId, serviceId и duration. В конкретной организации даже имя базы/адрес могут требовать ограниченного доступа.

Драйвер скрывает тела определённых команд аутентификации, но не обещает автоматического удаления личных значений из обычных find, aggregate, comment и reply. Выбор безопасного формата остаётся задачей приложения.

Строка таблицы «Начало и ответ:2» считает только пару find. Все записи, включая пустую getMore, остаются в observations.events; отсутствие частных строк и разрешённые ключи проверяются во всей трассе. Callback не делает новых запросов и не пишет файл.

Границы применения

Две контрольные строки не доказывают универсальную анонимизацию. Надёжнее вообще не включать raw command/reply в формат, чем удалять отдельные запрещённые ключи.

Полная самостоятельная программа и архив
Сохранить полезный журнал без контакта из фильтраСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$cursor=ct124Page($manager,$namespace,['contact'=>'buyer@example.invalid']);$products=$cursor->toArray();
$pairs=ct124TargetPairs($observer,$name,$transport);[$start,$end]=$pairs[0];
$allTrace=ct124Trace($observer);$trace=[];
foreach($pairs as [$targetStart,$targetEnd]){
    $trace[]=ct124Metadata($targetStart,'started');$trace[]=ct124Metadata($targetEnd,'succeeded');
}
$encoded=json_encode($allTrace,JSON_THROW_ON_ERROR);
$containsPrivate=str_contains($encoded,'buyer@example.invalid') || str_contains($encoded,'DEMO_PRIVATE_NOTE_124');
$allowed=['phase','command','database','request_id','operation_id','host','port','server_connection_id','service_id','duration_us'];
$unknown=[];foreach($allTrace as $row){$unknown=array_merge($unknown,array_diff(array_keys($row),$allowed));}
$observations['details']=['saved_event_count'=>count($trace),'allowed_field_names'=>$allowed,
    'private_filter_was_present'=>isset($start->getCommand()->filter->contact),
    'private_values_saved'=>$containsPrivate,'page_ids'=>array_column($products,'_id')];
$rows=[['Страница каталога',count($products),'нужные поля товара'],
    ['Начало и ответ',count($trace),'только разрешённые метаданные'],
    ['Контакт и служебная заметка',0,'в публичном журнале отсутствуют']];
$checks=['filter_really_has_contact'=>$start->getCommand()->filter->contact==='buyer@example.invalid',
    'allowlist_only'=>$unknown===[] && count($trace)===2,
    'private_literals_excluded'=>!$containsPrivate,
    'projection_without_contact'=>!isset($products[0]['contact']) && !isset($products[0]['private_note']) && array_column($products,'_id')===[107,106]];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-safe-log', ['Что сохраняем', 'Количество', 'Правило'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-safe-log: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-safe-log: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

Попробуйте изменить. Добавьте другое личное поле в документ. При неизменных projection и allowlist оно не должно попасть в публичный результат.

Прочитать свойства события и защитить снимок от случайной правки

Новые версии расширения позволяют читать свойства события напрямую. Проверим, какие имена соответствуют getter-методам и почему событие нельзя использовать как редактируемую настройку запроса.

Данные и подготовка

В ветке 2.5.5 выделяем начала и завершения двух целевых команд find/aggregate. Для этих четырёх объектов сравниваем 9 либо 10 свойств с методами и пробуем изменить host. События дополнительной getMore не скрыты: они показаны в полной трассе.

Как получается результат
  1. Настоящие события
  2. getter ↔ readonly property
  3. 4 попытки присваивания → Error
  4. Снимок остаётся неизменным

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

echo $started->commandName; // readonly property, mongodb 2.3+
echo $succeeded->duration;  // микросекунды
// Для собственного названия копируем значение в массив, а не меняем событие.

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

Что получит читатель

Объект / действиеЧислоРезультат
CommandStartedEvent9доступ только для чтения
CommandSucceededEvent10duration соответствует getDurationMicros()
Заблокированные изменения host4снимок остался неизменным

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

Readonly properties появились вmongodb2.3.0. У started есть command, у succeeded — reply и duration. Свойства durationMicros нет: duration хранит микросекунды, как getDurationMicros().

Сравнение stdClass через == проверяет содержимое, не обязательное тождество объектов, возвращаемых разными обращениями к API. Простые значения сопоставляются с теми же getter.

Присваивание host вызывает Error. Catch принимает только ошибку readonly; неожиданная другая ошибка или успешная запись не становятся успешным тестом.

Для псевдонима сервера создайте собственный массив label. Событие описывает уже состоявшееся обращение, а не принимает новые настройки.

Границы применения

Этот архив требует текущую ветку, не 1.20.1. Getter-методы не исчезли из-за появления свойств. Правка вложенного PHP-объекта также не меняет уже отправленную команду.

Полная самостоятельная программа и архив
Прочитать свойства события и защитить снимок от случайной правкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$cursor=ct124Page($manager,$namespace);$products=$cursor->toArray();$summary=ct124Summary($manager,$database,$name);
$matches=true;$blocked=0;$checksCount=0;$propertyCounts=[];
foreach(ct124TargetPairs($observer,$name,$transport) as [$start,$end]){
    foreach([[$start,['host'=>'getHost','port'=>'getPort','commandName'=>'getCommandName','databaseName'=>'getDatabaseName',
        'operationId'=>'getOperationId','requestId'=>'getRequestId','serverConnectionId'=>'getServerConnectionId','serviceId'=>'getServiceId','command'=>'getCommand']],
        [$end,['host'=>'getHost','port'=>'getPort','commandName'=>'getCommandName','databaseName'=>'getDatabaseName',
        'operationId'=>'getOperationId','requestId'=>'getRequestId','serverConnectionId'=>'getServerConnectionId','serviceId'=>'getServiceId','duration'=>'getDurationMicros','reply'=>'getReply']]] as [$event,$map]){
        foreach($map as $property=>$method){$matches=$matches && $event->$property==$event->$method();$checksCount++;}
        try{$event->host='other.example.invalid';throw new RuntimeException('Readonly mutation unexpectedly succeeded');}
        catch(Error $error){if(!str_contains($error->getMessage(),'readonly')){throw $error;}$blocked++;}
    }
    $propertyCounts[]=[$start->getCommandName(),9,10];
}
$observations['details']=['property_counts'=>$propertyCounts,'getter_property_comparisons'=>$checksCount,
    'readonly_mutations_rejected'=>$blocked,'page_ids'=>array_column($products,'_id'),'summary'=>$summary];
$rows=[['CommandStartedEvent',9,'доступ только для чтения'],['CommandSucceededEvent',10,'duration соответствует getDurationMicros()'],['Заблокированные изменения host',$blocked,'снимок остался неизменным']];
$checks=['all_properties_equal_getters'=>$matches && $checksCount===38,
    'four_readonly_errors'=>$blocked===4,
    'real_two_commands'=>count($propertyCounts)===2 && array_column($products,'_id')===[107,106]];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-readonly-properties', ['Объект / действие', 'Число', 'Результат'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-readonly-properties: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-readonly-properties: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

Попробуйте изменить. Скопируйте host и добавьте label="Каталог" в свой массив. Не добавляйте произвольные свойства самому event.

Различить ответ MongoDB, первую партию и данные экрана

На странице два товара, а aggregate вернул одну строку с числом пять. Разделим оболочку ответа, количество документов партии и бизнес-значения сводки.

Данные и подготовка

find возвращает Кабель/Чехол, aggregate — products=5, stock=21. getReply даёт stdClass, хотя Cursor настроен возвращать PHP-массивы.

Как получается результат
  1. succeeded
  2. getReply → cursor/ok
  3. firstBatch → данные
  4. 2 карточки и 1 сводка о 5 товарах

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

$reply = $event->getReply();
$firstBatch = $reply->cursor->firstBatch ?? [];
echo count($firstBatch); // только первая партия данного ответа

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

Что получит читатель

КомандаДокументов firstBatchЧто показывает экран
find2Кабель; Чехол
aggregate15 товаров; остаток 21

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

typeMap Cursor влияет на его документы, но не на getReply события. Поэтому приложение использует $product['_id'], а наблюдаемый документ ответа — $document->_id.

ok относится к команде. Для строк экрана выбираем только find/aggregate с firstBatch. Фактическая дополнительная getMore имеет пустой nextBatch, поэтому требовать у каждого события firstBatch ошибочно. Продолжение сверяется по ID курсора и сохраняется в журнале.

Сервер посчитал products=5 внутри одного агрегированного документа. Проверка сравнивает значения с полезным результатом приложения, а не только размер JSON.

Публикуется ограниченная сводка reply, не весь raw ответ. Для записи CommandSucceeded может сопровождаться writeErrors и исключением BulkWriteException: это отдельно показано в соседней главе.

Границы применения

У insert/ping нет обязательного cursor.firstBatch. Общий наблюдатель выбирает разбор reply по имени команды, не применяет форму find ко всему.

Полная самостоятельная программа и архив
Различить ответ MongoDB, первую партию и данные экранаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(false);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$cursor=ct124Page($manager,$namespace);$products=$cursor->toArray();$summary=ct124Summary($manager,$database,$name);
$rows=[];$batches=[];$valid=true;$std=true;
foreach(ct124TargetPairs($observer,$name,$transport) as [$start,$end]){
    $reply=$end->getReply();$std=$std && $reply instanceof stdClass;
    $batch=$reply->cursor->firstBatch??null;
    if(!is_array($batch)){throw new RuntimeException('Expected firstBatch');}
    $row=['command'=>$end->getCommandName(),'ok'=>(int)$reply->ok,'documents'=>count($batch),'cursor_id'=>(string)$reply->cursor->id];
    $batches[]=$row;
    if($end->getCommandName()==='find'){
        $valid=$valid && array_map(fn($d)=>$d->_id,$batch)===[107,106];
        $rows[]=['find',count($batch),'Кабель; Чехол'];
    }else{
        $valid=$valid && count($batch)===1 && $batch[0]->products===5 && $batch[0]->stock===21;
        $rows[]=['aggregate',count($batch),'5 товаров; остаток 21'];
    }
}
$observations['details']=['reply_summaries'=>$batches,'raw_reply_exported'=>false,'page_ids'=>array_column($products,'_id'),'summary'=>$summary];
$checks=['reply_stdclass_despite_cursor_array_map'=>$std && is_array($products[0]),
    'actual_batches_match_screen'=>$valid && count($rows)===2,
    'successful_read_replies'=>array_column($batches,'ok')===[1,1],
    'bounded_reply_documents'=>array_column($batches,'documents')===[2,1]];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-reply-summary', ['Команда', 'Документов firstBatch', 'Что показывает экран'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-reply-summary: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-reply-summary: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

Попробуйте изменить. Уберите stock>0 только из сводки. Число итоговых документов останется 1, products станет 6, stock останется 21.

Разобрать успешную команду с writeErrors11000.

Перенести прежний getServer для страницы и отчёта на host и port

Старый обработчик использует event->getServer(), которого нет вmongodb2.x. Покажем миграцию журнала страницы и отдельного складского отчёта с сохранением адреса.

Данные и подготовка

Отдельные PHP 8.4.27 и ext-mongodb 1.20.1 выполняют find/aggregate и наблюдаемую пустую getMore. Только четыре события целевых find/aggregate используются для исторического getServer; их адреса сравниваются с прямыми getHost/getPort. Продолжение остаётся в полной трассе без устаревшего вызова.

Как получается результат
  1. 2 команды на 1.20.1
  2. 4 исторических getServer
  3. 4 явных E_DEPRECATED
  4. Тот же endpoint через современный API

Граница партии проверена реальным запуском: find с limit=2 и batchSize=2 вернул два товара, а последующая getMore — пустой nextBatch и закрытый курсор. Полная трасса показывает обе команды. Для учебной таблицы выбираются целевые find/aggregate; дополнительное продолжение строго связывается с ID курсора их ответа, проверяется пустая партия и закрытие. Неизвестная дополнительная команда не игнорируется. У aggregate с batchSize=2 итоговый документ поместился без продолжения; при batchSize=1 наблюдалась пустая getMore с operationId="0", поэтому ID курсора важнее предположения об общем operationId.

Минимальное действие

// Только mongodb 1.20.x: прежний API выдаёт E_DEPRECATED.
$server = $event->getServer();
// Современная замена, доступная уже с 1.20:
$endpoint = $event->getHost().':'.$event->getPort();

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

Что получит читатель

КомандаСобытияМиграцияОжидаемых E_DEPRECATED
findstarted + succeededадрес сохранён2
aggregatestarted + succeededадрес сохранён2

Ниже этой повторяемой таблицы фактический HTML показывает настоящие ID, адрес и длительность каждой пары. observations.json содержит те же динамические значения; их не заменяют эталонной скоростью или выдуманным requestId.

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

getServer появился в 1.3.0, устарел в 1.20.0, удалён в 2.0. Новые getHost/getPort доступны с 1.20.0. DLL старого PHP нельзя переносить в другой ABI текущего PHP.

Четыре вызова выполняются после захвата ровно двух целевых пар. Поэтому счётчик не зависит от команд setup/cleanup. Все сообщения видны в HTML/observations; оператор@ не применяется.

Временный error handler принимает только E_DEPRECATED и два точных имени методов. Иные предупреждения не скрываются, прежний обработчик восстанавливается в finally.

Если старый код использовал Server только ради адреса, замена полная. Если через него выполнялись новые запросы, это отдельное поведение: механически заменить getter недостаточно.

Границы применения

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

Полная самостоятельная программа и архив
Перенести прежний getServer для страницы и отчёта на host и portСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ct124-helper.php';
$settings=ct124Settings(true);$database=$settings['database'];
$manager=new MongoDB\Driver\Manager($settings['uri'],['serverSelectionTimeoutMS'=>1500,'connectTimeoutMS'=>1000,'socketTimeoutMS'=>5000,'retryReads'=>false],['disableClientPersistence'=>true]);
$observer=new Ct124Observer;$owned=[];$subscribed=false;
$observations=['events'=>[],'details'=>[]];
try{
$version=ct124Command($manager,$database,['buildInfo'=>1])[0]['version'];
if(version_compare($version,'8.0','<')){throw new RuntimeException('MongoDB 8.0+ required');}
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>$version,'execution'=>'database'];
$name=ct124Create($manager,$database,$owned);$namespace=$database.'.'.$name;
$manager->addSubscriber($observer);$subscribed=true;
// Выделяем бизнес-команды; реальные getMore остаются в полной трассе.
function ct124TargetPairs(Ct124Observer $observer,string $name,array &$transport): array {
    $targets=[];$open=[];$transport=[];
    foreach(ct124Pairs($observer) as [$start,$end]){
        $kind=$start->getCommandName();$command=$start->getCommand();$reply=$end->getReply();
        if(in_array($kind,['find','aggregate'],true)){
            if(($command->$kind??null)!==$name || !isset($reply->cursor)
                || !is_array($reply->cursor->firstBatch??null)){
                throw new RuntimeException('Unexpected target namespace or firstBatch');
            }
            $targets[]=[$start,$end];$cursorId=(string)$reply->cursor->id;
            if($cursorId!=='0'){$open['cursor:'.$cursorId]=['command'=>$kind,'operation_id'=>$start->getOperationId()];}
            continue;
        }
        if($kind!=='getMore'){throw new RuntimeException('Unexpected extra command in this reading lesson');}
        $cursorId=(string)($command->getMore??'');$owner=$open['cursor:'.$cursorId]??null;
        if($owner===null || ($command->collection??null)!==$name
            || !isset($reply->cursor) || !is_array($reply->cursor->nextBatch??null)
            || $reply->cursor->nextBatch!==[] || (string)$reply->cursor->id!=='0'){
            throw new RuntimeException('Extra getMore must close its observed cursor with an empty nextBatch');
        }
        // У find общий operationId наблюдается; для aggregate драйвер может дать getMore="0".
        if($owner['command']==='find' && $owner['operation_id']!==$start->getOperationId()){
            throw new RuntimeException('Unexpected operation association for find continuation');
        }
        $transport[]=['command'=>'getMore','parent_command'=>$owner['command'],'request_id'=>$start->getRequestId(),
            'operation_id'=>$start->getOperationId(),'documents'=>0,'closed_cursor'=>true];
        unset($open['cursor:'.$cursorId]);
    }
    if($open!==[]){throw new RuntimeException('Reading lesson left an observed server cursor open');}
    return $targets;
}
$transport=[];


$cursor=ct124Page($manager,$namespace);$products=$cursor->toArray();$summary=ct124Summary($manager,$database,$name);
$pairs=ct124TargetPairs($observer,$name,$transport);
if(count($pairs)!==2 || array_map(fn($pair)=>$pair[0]->getCommandName(),$pairs)!==['find','aggregate']){
    throw new RuntimeException('Legacy demonstration requires exactly the two target commands');
}
$allowedMessages=[
    'Method MongoDB\\Driver\\Monitoring\\CommandStartedEvent::getServer() is deprecated',
    'Method MongoDB\\Driver\\Monitoring\\CommandSucceededEvent::getServer() is deprecated'];
$warnings=[];$addresses=[];$same=true;
set_error_handler(function(int $severity,string $message)use(&$warnings,$allowedMessages):bool{
    if($severity===E_DEPRECATED && in_array($message,$allowedMessages,true)){$warnings[]=$message;return true;}
    return false;
});
try{
    foreach($pairs as [$start,$end]){
        foreach([[$start,'started'],[$end,'succeeded']] as [$event,$phase]){
            $server=$event->getServer();
            $old=$server->getHost().':'.$server->getPort();$new=$event->getHost().':'.$event->getPort();
            $same=$same && $old===$new;
            $addresses[]=['command'=>$event->getCommandName(),'phase'=>$phase,'old_endpoint'=>$old,'modern_endpoint'=>$new];
        }
    }
}finally{restore_error_handler();}
$counts=array_count_values($warnings);
$observations['details']=['migration_addresses'=>$addresses,'deprecations'=>$warnings,'page_ids'=>array_column($products,'_id'),'summary'=>$summary];
$rows=[['find','started + succeeded','адрес сохранён',2],['aggregate','started + succeeded','адрес сохранён',2]];
$checks=['two_real_read_results'=>array_column($products,'_id')===[107,106] && $summary===[['products'=>5,'stock'=>21]],
    'old_modern_endpoint_equal'=>$same && count($addresses)===4,
    'exact_expected_deprecations'=>count($warnings)===4 && ($counts[$allowedMessages[0]]??0)===2 && ($counts[$allowedMessages[1]]??0)===2];

$observations['details']['transport_continuations']=$transport;
$observations['details']['all_command_pairs']=count($observer->succeeded);
    $observations['events']=ct124Trace($observer);
    $checks['actual_pairs_and_types']=ct124TraceChecks($observations['events'],$settings);
}finally{
    if($subscribed){$manager->removeSubscriber($observer);}
    unset($cursor);
    foreach(array_reverse($owned) as $ownName){ct124Command($manager,$database,['drop'=>$ownName],true);}
}
$removed=true;foreach($owned as $ownName){if(ct124Exists($manager,$database,$ownName)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
ct124Report('mongodb124-trace-server-legacy', ['Команда', 'События', 'Миграция', 'Ожидаемых E_DEPRECATED'], $rows, $checks, $runtime, $observations);
echo 'mongodb124-trace-server-legacy: saved result.html and result.json
';
Ожидаемый результат
mongodb124-trace-server-legacy: saved result.html and result.json

Настоящие команды локального MongoDB и callbacks драйвера; HTML/JSON показывают данные и наблюдения. Helper, полная инструкция среды и проверка удаления своей коллекции включены.

Открыть фактический результат и события · JSON результата · JSON реальной трассы

Попробуйте изменить. Замените старые getServer()->getHost()/getPort() прямыми методами события. В текущей среде уберите legacy handler и ожидание предупреждений, сохранив проверку адреса и данных.

Все свойства started/succeeded и история getServer

Readonly property с 2.3.0ТипГде / getter
hoststringоба события / getHost
portintоба / getPort
commandNamestringоба / getCommandName
databaseNamestringоба / getDatabaseName
operationIdstringоба / getOperationId
requestIdstringоба / getRequestId
serverConnectionId?intоба / getServerConnectionId
serviceId?MongoDB\BSON\ObjectIdоба / getServiceId
commandobjectтолько started / getCommand
replyobjectтолько succeeded / getReply
durationint, микросекундытолько succeeded / getDurationMicros

События final, их создаёт драйвер. Свойства доступны только для чтения с версии расширения 2.3.0. Имя duration важно: оно отличается от getter getDurationMicros. Обе формы API чтения существуют в текущей ветке; дополнительные прикладные подписи хранятся в собственном массиве.

Методы getServer — отдельный исторический раздел обеих официальных страниц: с 1.3.0, deprecated 1.20.0, removed 2.0. Они не превращаются в readonly property server. Перенос адреса показан в настоящем legacy-архиве с явными предупреждениями.

Справочник:10 методов started,11 методов succeeded и 2callback

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

МетодДля чего нужен
MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandПрочитать BSON-документ действительно отправленной команды
MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandNameОпределить вид отправляемого действия
MongoDB\Driver\Monitoring\CommandStartedEvent::getDatabaseNameПривязать отправленную команду к базе
MongoDB\Driver\Monitoring\CommandStartedEvent::getHostПолучить адрес сервера отправленной команды
MongoDB\Driver\Monitoring\CommandStartedEvent::getOperationIdСгруппировать команды одной операции драйвера
MongoDB\Driver\Monitoring\CommandStartedEvent::getPortПолучить порт сервера отправляемой команды
MongoDB\Driver\Monitoring\CommandStartedEvent::getRequestIdСоздать ключ ожидания ответа конкретной команды
MongoDB\Driver\Monitoring\CommandStartedEvent::getServerИсторически получить Server начала команды
MongoDB\Driver\Monitoring\CommandStartedEvent::getServerConnectionIdСвязать отправку с идентификатором соединения сервера
MongoDB\Driver\Monitoring\CommandStartedEvent::getServiceIdПрочитать идентификатор сервиса при load-balanced подключении
MongoDB\Driver\Monitoring\CommandSucceededEvent::getCommandNameУзнать вид команды, на которую получен успешный ответ
MongoDB\Driver\Monitoring\CommandSucceededEvent::getDatabaseNameПолучить базу завершённой команды
MongoDB\Driver\Monitoring\CommandSucceededEvent::getDurationMicrosИзмерить отправку команды и получение ответа в микросекундах
MongoDB\Driver\Monitoring\CommandSucceededEvent::getHostПрочитать адрес сервера завершённой команды
MongoDB\Driver\Monitoring\CommandSucceededEvent::getOperationIdСвязать завершённую команду с группой действий драйвера
MongoDB\Driver\Monitoring\CommandSucceededEvent::getPortПрочитать порт сервера ответа
MongoDB\Driver\Monitoring\CommandSucceededEvent::getReplyПрочитать документ ответа для осмысленного результата
MongoDB\Driver\Monitoring\CommandSucceededEvent::getRequestIdНайти начало конкретного успешного ответа
MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerИсторически получить Server успешной команды
MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerConnectionIdПрочитать server connectionId для завершённой команды
MongoDB\Driver\Monitoring\CommandSucceededEvent::getServiceIdПолучить serviceId успешной команды в load-balanced режиме
MongoDB\Driver\Monitoring\CommandSubscriber::commandStartedПолучить уведомление об отправляемой команде
MongoDB\Driver\Monitoring\CommandSubscriber::commandSucceededПолучить уведомление о полученном успешном ответе команды

Перейти от наблюдения к следующему действию

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

Следующие связи: отказы команд, writeErrors и безопасное исправление; партии и жизненный цикл Cursor; индексы под выборку каталога; описание сервера и RTT; подписки и события топологии.

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

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

MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandПрочитать BSON-документ действительно отправленной команды

mongodb 1.3.0 · mongodb

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getCommand(): object

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

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getCommand(): object
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратВозвращает stdClass по стандартным правилам BSON-декодирования: вложенные документы — объекты, BSON-массивы — PHP-массивы. Это документ команды, несмотря на опечатку reply в вводной фразе официальной страницы. typeMap Cursor его не меняет.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property command появилась в 2.3.0; getter остаётся доступен.

Форма зависит от команды. Для find читаем filter/projection/sort/limit, для getMore — иной документ. Содержимое может включать личные значения; изменение объекта не правит уже отправленную команду. Системная redaction охватывает определённые команды аутентификации, не весь пользовательский контент.

В текущей ветке то же поле доступно через $event->command. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Проверить причину неверного состава страницы

Сверяем реально отправленные stock>0, три поля и limit=2 с двумя товарами. Числовой limit в диагностике превращается в десятичную строку: наблюдение не зависит от представления BSON Int64 как объекта или native int и не теряет точность через float.

$command = $event->getCommand();
$limitForLog = (string) $command->limit; // '2'

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Проверить, что личное условие не попало в публичный журнал

Контакт действительно присутствует в command.filter, но файл содержит только allowlist метаданных. Так проверяется отбор данных на реальном чувствительном условии.

$hasContact = isset($event->getCommand()->filter->contact);
$safe = ['command'=>$event->getCommandName()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandNameОпределить вид отправляемого действия

mongodb 1.3.0 · mongodb

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

Определить вид отправляемого действия

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandName(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратВозвращает имя команды, например find, aggregate или getMore. Это не имя PHP-метода и не имя коллекции.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property commandName появилась в 2.3.0; getter остаётся доступен.

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

В текущей ветке то же поле доступно через $event->commandName. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Развести страницу и складской отчёт

Два начала имеют имена find и aggregate. В итоговой таблице видно, какой ответ наполнил карточки, а какой — сводку.

$kind = $event->getCommandName(); // find или aggregate

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Узнать продолжение уже открытого курсора

Считаем настоящие getMore отдельно от первого find; число сетевых продолжений не выдаём за число поисков пользователя.

$isContinuation = $event->getCommandName() === 'getMore';

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getDatabaseNameПривязать отправленную команду к базе

mongodb 1.3.0 · mongodb

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

Привязать отправленную команду к базе

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getDatabaseName(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратВозвращает имя базы, на которой выполняется команда. Это отдельное поле события, не database.collection и не PHP namespace.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property databaseName появилась в 2.3.0; getter остаётся доступен.

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

В текущей ветке то же поле доступно через $event->databaseName. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Убедиться, что два элемента экрана читают учебную базу

Начало find и aggregate сравниваются с явно выбранной базой; endpoint и database показываются вместе.

$sameDatabase = $event->getDatabaseName() === $database;

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Оставить базовый контекст без URI

В allowlist сохраняем только название разрешённой учебной базы, не строку подключения с настройками и credentials.

$safe = ['database'=>$event->getDatabaseName(), 'command'=>$event->getCommandName()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getHostПолучить адрес сервера отправленной команды

mongodb 1.20.0 · mongodb

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

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

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getHost(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратСтрока имени хоста или адреса сервера, которому направлена эта команда. Для порта предусмотрен отдельный getPort().
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.20.0. Readonly property host появилась в 2.3.0; getter остаётся доступен.

Не идентификатор кластера и не доказательство будущей доступности. Он описывает данное событие; URI приложения может содержать несколько исходных адресов.

В текущей ветке то же поле доступно через $event->host. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Записать, куда ушли страница и отчёт

Сравниваем host начала и завершения каждой пары и показываем фактический endpoint.

$host = $event->getHost();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Заменить получение адреса через устаревший Server

У реального find на 1.20.1 прямой getHost совпадает с getServer()->getHost; тот же современный вызов доступен в 2.x.

$host = $event->getHost(); // вместо getServer()->getHost()

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getOperationIdСгруппировать команды одной операции драйвера

mongodb 1.3.0 · mongodb

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

Сгруппировать команды одной операции драйвера

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getOperationId(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратСтроковый ID, сформированный расширением для связи команд, например частей пакетной записи. Несколько команд могут иметь одно значение; requestId сопоставляет отдельное начало и ответ.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property operationId появилась в 2.3.0; getter остаётся доступен.

Не обещаны глобальная уникальность, ненулевое значение или связь с ID заказа. Не переводите в PHP int/float. Для пары используйте getRequestId().

В текущей ветке то же поле доступно через $event->operationId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Показать историю чтения пяти товаров

У наблюдаемого find и его getMore сохраняем общий operationId вместе с разными requestId. Видно, что транспортных команд несколько, а чтение связано.

$operation = $event->getOperationId();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Проверить согласованность начала и ответа экрана

После выбора pending по requestId сверяем operationId как дополнительный признак, не перезаписываем pending общей операцией.

$sameOperation = $start->getOperationId() === $end->getOperationId();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getPortПолучить порт сервера отправляемой команды

mongodb 1.20.0 · mongodb

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

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

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getPort(): int
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратЦелое значение порта сервера команды. Пара host/port задаёт наблюдаемый адрес.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.20.0. Readonly property port появилась в 2.3.0; getter остаётся доступен.

Порт не номер запроса и не connectionId. Не зашивайте порт из примера в рабочую диагностику: берите фактическое значение.

В текущей ветке то же поле доступно через $event->port. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Отличить локальные лаборатории по порту

Контракт принимает выбранный явный loopback-порт и проверяет его в событиях двух команд.

$endpoint = $event->getHost().':'.$event->getPort();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Перенести складской журнал со старого API

Для начала aggregate прямой порт совпадает с портом старого Server; сохраняется адрес без вызова удалённого метода в будущем.

$port = $event->getPort(); // современная замена

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getRequestIdСоздать ключ ожидания ответа конкретной команды

mongodb 1.3.0 · mongodb

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

Создать ключ ожидания ответа конкретной команды

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getRequestId(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратСтроковый ID запроса расширения. Позволяет связать это начало с последующим CommandSucceededEvent или CommandFailedEvent.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property requestId появилась в 2.3.0; getter остаётся доступен.

Ограничивайте пространство корреляции клиентом/захватом; ID не ключ заказа и не постоянный UUID. Совпадение имени команды не заменяет корреляцию.

В текущей ветке то же поле доступно через $event->requestId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Сопоставить ответ сводки с правильным началом

Ключ request: сохраняет строковую природу ID и не зависит от порядка названий find/aggregate.

$pending['request:'.$event->getRequestId()] = $event;

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Оставить связь журналов без текста фильтра

Две безопасные записи содержат requestId, поэтому их можно сопоставить даже после полного исключения command/filter.

$safe = ['phase'=>'started', 'request_id'=>$event->getRequestId()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getServerИсторически получить Server начала команды

mongodb 1.3.0 · mongodb

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

Исторически получить Server начала команды

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getServer(): MongoDB\Driver\Server
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратНа поддерживающей ветке возвращает MongoDB\Driver\Server сервера, на котором выполняется команда. Параметров выбора или изменения сервера у getter нет.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. С 1.20.0 устарел, в 2.0 удалён. Здесь исполняется только на изолированной ветке 1.20.1; современные замены — getHost() и getPort().

Только историческая карточка. Deprecated с 1.20.0, удалён в 2.0. Для адреса достаточно getHost/getPort. Пример явно фиксирует предупреждения; @ не применяется.

Применение 1: Мигрировать журнал отправки страницы

У started команды find извлекаем старый Server, сравниваем его адрес с прямыми getters и сохраняем ожидаемое предупреждение.

$server = $started->getServer(); // только mongodb 1.20.x
$endpoint = $started->getHost().':'.$started->getPort();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Мигрировать журнал отправки отчёта

Отдельный started aggregate проходит то же сравнение; это другое обращение и форма команды, а не копия результата find.

// $started относится к aggregate из второй пары.
$oldPort = $started->getServer()->getPort();
$newPort = $started->getPort();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getServerConnectionIdСвязать отправку с идентификатором соединения сервера

mongodb 1.14.0 · mongodb

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

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

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getServerConnectionId(): ?int
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратВозвращает серверный connectionId из ответа hello, либо NULL, если значение недоступно. Поле поддерживается сервером MongoDB 4.2 и новее.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.14.0. Readonly property serverConnectionId появилась в 2.3.0; getter остаётся доступен.

Это не requestId, operationId или число подключений. Не заменяйте NULL нулём; не обещайте постоянство соединения при следующих действиях.

В текущей ветке то же поле доступно через $event->serverConnectionId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Добавить контекст двум запросам экрана

Вместе с адресом сохраняем ID hello для find и aggregate; на фактическом сервере 8.0 он известен.

$connection = $event->getServerConnectionId(); // int или NULL

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Сопоставить продолжения с их транспортным контекстом

У каждой команды чтения виден serverConnectionId. Таблица позволяет изучать фактический путь, не подменяя его курсорным ID.

$trace = ['request_id'=>$event->getRequestId(), 'server_connection_id'=>$event->getServerConnectionId()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandStartedEvent::getServiceIdПрочитать идентификатор сервиса при load-balanced подключении

mongodb 1.11.0 · mongodb

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

Прочитать идентификатор сервиса при load-balanced подключении

final public function MongoDB\Driver\Monitoring\CommandStartedEvent::getServiceId(): ?MongoDB\BSON\ObjectId
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратObjectId из поля serviceId ответа hello при подключении через балансировщик. Возвращает NULL, если драйвер не находится в load-balanced режиме.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.11.0. Readonly property serviceId появилась в 2.3.0; getter остаётся доступен.

NULL здесь нормально. Это не _id товара и не постоянное имя кластера. Наша лаборатория проверяет ветвь NULL; настоящую топологию балансировщика не имитирует.

В текущей ветке то же поле доступно через $event->serviceId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Честно подписать standalone-снимок

Для обоих начал показываем NULL: не load-balanced, а не выдуманный сервис или сообщение о сломанном соединении.

$service = $event->getServiceId();
$label = $service === null ? 'не load-balanced' : (string) $service;

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Сохранить nullable техническое поле без падения журнала

Allowlist сериализует NULL как NULL, а ObjectId — строкой; журнал остаётся типизированным независимо от топологии.

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

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getCommandNameУзнать вид команды, на которую получен успешный ответ

mongodb 1.3.0 · mongodb

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

Узнать вид команды, на которую получен успешный ответ

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getCommandName(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратВозвращает имя завершённой команды, например find, aggregate или getMore.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property commandName появилась в 2.3.0; getter остаётся доступен.

Имя не идентификатор пары и не признак успешности каждой записи внутри команды. Для результата записи отдельно исследуются writeErrors и счётчики.

В текущей ветке то же поле доступно через $event->commandName. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Выбрать понятное описание результата

У find подпись говорит о двух товарах, у aggregate — об одном итоговом документе с пятью позициями.

$kind = $event->getCommandName();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Различить ответ первой и следующих партий

В таблице завершения разделены find и getMore: их reply содержат firstBatch и nextBatch соответственно.

$isNextBatch = $event->getCommandName() === 'getMore';

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getDatabaseNameПолучить базу завершённой команды

mongodb 1.19.0 · mongodb

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

Получить базу завершённой команды

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getDatabaseName(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратВозвращает имя базы, на которой команда была выполнена. Этот getter у succeeded появился позже одноимённого метода started: с 1.19.0.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.19.0. Readonly property databaseName появилась в 2.3.0; getter остаётся доступен.

Не извлекает имя коллекции и не проверяет права пользователя. Для точной пары сверяйте requestId и контекст начала.

В текущей ветке то же поле доступно через $event->databaseName. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

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

В конце aggregate имя базы совпадает с началом и с явно выбранной учебной базой.

$sameDatabase = $end->getDatabaseName() === $start->getDatabaseName();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Оставить контекст безопасной записи завершения

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

$safe = ['database'=>$event->getDatabaseName(), 'duration_us'=>$event->getDurationMicros()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getDurationMicrosИзмерить отправку команды и получение ответа в микросекундах

mongodb 1.3.0 · mongodb

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

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

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getDurationMicros(): int
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратЦелая длительность в микросекундах. Вычисление включает отправку сообщения и получение ответа от сервера; это не только серверное выполнение запроса.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property duration появилась в 2.3.0; getter остаётся доступен.

Не путайте с RTT hello в миллисекундах, календарным временем или полным временем экрана. Нельзя объявлять задержку фиксированной по одному локальному запуску.

В текущей ветке то же поле доступно через $event->duration. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Поставить диагностическую метку превышения бюджета

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

$us = $event->getDurationMicros();
$overBudget = $us > 10000;

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Увидеть цену отдельных продолжений курсора

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

$batchDurationUs = $event->getDurationMicros();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getHostПрочитать адрес сервера завершённой команды

mongodb 1.20.0 · mongodb

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

Прочитать адрес сервера завершённой команды

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getHost(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратСтрока хоста сервера, на котором выполнена команда. Для порта используется getPort().
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.20.0. Readonly property host появилась в 2.3.0; getter остаётся доступен.

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

В текущей ветке то же поле доступно через $event->host. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Подтвердить адрес ответа страницы

Сравниваем host/port завершения с началом find и ожидаемым локальным endpoint.

$sameHost = $end->getHost() === $start->getHost();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Перенести адрес ответа отчёта без Server

У succeeded aggregate прямой host равен хосту исторического Server, после перехода на 2.x дополнительный объект не нужен.

$host = $event->getHost(); // вместо getServer()->getHost()

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getOperationIdСвязать завершённую команду с группой действий драйвера

mongodb 1.3.0 · mongodb

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

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

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getOperationId(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратСтроковый ID операции расширения для связанных команд, в том числе составных пакетных операций. Для отдельной пары событий предусмотрен requestId.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property operationId появилась в 2.3.0; getter остаётся доступен.

Повтор одного operationId нормален. Значение "0" допустимо; оно не ключ бизнес-операции или идемпотентности.

В текущей ветке то же поле доступно через $event->operationId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Проверить пару ответа страницы и начала

После lookup по requestId сопоставляем operationId, чтобы не смешать разные записи ограниченного захвата.

$sameOperation = $end->getOperationId() === $start->getOperationId();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Собрать последовательность завершённых партий

operationId объединяет наблюдаемые части чтения, а requestId и duration остаются у каждой отдельной команды.

$group = $event->getOperationId();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getPortПрочитать порт сервера ответа

mongodb 1.20.0 · mongodb

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

Прочитать порт сервера ответа

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getPort(): int
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратЦелый порт сервера, на котором выполнена команда. Не зависит от имени PHP-метода.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.20.0. Readonly property port появилась в 2.3.0; getter остаётся доступен.

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

В текущей ветке то же поле доступно через $event->port. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Сравнить адрес начала и ответа двух компонентов

find и aggregate проверяются вместе с host и database; журнал показывает настоящий выбранный порт.

$endpoint = $event->getHost().':'.$event->getPort();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Убрать устаревший Server из журнала ответа страницы

Для succeeded find новый getPort сохраняет значение старого Server без обращения к удалённому API в текущей ветке.

$port = $event->getPort();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getReplyПрочитать документ ответа для осмысленного результата

mongodb 1.3.0 · mongodb

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

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

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getReply(): object
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратВозвращает stdClass документа ответа по стандартным правилам BSON-декодирования. Это не автоматически список всех найденных документов; форма зависит от команды.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property reply появилась в 2.3.0; getter остаётся доступен.

typeMap Cursor не меняет getReply. Ответ может содержать личные поля, а CommandSucceeded записи — writeErrors. Для find/aggregate firstBatch и nextBatch относятся к одной партии.

В текущей ветке то же поле доступно через $event->reply. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Понять, почему один ответ описывает пять товаров

Читаем products/stock внутри единственного документа firstBatch aggregate и сравниваем с данными приложения.

$reply = $event->getReply();
$batch = $reply->cursor->firstBatch ?? [];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Проверить, что партии не потеряли строки

Суммируем реальные размеры firstBatch/nextBatch, включая возможную пустую последнюю партию, и получаем пять товаров.

$reply = $event->getReply();
$batch = $reply->cursor->firstBatch ?? $reply->cursor->nextBatch ?? [];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Связанный случай: CommandSucceeded с writeErrors11000 и BulkWriteException.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getRequestIdНайти начало конкретного успешного ответа

mongodb 1.3.0 · mongodb

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

Найти начало конкретного успешного ответа

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getRequestId(): string
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратСтроковый ID запроса расширения, позволяющий связать это завершение с предыдущим CommandStartedEvent.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. Readonly property requestId появилась в 2.3.0; getter остаётся доступен.

Не используйте одинаковое имя команды или общий operationId вместо пары requestId. Сохранение строки избегает преобразования больших идентификаторов в float.

В текущей ветке то же поле доступно через $event->requestId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Закрыть именно соответствующую запись pending

Ответ aggregate извлекает своё начало по request:ID, даже если рядом есть другой запрос экрана.

$start = $pending['request:'.$event->getRequestId()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Связать завершение с началом без полезной нагрузки

Безопасная запись сохраняет requestId и duration; сам reply в общий журнал не попадает.

$safe = ['request_id'=>$event->getRequestId(), 'duration_us'=>$event->getDurationMicros()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerИсторически получить Server успешной команды

mongodb 1.3.0 · mongodb

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

Исторически получить Server успешной команды

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getServer(): MongoDB\Driver\Server
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратНа поддерживающей версии возвращает MongoDB\Driver\Server сервера, на котором была выполнена команда.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.3.0. С 1.20.0 устарел, в 2.0 удалён. Здесь исполняется только на изолированной ветке 1.20.1; современные замены — getHost() и getPort().

Deprecated с 1.20.0, удалён в 2.0. Показан изолированно для миграции; новые программы читают host/port прямо у события.

Применение 1: Перенести журнал успешного ответа страницы

На 1.20.1 Server succeeded find и новые getters дают один endpoint. E_DEPRECATED сохранён как ожидаемое наблюдение.

$old = $succeeded->getServer(); // только ветка 1.20.x
$newHost = $succeeded->getHost();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Перенести журнал завершения агрегированного отчёта

У второго ответа aggregate проверяется тот же контракт независимо от формы его содержимого.

// $succeeded — завершение aggregate второй пары.
$oldPort = $succeeded->getServer()->getPort();
$newPort = $succeeded->getPort();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerConnectionIdПрочитать server connectionId для завершённой команды

mongodb 1.14.0 · mongodb

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

Прочитать server connectionId для завершённой команды

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerConnectionId(): ?int
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратЦелый connectionId из hello сервера, либо NULL при недоступном значении. Поддержка поля на стороне MongoDB — с 4.2.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.14.0. Readonly property serverConnectionId появилась в 2.3.0; getter остаётся доступен.

Не совпадает по назначению с requestId или cursor.id. Не объявляйте NULL ошибкой выполнения команды.

В текущей ветке то же поле доступно через $event->serverConnectionId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Дополнить сведения об источнике ответа

Журнал страницы и сводки включает фактический serverConnectionId вместе с адресом, без строки URI.

$connection = $event->getServerConnectionId();

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Изучить завершения транспортных партий

В реальной трассе каждое succeeded getMore содержит свой контекст соединения; его не подменяем номером партии.

$diagnostic = ['command'=>$event->getCommandName(), 'server_connection_id'=>$event->getServerConnectionId()];

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSucceededEvent::getServiceIdПолучить serviceId успешной команды в load-balanced режиме

mongodb 1.11.0 · mongodb

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

Получить serviceId успешной команды в load-balanced режиме

final public function MongoDB\Driver\Monitoring\CommandSucceededEvent::getServiceId(): ?MongoDB\BSON\ObjectId
Часть контрактаОбъяснение
ПараметрыПараметров нет. Метод читается у настоящего объекта события, переданного драйвером подписчику.
ВозвратObjectId из hello.serviceId при подключении через балансировщик; NULL, если драйвер не использует load-balanced режим.
ОшибкиОфициальная страница указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Отказ самой команды — другое событие и исключение вызывающего кода; getter не повторяет запрос.
ВерсииДоступен с ext-mongodb 1.11.0. Readonly property serviceId появилась в 2.3.0; getter остаётся доступен.

Поле не _id результата и не ID операции. В этой standalone-лаборатории наблюдается NULL, не имитируется балансировщик.

В текущей ветке то же поле доступно через $event->serviceId. Полное сопоставление и реальные ошибки изменения — в опыте readonly properties.

Применение 1: Показать корректное отсутствие serviceId

Оба успешных ответа подписаны как не load-balanced. NULL не заменяется фиктивным ObjectId.

$notLoadBalanced = $event->getServiceId() === null;

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Сохранить типизированную запись успешного ответа

Allowlist содержит nullable строковое представление ObjectId и не требует создавать новый идентификатор при NULL.

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

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSubscriber::commandStartedПолучить уведомление об отправляемой команде

mongodb 1.3.0 · mongodb

abstract public function MongoDB\Driver\Monitoring\CommandSubscriber::commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event): void

Получить уведомление об отправляемой команде

abstract public function MongoDB\Driver\Monitoring\CommandSubscriber::commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event): void
Часть контрактаОбъяснение
Параметры$event: MongoDB\Driver\Monitoring\CommandStartedEvent — объект сведений о данном событии, переданный драйвером зарегистрированному подписчику.
Возвратvoid: значение не возвращается. Этот метод реализует ваш класс; драйвер вызывает его при отправке команды.
ОшибкиОфициальная страница перечисляет InvalidArgumentException при ошибке разбора аргументов. В пользовательской реализации возможны собственные ошибки; не превращайте callback в механизм сетевых повторов и не прячьте сбои основного запроса.
ВерсииИнтерфейс с 1.3.0. Начиная с 1.15 для PHP 8 объявлен tentative void; в 2.0 void стал обязательным. В примерах явно указаны тип события и : void.

Нужно реализовать все три метода интерфейса, включая commandFailed. Перед I/O зарегистрируйте observer через Manager::addSubscriber или глобальную функцию; снимите его в finally. Не вызывайте callback вручную ради примера, не выполняйте запросы из callback. Started не означает успех.

Применение 1: Проверить реально отправленный запрос страницы

Регистрация делается после подготовки коллекции и до find. Callback получает созданное драйвером событие; его command сравнивается с намерением приложения.

public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event): void {
    $this->started[] = $event;
}

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Начать безопасную диагностическую пару

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

public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event): void {
    if (count($this->started) < 48) { $this->started[] = $event; }
}

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

MongoDB\Driver\Monitoring\CommandSubscriber::commandSucceededПолучить уведомление о полученном успешном ответе команды

mongodb 1.3.0 · mongodb

abstract public function MongoDB\Driver\Monitoring\CommandSubscriber::commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void

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

abstract public function MongoDB\Driver\Monitoring\CommandSubscriber::commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void
Часть контрактаОбъяснение
Параметры$event: MongoDB\Driver\Monitoring\CommandSucceededEvent — объект сведений о данном событии, переданный драйвером зарегистрированному подписчику.
Возвратvoid: значение не возвращается. Этот метод реализует ваш класс; драйвер вызывает его при успешном завершении команды.
ОшибкиОфициальная страница перечисляет InvalidArgumentException при ошибке разбора аргументов. В пользовательской реализации возможны собственные ошибки; не превращайте callback в механизм сетевых повторов и не прячьте сбои основного запроса.
ВерсииИнтерфейс с 1.3.0. Начиная с 1.15 для PHP 8 объявлен tentative void; в 2.0 void стал обязательным. В примерах явно указаны тип события и : void.

Нужно реализовать все три метода интерфейса, включая commandFailed. Перед I/O зарегистрируйте observer через Manager::addSubscriber или глобальную функцию; снимите его в finally. Не вызывайте callback вручную ради примера, не выполняйте запросы из callback. Успешный ответ команды записи может содержать writeErrors; это не гарантирует успех каждой операции.

Применение 1: Проверить смысл ответа страницы и сводки

Callback сохраняет фактический reply. После завершения вызова код различает документы товара и агрегированную строку.

public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void {
    $this->succeeded[] = $event;
}

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Применение 2: Получить время команды для диагностического бюджета

Длительность читается из реального succeeded, затем сравнивается с порогом без остановки команды и без выдуманного benchmark.

public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void {
    $this->durations[] = $event->getDurationMicros();
}

Фрагмент читается в контексте настоящего callback или сохранённого события. Все переменные и подготовка включены в полный самостоятельный пример с результатом.

Связанный случай: CommandSucceeded с writeErrors11000 и BulkWriteException.

Методы события не открывают новое соединение и не повторяют команду. Для понимания исходной операции: Query и чтение документов; партии курсора.

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

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

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

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

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