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

Дерево руководства
Глава 172 · После первого запроса к MongoDB
MongoDB: сессия заказа и логические часы между обработчиками
От вызова startSession до зависимого чтения в другом обработчике. На собственных записях разбираем lsid, две логические отметки, закрепление сервера и сетевой признак.
От подтверждения резерва к чтению карточки
Оператор подтвердил резерв. Упаковщик должен увидеть заказ как минимум в состоянии после этого действия. Начнём с одной сессии и заказа A-17. Затем передадим работу другому обработчику и увидим, какое условие драйвер действительно отправил базе при чтении.
- Клиент открывает сессию обработки A-17
- Сессия сопровождает запись «готов»
- MongoDB отвечает, драйвер запоминает логическое время
- Клиент читает A-17 в этой же сессии и выводит статус
Сессию удобно представить как лист маршрута. У него есть служебный номер lsid, а после операций появляются отметки о пройденных шагах. Данные заказа хранятся в коллекции. Сессия хранит сведения о последовательности действий клиента.
$session = $manager->startSession();
$time = $session->getOperationTime();
var_dump($time); // NULL у новой сессииЗдесь $manager — клиент драйвера MongoDB. Знак = присваивает значение переменной. Стрелка -> вызывает метод у объекта, пустые скобки означают, что в этот раз ему ничего не передают. null обозначает отсутствие значения. Как получить Manager, разобрано в первом подключении к каталогу.
О SQL-транзакциях и сессиях браузера можно знать заранее или вовсе с ними не работать. Слово Session здесь относится к клиенту MongoDB. Этот объект создаёт Manager::startSession. Когда работа завершена, доступен Session::endSession.
Как различать номер заказа, сессию и два вида времени
| Значение | Что содержит | Где понадобится |
|---|---|---|
| _id заказа | Например K-71 | Найти документ после завершения сессии |
| lsid | Идентификатор сессии в документе | Соотнести серверные команды одного клиента |
| operationTime | BSON Timestamp | Передать нижнюю границу после известной операции |
| clusterTime | Серверный документ с логической отметкой и служебными полями | Передать знание о времени кластера |
| state заказа | Например «зарезервирован» | Показать человеку после отдельного чтения |
Логическая отметка использует секунды и приращение. Пара различает изменения в пределах секунды и позволяет сравнить порядок. Для даты доставки на экране нужен отдельный календарный тип. Как устроен BSON Timestamp.
В причинно согласованной сессии подтверждённые majority-записи и majority-чтения дают четыре гарантии порядка. Читатель видит собственные предшествующие записи. Следующее чтение не перемещается в более раннее состояние. Записи выполняются в зависимом порядке, а запись после чтения учитывает состояние, которое уже прочитано. Другие клиенты по-прежнему могут изменить документ между шагами.
$query = new MongoDB\Driver\Query(['_id' => 'K-71'], [
'projection' => ['_id' => 0, 'state' => 1],
'readConcern' => new MongoDB\Driver\ReadConcern('majority'),
]);
$cursor = $manager->executeQuery($ns, $query, ['session' => $packing]);ReadConcern в этом API передаётся в конструктор Query. Опция session входит в вызов executeQuery. В главе о настройках чтения и записи есть вводные примеры подтверждения. В сценарии передачи резерва мы посмотрим фактические поля команды find.
Запуск и чтение отчёта
Восемь архивов рассчитаны на отдельную одноузловую реплику phpmanRs, MongoDB 8.0, PHP CLI 8.4+ x64 и расширение mongodb 2.5+. Рабочая конфигурация проекта — PHP 8.5.11, драйвер 2.5.5 и MongoDB 8.0.34. В каждом ZIP лежат example.php, ms130-helper.php и инструкция по созданию своей учебной базы.
php --ri mongodb
$env:PHPMAN_MONGODB_URI='mongodb://127.0.0.1:27076/?replicaSet=phpmanRs&retryWrites=false'
$env:PHPMAN_MONGODB_DATABASE='php_manual_txn_lab'
php example.phpОткройте result.html. result.json содержит ту же таблицу, а metrics.json — проверки. Каждому запуску принадлежит отдельная случайно именованная коллекция. Программа удаляет её в finally и проверяет отсутствие. На стенде большинство голосующих узлов составляет один узел. Для наблюдения отставания реплики или смены primary понадобилась бы другая топология.
Сохранить заказ и прочитать его в одной сессии
Покупатель нажал «Оформить» и сразу открыл карточку своего заказа. Программа отправит запись и последующее чтение в одной сессии. Результатом станет статус «готов» для A-17.
Входные данные
Сессия хранит сведения о порядке операций одного клиента. Переменная $manager содержит подключение драйвера. Стрелка вызывает его метод, круглые скобки передают настройки. Пустая сессия ещё не получала ответа сервера с временем своей операции.
- Создать сессию для обработки A-17
- Записать заказ со статусом «готов» и дождаться majority
- Прочитать поле state с той же сессией
- Показать «готов» и завершить работу сессии
Вызов
$session = $manager->startSession(['causalConsistency' => true]);
$initial = $session->getOperationTime(); // null
Результат
| Что проверили | Результат |
|---|---|
| Начальная метка | NULL |
| Добавлено | 1 |
| Статус заказа | готов |
| Получена метка операции | true |
Почему получился такой результат
Оба обращения получили параметр session с одним объектом. Менеджер сам создаёт Session через startSession. В полном файле addEvent собирает BulkWrite, а readEvent отправляет Query с проекцией state. В запросе признак majority задаёт уровень чтения, в записи — требуемое подтверждение.
Границы применения
Сессия сама по себе не объединяет операции в транзакцию. Между вашей записью и чтением другой клиент может снова изменить документ.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$session = $manager->startSession(['causalConsistency' => true]);
$initial = $session->getOperationTime();
$before = $session->getLogicalSessionId();
$count = addEvent($manager, $ns, $session, 'A-17', 'готов');
$state = readEvent($manager, $ns, $session, 'A-17');
$after = $session->getLogicalSessionId();
$timed = $session->getOperationTime() instanceof Timestamp;
$checks = [
'new_clock_empty' => $initial === null,
'session_created' => $session instanceof Session,
'inserted_one' => $count === 1,
'saved_state' => $state === 'готов',
'same_id' => $before->id == $after->id,
'clock_observed' => $timed,
];
$details = ['order' => 'A-17', 'writeConcern' => 'majority', 'readConcern' => 'majority'];
$session->endSession();
return [['Начальная метка', $initial], ['Добавлено', $count], ['Статус заказа', $state], ['Получена метка операции', $timed]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-first-order', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-first-order: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-first-order: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Замените статус на «упакован» в исходных данных и ожидаемой строке проверки. Количество созданных заказов останется равным одному.
Автор указал majority и ожидал результата предыдущей записи при чтении с другого узла. В вопросе упущена связь операций с одной сессией. Read own writes in a MongoDB replicaset на Stack Overflow.
Различить две заявки и две одновременно открытые сессии
В кассе и мобильном приложении принимают разные заказы. Для журнала нужно отличить обращения MongoDB от номера документа, по которому поддержку найдёт заказ через неделю.
Входные данные
lsid — обозначение логической сессии на стороне MongoDB. getLogicalSessionId() возвращает документ. В нашем драйвере поле id содержит Binary с подтипом UUID. Метод getData() возвращает байты этого значения.
- Касса и приложение открывают по одной сессии
- Записывают D-31 и M-42 в общей коллекции
- Сравниваем UUID двух ещё активных сессий
- Каждая заявка читается по своему деловому номеру
Вызов
$deskId = $desk->getLogicalSessionId()->id;
$mobileId = $mobile->getLogicalSessionId()->id;
$different = $deskId->getData() !== $mobileId->getData(); // trueРезультат
| Что проверили | Результат |
|---|---|
| Заказ D-31 | принят в кассе |
| Заказ M-42 | принят в приложении |
| Разные активные lsid | true |
| Байт в id сессии | 16 |
Почему получился такой результат
На время проверки обе сессии открыты, их байтовые идентификаторы различаются. Значение UUID меняется от запуска к запуску. Таблица показывает воспроизводимую проверку различия и считанные статусы.
Границы применения
Операции одной сессии выполняйте последовательно. Для независимых запросов приложения создавайте отдельные сессии по необходимости. После завершения драйвер может использовать серверную сессию повторно. lsid не служит ключом заказа или авторизации.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$desk = $manager->startSession();
$mobile = $manager->startSession();
$deskId = $desk->getLogicalSessionId()->id;
$mobileId = $mobile->getLogicalSessionId()->id;
$a = addEvent($manager, $ns, $desk, 'D-31', 'принят в кассе');
$b = addEvent($manager, $ns, $mobile, 'M-42', 'принят в приложении');
$first = readEvent($manager, $ns, $desk, 'D-31');
$second = readEvent($manager, $ns, $mobile, 'M-42');
$different = $deskId->getData() !== $mobileId->getData();
$checks = [
'two_inserts' => $a === 1 && $b === 1,
'uuid_type' => $deskId instanceof Binary && $deskId->getType() === Binary::TYPE_UUID,
'uuid_width' => strlen($deskId->getData()) === 16,
'distinct_active_sessions' => $different,
'desk_order' => $first === 'принят в кассе',
'app_order' => $second === 'принят в приложении',
'identity_stable' => $mobileId == $mobile->getLogicalSessionId()->id,
];
$details = ['business_ids' => ['D-31','M-42'], 'uuid_bytes' => strlen($deskId->getData())];
$desk->endSession();
$mobile->endSession();
return [['Заказ D-31', $first], ['Заказ M-42', $second], ['Разные активные lsid', $different], ['Байт в id сессии', strlen($deskId->getData())]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-request-identities', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-request-identities: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-request-identities: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Прочитайте D-31 через объект $mobile. При тех же правах результат доступен и другой сессии. Доступ определяется авторизацией, а не именем сессии.
Автор не понимает, распространяется ли требование последовательной работы одной сессии на весь веб-сервис. MongoDB sessions with PHP-FPM на Stack Overflow.
Упорядочить события упаковки и отправки
Два события могут произойти за одну секунду. Для сверки последовательности складских операций нужны отметки, выданные MongoDB после каждой действительной записи.
Входные данные
Timestamp BSON включает секунды и приращение. Помощник clockParts возвращает пару целых в порядке [секунды, приращение]. Сначала сравниваем секунды, а при их равенстве сравниваем приращение.
- Подтвердить запись «упаковано»
- Сохранить первый Timestamp
- Подтвердить запись «отправлено» и сохранить второй Timestamp
- Сравнить обе пары и отдельно прочитать события
Вызов
$packed = $session->getOperationTime();
// Завершилась следующая подтверждённая запись
$sent = $session->getOperationTime();
$later = clockParts($sent) > clockParts($packed);Результат
| Что проверили | Результат |
|---|---|
| Первое событие | упаковано |
| Второе событие | отправлено |
| Метка второй записи больше | true |
Почему получился такой результат
Обе операции вставили новые документы. Их метки получены в порядке выполнения. Обычное чтение или операция без изменения данных могут вернуть уже известную метку. Ожидать строгого роста на каждый вызов нельзя.
Границы применения
Timestamp определяет логический порядок. Для поля «дату отправки показывать по Москве» сохраните отдельную дату предметной области.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$session = $manager->startSession(['causalConsistency' => true]);
$initial = $session->getOperationTime();
$a = addEvent($manager, $ns, $session, 'P-1', 'упаковано');
$packed = $session->getOperationTime();
$b = addEvent($manager, $ns, $session, 'P-2', 'отправлено');
$sent = $session->getOperationTime();
$later = clockParts($sent) > clockParts($packed);
$first = readEvent($manager, $ns, $session, 'P-1');
$last = readEvent($manager, $ns, $session, 'P-2');
$checks = [
'empty_before' => $initial === null, 'two_events' => $a === 1 && $b === 1,
'valid_timestamps' => $packed instanceof Timestamp && $sent instanceof Timestamp,
'forward_order' => $later, 'packed' => $first === 'упаковано',
'dispatched' => $last === 'отправлено',
];
$details = ['event_ids' => ['P-1','P-2'], 'order_comparison' => $later];
return [['Первое событие', $first], ['Второе событие', $last], ['Метка второй записи больше', $later]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-operation-order', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-operation-order: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-operation-order: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Выведите обе пары рядом в отладочном терминале. Если первые числа одинаковы, объясните порядок с помощью вторых чисел.
Сохранить метку кластера вместе с её документом
После закрытия приёмки сервис сохраняет сведения о времени кластера для следующего обработчика. Если передать только число секунд, утратятся тип Timestamp и другие поля серверного документа.
Входные данные
getClusterTime() возвращает объект с логическим временем кластера, которое узнала сессия. Исходный документ может включать служебную подпись. Передавайте его целиком между доверенными частями приложения.
- Открыть две сессии в том же развёртывании
- В первой записать приёмку R-9
- Из ответа взять документ с clusterTime
- Передать документ второй сессии и сравнить метки
Вызов
$envelope = $source->getClusterTime();
if ($envelope !== null) {
$receiver->advanceClusterTime($envelope);
}Результат
| Что проверили | Результат |
|---|---|
| До операции | NULL |
| Приёмка R-9 | приёмка закрыта |
| Вложена метка BSON | true |
| Метка принята | true |
Почему получился такой результат
Пример изолирует один шаг передачи. Мы отдельно проверяем метку получателя и документ приёмки, прочитанный отправителем. Полная зависимость между операциями двух сессий разбирается в следующем сценарии с обеими метками.
Границы применения
Не принимайте часы из произвольного клиентского JSON. Дата компьютера и случайно составленный документ не являются корректной заменой полученного серверного значения.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$source = $manager->startSession();
$receiver = $manager->startSession();
$empty = $source->getClusterTime();
$count = addEvent($manager, $ns, $source, 'R-9', 'приёмка закрыта');
$envelope = $source->getClusterTime();
$receiver->advanceClusterTime($envelope);
$accepted = $receiver->getClusterTime();
$same = clockParts($accepted->clusterTime) === clockParts($envelope->clusterTime);
$state = readEvent($manager, $ns, $source, 'R-9');
$checks = [
'initially_empty' => $empty === null, 'one_receipt' => $count === 1,
'envelope_object' => is_object($envelope),
'bson_clock' => $envelope->clusterTime instanceof Timestamp,
'same_cluster_clock' => $same, 'receipt_state' => $state === 'приёмка закрыта',
];
$details = ['receipt' => 'R-9', 'copied_full_document' => $same];
return [['До операции', $empty], ['Приёмка R-9', $state], ['Вложена метка BSON', $envelope->clusterTime instanceof Timestamp], ['Метка принята', $same]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-cluster-envelope', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-cluster-envelope: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-cluster-envelope: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Посмотрите getOperationTime() получателя сразу после единственного вызова advanceClusterTime. Затем передайте и метку операции, как в следующем примере.
Передать упаковщику нижнюю границу чтения после резерва
Сначала один обработчик резервирует товар, затем другой готовит задание упаковщику. Двум обработчикам нужны отдельные сессии, но при чтении нельзя оказаться раньше подтверждённой операции резерва.
Входные данные
Обе сессии намеренно открываются одним Manager с causalConsistency=true. Источник получил обе метки от успешной majority-записи. Получателю передаём BSON Timestamp операции и полный документ версии кластера. Получатель читает с majority.
- Оформить резерв K-71
- Передать доверенному обработчику две метки от этой операции
- Установить обе нижние границы до чтения
- В собственной сессии прочитать «зарезервирован»
Вызов
$packing->advanceOperationTime($reservations->getOperationTime());
$packing->advanceClusterTime($reservations->getClusterTime());
$state = readEvent($manager, $ns, $packing, 'K-71');Результат
| Что проверили | Результат |
|---|---|
| Указана граница операции | true |
| Указано время кластера | true |
| Уровень чтения в команде find | majority |
| afterClusterTime совпал с меткой операции | true |
| Склад видит K-71 | зарезервирован |
Почему получился такой результат
Начальная граница получателя пуста. После передачи обе метки совпадают с метками источника. Сам advance меняет состояние драйвера. Для чтения драйвер сформировал find. Подписчик на событие начала именно этой команды проверил значение majority в readConcern.level и сравнил отправленный afterClusterTime с меткой операции источника. В отчёт попали только уровень и результат сравнения. Разные lsid продолжают обозначать разные сессии.
Границы применения
Допустимы более свежие изменения других клиентов. Для точной исторической копии или нескольких действий как единого целого изучите snapshot и транзакции. В распределённом приложении сохраняйте BSON-типы меток и доверие к их происхождению.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$reservations = $manager->startSession(['causalConsistency' => true]);
$packing = $manager->startSession(['causalConsistency' => true]);
$count = addEvent($manager, $ns, $reservations, 'K-71', 'зарезервирован');
$operation = $reservations->getOperationTime();
$cluster = $reservations->getClusterTime();
$fresh = $packing->getOperationTime() === null && $packing->getClusterTime() === null;
$packing->advanceOperationTime($operation);
$packing->advanceClusterTime($cluster);
$gotOperation = clockParts($operation) === clockParts($packing->getOperationTime());
$gotCluster = clockParts($cluster->clusterTime) === clockParts($packing->getClusterTime()->clusterTime);
$wire = new class($ns, $operation) implements MongoDB\Driver\Monitoring\CommandSubscriber {
public int $finds = 0;
public ?string $level = null;
public bool $boundaryMatches = false;
public function __construct(private string $ns, private Timestamp $boundary) {}
public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event): void
{
if ($event->getCommandName() !== 'find') {
return;
}
$command = $event->getCommand();
if ($event->getDatabaseName().'.'.$command->find !== $this->ns) {
return;
}
$this->finds++;
$concern = $command->readConcern ?? null;
$this->level = $concern->level ?? null;
$sentTime = $concern->afterClusterTime ?? null;
$this->boundaryMatches = $sentTime instanceof Timestamp
&& clockParts($sentTime) === clockParts($this->boundary);
}
public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event): void {}
public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event): void {}
};
$manager->addSubscriber($wire);
try {
$state = readEvent($manager, $ns, $packing, 'K-71');
} finally {
$manager->removeSubscriber($wire);
}
$checks = [
'inserted' => $count === 1, 'receiver_empty' => $fresh,
'operation_handed_over' => $gotOperation, 'cluster_handed_over' => $gotCluster,
'separate_sessions' => $reservations->getLogicalSessionId()->id != $packing->getLogicalSessionId()->id,
'packing_sees_reservation' => $state === 'зарезервирован',
'one_observed_find' => $wire->finds === 1,
'read_concern_on_wire' => $wire->level === 'majority',
'after_cluster_time_on_wire' => $wire->boundaryMatches,
];
$details = ['document' => 'K-71', 'read_and_write_concern' => 'majority', 'reader_is_independent' => $checks['separate_sessions']];
return [['Указана граница операции', $gotOperation], ['Указано время кластера', $gotCluster],
['Уровень чтения в команде find', $wire->level], ['afterClusterTime совпал с меткой операции', $wire->boundaryMatches],
['Склад видит K-71', $state]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-handoff', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-handoff: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-handoff: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Передайте метки в функцию подготовки задания с аргументами $operation, $cluster и $orderId. Пока оба значения не получены, не передавайте null в advance и не обещайте ту же нижнюю границу.
Обработать запоздавшее сообщение без отката часов
В очереди сначала обработали сообщение «передан курьеру», затем опоздало более раннее «проверен». Получатель должен помнить уже пройденную границу. Подменим порядок доставки двух реальных наборов меток вручную.
Входные данные
Записи S-1 и S-2 выполняются последовательно и получают собственные серверные отметки. Изменяется только порядок последующей передачи отметок внутри PHP. Принимающая сессия работает с тем же развёртыванием.
- Сохранить метки для S-1
- Сохранить метки для S-2
- Получатель принимает S-2, потом S-1
- Проверить обе нижние границы и прочитать оба документа
Вызов
$reader->advanceOperationTime($secondOp);
$reader->advanceOperationTime($firstOp); // пришла более старая отметка
$current = $reader->getOperationTime(); // осталась $secondOpРезультат
| Что проверили | Результат |
|---|---|
| Метка операции осталась новой | true |
| Граница кластера сохранена | true |
| Событие S-1 | проверен |
| Событие S-2 | передан курьеру |
Почему получился такой результат
Оба метода advance принимают более новое значение. Более раннее либо равное уже установленному оставляет соответствующие часы на месте. Проверка выполняется до чтения, поскольку чтение также может продвинуть часы.
Границы применения
Сохранение нижней границы не обеспечивает дедупликацию бизнес-сообщений. Если обработчик отдельной командой уменьшает остаток, ему всё равно нужен ключ операции.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$writer = $manager->startSession();
$reader = $manager->startSession(['causalConsistency' => true]);
addEvent($manager, $ns, $writer, 'S-1', 'проверен');
$firstOp = $writer->getOperationTime();
$firstCluster = $writer->getClusterTime();
addEvent($manager, $ns, $writer, 'S-2', 'передан курьеру');
$secondOp = $writer->getOperationTime();
$secondCluster = $writer->getClusterTime();
$reader->advanceOperationTime($secondOp);
$reader->advanceClusterTime($secondCluster);
$reader->advanceOperationTime($firstOp);
$reader->advanceClusterTime($firstCluster);
$opRetained = clockParts($reader->getOperationTime()) === clockParts($secondOp);
$clusterRetained = clockParts($reader->getClusterTime()->clusterTime) === clockParts($secondCluster->clusterTime);
$a = readEvent($manager, $ns, $reader, 'S-1');
$b = readEvent($manager, $ns, $reader, 'S-2');
$checks = [
'newer_operation' => clockParts($secondOp) > clockParts($firstOp),
'ordered_cluster_times' => clockParts($secondCluster->clusterTime) >= clockParts($firstCluster->clusterTime),
'retained_operation' => $opRetained, 'retained_cluster' => $clusterRetained,
'first_saved' => $a === 'проверен', 'latest_saved' => $b === 'передан курьеру',
];
$details = ['delivery_order' => ['S-2', 'S-1'], 'same_newest_operation' => $opRetained];
return [['Метка операции осталась новой', $opRetained], ['Граница кластера сохранена', $clusterRetained], ['Событие S-1', $a], ['Событие S-2', $b]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-older-message', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-older-message: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-older-message: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Передайте второй набор два раза. Сравните пары BSON и найдите место, где обрабатывается повтор бизнес-события в вашем сервисе. Одни часы эту часть не реализуют.
Разработчик спрашивает, как зависимое чтение может учитывать операции из разных сессий и зачем хранить два вида отметок. Latest of multiple causally consistent sessions на Stack Overflow.
Показать сервер чтения в диагностике заказа
Заказ получен из базы, но в диагностике Session::getServer показывает null. Сотруднику поддержки нужно объяснить, кто ответил на запрос и почему пустое поле здесь закономерно.
Входные данные
Закрепление означает, что сессия связана с определённым сервером для соответствующей работы, например с маршрутизатором в распределённой транзакции. Курсор — результат выполнения одного запроса. Его метод getServer сообщает, куда был отправлен этот запрос.
- Начать обычную сессию без закрепления
- Выбрать T-8 и получить курсор
- У курсора узнать выполнивший сервер
- У сессии независимо проверить закрепление
Вызов
$pinned = $session->getServer(); // null в этом сценарии
$selected = $cursor->getServer(); // сервер выполненного запросаРезультат
| Что проверили | Результат |
|---|---|
| Сервер закреплён до чтения | false |
| Чтение выполнил primary | true |
| Статус T-8 | на выдаче |
| Сервер закреплён после чтения | false |
Почему получился такой результат
На стенде запрос обслужил primary. Пока выполнялось это обычное чтение, закрепления не появилось. Поэтому в одной диагностической строке корректно соседствуют «читал primary» и «закрепления нет».
Границы применения
Для одного узла и обычного чтения демонстрируется ветка null. Закреплённый mongos требует соответствующей шардированной топологии и транзакции.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$session = $manager->startSession();
$before = $session->getServer();
$count = addEvent($manager, $ns, $session, 'T-8', 'на выдаче');
$cursor = eventCursor($manager, $ns, $session, 'T-8');
$selected = $cursor->getServer();
$documents = $cursor->toArray();
$pinned = $session->getServer();
$isPrimary = $selected->getType() === MongoDB\Driver\Server::TYPE_RS_PRIMARY;
$state = $documents[0]->state;
$checks = [
'no_initial_pin' => $before === null, 'one_insert' => $count === 1,
'served_by_primary' => $isPrimary, 'one_document' => count($documents) === 1,
'correct_state' => $state === 'на выдаче', 'no_final_pin' => $pinned === null,
];
$details = ['topology' => 'один узел replica set', 'read_from_primary' => $isPrimary];
return [['Сервер закреплён до чтения', $before !== null], ['Чтение выполнил primary', $isPrimary], ['Статус T-8', $state], ['Сервер закреплён после чтения', $pinned !== null]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-actual-server', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-actual-server: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-actual-server: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Добавьте в отчёт адрес из $selected->getHost() и $selected->getPort(). В своих логах отнесите эти поля к конкретному запросу.
Разобрать конфликт номера при исправной сессии
Интеграция второй раз отправила квитанцию с тем же номером. Сервис должен отличить ошибку ограничения уникальности от сетевого сбоя, потом прочитать уже принятый документ.
Входные данные
Поле _id уникально в коллекции. Первое событие получило ключ R-12, поэтому вторая вставка того же ключа вызывает BulkWriteException. isDirty() сообщает специальный признак сессии, которую использовала команда, столкнувшаяся с сетевой ошибкой.
- Первой вставкой сохранить R-12
- Повторить ключ и получить код 11000
- Проверить сетевой признак сессии
- Прочитать исходную квитанцию
Вызов
$dirty = $session->isDirty();
$errors = $error->getWriteResult()->getWriteErrors();
$duplicate = $errors[0]->getCode() === 11000;Результат
| Что проверили | Результат |
|---|---|
| Первый документ | получен |
| Код повторной вставки | 11000 |
| Сессия dirty после конфликта | false |
Почему получился такой результат
Сервер ответил на вторую команду понятной ошибкой ключа. Она находится в WriteError. Сессия осталась чистой, а отдельное чтение вернуло первый вариант квитанции.
Границы применения
false не доказывает успех команды. Для поля dirty=true понадобилась бы сетевая ошибка. Программа проверяет чистую сессию после конфликта при работающем соединении.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/ms130-helper.php';
use MongoDB\Driver\{Manager, Session};
use MongoDB\BSON\{Binary, Timestamp};
[$uri, $db] = ms130Config();
$manager = new Manager($uri, [
'serverSelectionTimeoutMS' => 2000,
'socketTimeoutMS' => 4500,
'retryReads' => false,
]);
$hello = ms130RequireLab($manager, $db);
$runtime = [
'php' => PHP_VERSION, 'extension' => phpversion('mongodb'),
'server' => ms130Command($manager, $db, ['buildInfo' => 1])[0]['version'],
'execution' => 'database', 'replica_set' => $hello['setName'], 'members' => count($hello['hosts']),
];
$checks = [];
$details = [];
$rows = ms130WithCollection($manager, $db, function (string $ns) use ($manager, &$checks, &$details): array {
$session = $manager->startSession();
$initialDirty = $session->isDirty();
$count = addEvent($manager, $ns, $session, 'R-12', 'получен');
$code = null;
try {
addEvent($manager, $ns, $session, 'R-12', 'дубликат');
} catch (MongoDB\Driver\Exception\BulkWriteException $error) {
$errors = $error->getWriteResult()->getWriteErrors();
$code = $errors[0]->getCode() ?? null;
}
$afterError = $session->isDirty();
$state = readEvent($manager, $ns, $session, 'R-12');
$checks = [
'clean_start' => $initialDirty === false, 'first_insert' => $count === 1,
'duplicate_conflict' => $code === 11000, 'no_network_mark' => $afterError === false,
'stored_original' => $state === 'получен',
'usable_session' => $session->getOperationTime() instanceof Timestamp,
];
$details = ['order' => 'R-12', 'duplicate_error_code' => $code, 'dirty' => $afterError];
return [['Первый документ', $state], ['Код повторной вставки', $code], ['Сессия dirty после конфликта', $afterError]];
});
$checks['owned_collection_removed'] = true;
ms130Report('mongodb130-session-duplicate', ['Что проверили', 'Результат'], $rows, $checks, $runtime, $details);
echo 'mongodb130-session-duplicate: OK', PHP_EOL;
Ожидаемый результат
mongodb130-session-duplicate: OK
Запустите полный файл в отдельной учебной реплике. В HTML и JSON появятся значения, полученные из собственной коллекции примера.
Открыть таблицу запуска · Скачать JSON с результатом
Упражнение. Измените ключ второй квитанции на R-13. Ошибки повтора уже не возникнет. Прочитайте оба документа и обновите ожидаемые строки проверки.
Класс Session и методы работы со временем
| Имя | Какую задачу решает |
|---|---|
| MongoDB\Driver\Session | Связать последовательные операции клиента и хранить их логическое время. |
| MongoDB\Driver\Session::__construct | Понять, откуда берётся клиентская сессия. |
| MongoDB\Driver\Session::getLogicalSessionId | Узнать идентификатор, по которому сервер узнаёт операции сессии. |
| MongoDB\Driver\Session::getOperationTime | Получить известную сессии логическую отметку операции. |
| MongoDB\Driver\Session::getClusterTime | Получить документ логического времени кластера, известного сессии. |
| MongoDB\Driver\Session::advanceOperationTime | Передать сессии отметку уже выполненной операции. |
| MongoDB\Driver\Session::advanceClusterTime | Перенести полное серверное значение времени кластера в другую сессию. |
| MongoDB\Driver\Session::getServer | Узнать, закреплена ли сессия за конкретным сервером. |
| MongoDB\Driver\Session::isDirty | Проверить, отмечена ли сессия драйвером после сетевой ошибки. |
Транзакция в этой же Session имеет пять обозначенных константами состояний. TRANSACTION_NONE означает, что транзакции нет. TRANSACTION_STARTING означает, что она начата, но первая команда ещё не отправлена. Дальше возможны TRANSACTION_IN_PROGRESS, TRANSACTION_COMMITTED или TRANSACTION_ABORTED. После чтения заказа без startTransaction состояние остаётся NONE.
Управление транзакцией вынесено в отдельные карточки. startTransaction, isInTransaction, getTransactionState, getTransactionOptions, commitTransaction, abortTransaction.
Выбрать сведения для карточки, очереди и диагностики
Для карточки заказа нужен ключ заказа и фактическое чтение. Для последовательной цепочки операций нужен общий контекст. Для передачи цепочки другому обработчику сохраните две настоящие серверные метки и установите их до следующего чтения. Для разбора ошибки отдельно изучите результат записи и сервер курсора.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
MongoDB\Driver\SessionСвязать последовательные операции клиента и хранить их логическое время.
ext-mongodb 1.4.0 и новее · mongodb
final class MongoDB\Driver\SessionПараметры и вызов
Экземпляр принадлежит создавшему его клиенту. Получите его из Manager::startSession и передайте как session в опции каждой зависимой операции.
Возвращаемое значение
Значение Session можно использовать для идентификации операций, передачи времени и управления транзакциями.
Ошибки и границы
Операции одной сессии выполняйте последовательно. У сессии свой жизненный цикл. Запущенная транзакция имеет отдельные правила чтения, записи и завершения.
Версии
ext-mongodb 1.4.0 и новее
Связать запись заказа и его чтение
$s = $manager->startSession(['causalConsistency' => true]);
addEvent($manager, $ns, $s, 'A-17', 'готов');
$status = readEvent($manager, $ns, $s, 'A-17');Обе операции ссылаются на одну сессию. Прочитанный статус A-17 будет «готов».
Открыть пример с исходными данными и результатом
Передать работу другому обработчику
$packing = $manager->startSession(['causalConsistency' => true]);
$packing->advanceOperationTime($reservations->getOperationTime());
$packing->advanceClusterTime($reservations->getClusterTime());У упаковщика собственная сессия. Переданные метки относятся к уже подтверждённому резерву K-71.
MongoDB\Driver\Session::__constructПонять, откуда берётся клиентская сессия.
ext-mongodb 1.4.0 и новее · mongodb
final private function MongoDB\Driver\Session::__construct()Параметры и вызов
Конструктор закрыт и не принимает параметров. Опции нужно передавать публичному методу Manager::startSession.
Возвращаемое значение
Публичная фабрика возвращает Session. Экземпляр связан с клиентом, который знает, как отправлять его операции.
Ошибки и границы
Вызов new MongoDB\Driver\Session() из пользовательского кода недоступен. Публичная фабрика получает корректный объект вместе с его идентификатором lsid.
Версии
ext-mongodb 1.4.0 и новее
Начать обработку заказа
$session = $manager->startSession(['causalConsistency' => true]);
$initial = $session->getOperationTime();Менеджер возвращает готовую сессию. До первого обращения начальная метка операции равна null.
Открыть пример с исходными данными и результатом
Выдать двум обработчикам независимые сессии
$desk = $manager->startSession();
$mobile = $manager->startSession();Оба объекта получены одной фабрикой и могут обслуживать независимые заказы. В примере одновременно открытые сессии имеют разные идентификаторы.
MongoDB\Driver\Session::getLogicalSessionIdУзнать идентификатор, по которому сервер узнаёт операции сессии.
ext-mongodb 1.4.0 и новее · mongodb
final public function MongoDB\Driver\Session::getLogicalSessionId(): objectПараметры и вызов
Аргументов нет. Обратитесь к действующей сессии.
Возвращаемое значение
Возвращается документ. В примерах его поле id содержит бинарный UUID длиной 16 байт.
Ошибки и границы
Операции могут относиться к разным заказам и одной сессии. Наличие одинакового lsid нельзя использовать как подтверждение, что бизнес-запрос уже обработан.
В описании метода PHP Manual указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сигнатура также задаёт допустимые типы и число параметров.
Версии
ext-mongodb 1.4.0 и новее
Сопоставить два активных обработчика
$different = $desk->getLogicalSessionId()->id->getData()
!== $mobile->getLogicalSessionId()->id->getData();Проверка сравнивает бинарные значения и возвращает true. Выводить служебный UUID в публичную таблицу не требуется.
Открыть пример с исходными данными и результатом
Связать запись с чтением
$before = $session->getLogicalSessionId();
// Выполнились запись и чтение заказа.
$after = $session->getLogicalSessionId();
$same = $before->id == $after->id;Сравниваем значения двух документов. True означает, что идентификатор остался прежним во время обработки заказа.
MongoDB\Driver\Session::getOperationTimeПолучить известную сессии логическую отметку операции.
ext-mongodb 1.4.0 и новее · mongodb
final public function MongoDB\Driver\Session::getOperationTime(): ?MongoDB\BSON\TimestampПараметры и вызов
Аргументов нет. Чтение свойства состояния выполняется локально.
Возвращаемое значение
Возвращается BSON Timestamp либо null, если драйвер ещё не получил отметки для сессии и не была передана граница через advanceOperationTime.
Ошибки и границы
Для использования результата проверьте null. Метка может оставаться прежней после чтения. Временные отметки разных развёртываний напрямую не сопоставляются.
В описании метода PHP Manual указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сигнатура также задаёт допустимые типы и число параметров.
Версии
ext-mongodb 1.4.0 и новее
Определить порядок двух изменений
$packed = $session->getOperationTime();
// Новая запись упаковки на том же сервере.
$sent = $session->getOperationTime();
$later = clockParts($sent) > clockParts($packed);После двух отдельных фактических вставок проверяем секунды и приращение. Результат сравнения — true.
Открыть пример с исходными данными и результатом
Установить нижнюю границу зависимого чтения
$checkpoint = $reservations->getOperationTime();
$packing->advanceOperationTime($checkpoint);Для этой вставки checkpoint содержит настоящий Timestamp. Упаковщику передают также документ времени кластера.
MongoDB\Driver\Session::getClusterTimeПолучить документ логического времени кластера, известного сессии.
ext-mongodb 1.4.0 и новее · mongodb
final public function MongoDB\Driver\Session::getClusterTime(): ?objectПараметры и вызов
Аргументов нет. Используйте объект активной сессии, участвовавшей в операции.
Возвращаемое значение
Возвращается объект с временем кластера и служебными полями сервера либо null для новой сессии без операций и без advanceClusterTime.
Ошибки и границы
Поле clusterTime внутри документа — BSON Timestamp. Для передачи другой сессии нужен полный документ, поэтому извлечение одного числа меняет смысл данных.
В описании метода PHP Manual указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сигнатура также задаёт допустимые типы и число параметров.
Версии
ext-mongodb 1.4.0 и новее
Проверить форму полученной отметки
$envelope = $source->getClusterTime();
$hasTimestamp = $envelope->clusterTime instanceof MongoDB\BSON\Timestamp;После записи R-9 проверка показывает true. Документ оставляем целым.
Открыть пример с исходными данными и результатом
Сохранить отдельную отметку для каждого сообщения
$firstCluster = $writer->getClusterTime();
// Подтверждена следующая фактическая запись.
$secondCluster = $writer->getClusterTime();Принимающей стороне можно передать оба полученных документа в порядке доставки сообщений. Драйвер сохраняет свою наиболее позднюю известную границу.
MongoDB\Driver\Session::advanceOperationTimeПередать сессии отметку уже выполненной операции.
ext-mongodb 1.4.0 и новее · mongodb
final public function MongoDB\Driver\Session::advanceOperationTime(MongoDB\BSON\TimestampInterface $operationTime): voidПараметры и вызов
$operationTime принимает значение TimestampInterface из доверенной сессии того же развёртывания. Подходящая пара методов — getOperationTime и advanceOperationTime.
Возвращаемое значение
Метод возвращает void. Результат изменения доступен через getOperationTime. При передаче более ранней или равной отметки состояние остаётся прежним.
Ошибки и границы
Не подставляйте Unix time или timestamp из непроверенного клиентского запроса. getOperationTime может вернуть null. На него не распространяется тип обязательного параметра.
В описании метода PHP Manual указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сигнатура также задаёт допустимые типы и число параметров.
Версии
ext-mongodb 1.4.0 и новее
Связать резерв и чтение складской формы
$packing->advanceOperationTime($operation);
$packing->advanceClusterTime($cluster);Сверка двух отметок перед чтением успешна. Драйвер отправляет find с нижней границей afterClusterTime.
Открыть пример с исходными данными и результатом
Принять запоздавшую отметку
$reader->advanceOperationTime($secondOp);
$reader->advanceOperationTime($firstOp);После обработки запоздавшего сообщения сохраняется более новая из двух реальных отметок.
MongoDB\Driver\Session::advanceClusterTimeПеренести полное серверное значение времени кластера в другую сессию.
ext-mongodb 1.4.0 и новее · mongodb
final public function MongoDB\Driver\Session::advanceClusterTime(array|object $clusterTime): voidПараметры и вызов
$clusterTime — документ (массив или объект) с логической отметкой и служебными данными подписи. Возьмите полный результат getClusterTime из того же развёртывания.
Возвращаемое значение
Возвращаемого значения нет. Проверить новую границу можно getter-методом. Значение меньше либо равно известному — изменений нет.
Ошибки и границы
В документации предусмотрен InvalidArgumentException при ошибке разбора аргументов. Перенос этой границы относится к клиентскому состоянию и не выполняет чтение документа.
В описании метода PHP Manual указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сигнатура также задаёт допустимые типы и число параметров.
Версии
ext-mongodb 1.4.0 и новее
Передать полное значение при закрытии приёмки
$envelope = $source->getClusterTime();
if ($envelope !== null) {
$receiver->advanceClusterTime($envelope);
}Получатель сохраняет метку. Пример проверяет поле clusterTime после вызова; исходные служебные поля не преобразуются в JSON.
Открыть пример с исходными данными и результатом
Сохранить новую границу при поздней доставке
$reader->advanceClusterTime($secondCluster);
$reader->advanceClusterTime($firstCluster);Получение старого сообщения не возвращает часы назад. Для причинного чтения так же передаётся метка операции.
MongoDB\Driver\Session::getServerУзнать, закреплена ли сессия за конкретным сервером.
ext-mongodb 1.6.0 и новее · mongodb
final public function MongoDB\Driver\Session::getServer(): ?MongoDB\Driver\ServerПараметры и вызов
Аргументов нет. Объект сессии хранит состояние закрепления.
Возвращаемое значение
Возвращается Server либо null. Область применения включает библиотеки, которым нужно отправлять команды шардированной транзакции через закреплённый mongos.
Ошибки и границы
Значение null встречается после успешного чтения. Фактический сервер отдельного запроса можно получить у его курсора, даже когда у сессии нет закрепления.
В описании метода PHP Manual указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сигнатура также задаёт допустимые типы и число параметров.
Версии
ext-mongodb 1.6.0 и новее
Отобразить наличие закрепления
$pinned = $session->getServer();
$label = $pinned === null ? 'без закрепления' : $pinned->getHost();В обычной сессии этого стенда сначала увидим «без закрепления». Проверка null обязательна перед вызовом метода Server.
Открыть пример с исходными данными и результатом
Определить, кто ответил на выполненный запрос
$selected = $cursor->getServer();
$pinnedAfter = $session->getServer();После запроса getServer курсора возвращает primary. Соседнее поле сессии остаётся пустым.
MongoDB\Driver\Session::isDirtyПроверить, отмечена ли сессия драйвером после сетевой ошибки.
ext-mongodb 1.13.0 и новее · mongodb
final public function MongoDB\Driver\Session::isDirty(): boolПараметры и вызов
Аргументов нет. Проверка читает состояние действующей сессии.
Возвращаемое значение
Возвращается bool. Значение true означает, что сессия была помечена после команды с сетевой ошибкой.
Ошибки и границы
Отдельная ошибка команды или документа не равнозначна сетевому отказу. Результат записи, тип ошибки и сохранённое состояние нужно проверять отдельно.
В описании метода PHP Manual указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сигнатура также задаёт допустимые типы и число параметров.
Версии
ext-mongodb 1.13.0 и новее
Отметить сетевое состояние до обработки квитанции
$initialDirty = $session->isDirty();На исправном соединении начальное значение false. По нему ещё нельзя судить об исходе предстоящей вставки.
Открыть пример с исходными данными и результатом
Разобрать конфликт ключа
$afterError = $session->isDirty();
$state = readEvent($manager, $ns, $session, 'R-12');После кода 11000 значение false, а чтение возвращает «получен». Обе проверки отвечают на разные вопросы.
Источники и обсуждения
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


