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

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

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

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

Глава 149 · После каталога, PHP-драйвера и атомарной записи MongoDB

MongoDB: подтверждение записи, причинное чтение и согласованный снимок отчёта

WriteConcern, ReadConcern и ReadPreference на заявке, цене, адресе и складском отчёте; snapshot против внешней правки, несовместимые политики и отказ отсутствующего secondary.

Что подтверждает запись и что разрешает чтение

Пользователь сохранил адрес — что означает подтверждение? Отчёт прочитал общий остаток, затем детали — относятся ли они к одному моменту? Сервис попросил secondary — допустимо ли незаметно прочитать primary? Эти вопросы решаются разными настройками. Здесь они показаны через сохранённые заявки, цену товара, адрес и складской отчёт.

Начните с первого документа и коллекции, Manager, Query, BulkWrite и Cursor и условной атомарной записи. В этой главе replica set — группа MongoDB-узлов с общим набором данных. Primary принимает записи, secondary получает изменения. Oplog — журнал операций для репликации; journal — механизм восстановления хранилища. Это разные журналы. Учебный replica set намеренно имеет только primary: режимы API доступны, но резервного узла нет.

Вопрос приложенияМеханизмПример
Какого подтверждения ждать от записи?WriteConcernw = majority; journal = true
Какие данные допустимо прочитать?ReadConcernmajority или снимок snapshot
С какого узла разрешено читать?ReadPreferenceprimary, secondary, secondaryPreferred
Как связать свои обращения во времени?Sessioncausal-порядок или общий snapshot
Три независимых решения
  1. Приложение выбирает WriteConcern и пишет заявку
  2. Сервер подтверждает выполнение выбранного требования
  3. ReadPreference выбирает подходящий узел чтения
  4. ReadConcern ограничивает допустимое состояние данных
  5. Session при необходимости связывает обращения между собой

Объекты concern/preference неизменяемы: new создаёт описание политики, а не меняет конфигурацию сервера. Геттеры возвращают её значения. Они полезны для проверки настроек и журналирования, но не измеряют число реплик, задержку или сохранность диска. Фактические данные читаем отдельным запросом, выбранный узел узнаём через Cursor/Server.

Подготовить отдельный односерверный replica set

Требуются PHP 8.4+ CLI с ext-mongodb 2.5+, MongoDB 8.0 и отдельный replica set phpmanRs на порту 27076. Прежний standalone на 27075 не подходит для этого раздела. Полный README в каждом ZIP содержит установку MongoDB/mongosh/расширения, запуск mongod с --replSet, одноразовый rs.initiate и проверку hello до начала примеров. Composer не требуется.

$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

Это команды PowerShell после подготовки стенда. В URI приложения нет directConnection=true, чтобы драйвер выполнял обычный выбор участника replica set. Helper проверяет локальный адрес, имя базы, phpmanRs и одного primary. Все настройки сервера и прежние коллекции остаются за пределами примеров. Каждая программа создаёт свою уникальную коллекцию, закрывает сессии, удаляет только её в finally и проверяет отсутствие. Принудительное завершение процесса может прервать finally; точечная уборка описана в README.

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

Сервис принял заявку на замену лампы. Прежде чем показать «Сохранено», нужно определить, какого подтверждения мы ждём от базы. Получить ответ primary и потребовать записи в журнал — два явно задаваемых условия, а не одно расплывчатое «всё надёжно».

Исходные данные

Заявка 101 содержит текст и статус accepted. WriteConcern(1, 1500, true) задаёт w = 1, ожидание подтверждения до 1500 мс и journal = true. После записи читаем только эту заявку с primary. Все данные живут в отдельной учебной коллекции.

Как получается результат
  1. PHP создаёт описание заявки
  2. BulkWrite ставит insert в очередь
  3. WriteConcern задаёт требуемое подтверждение
  4. Primary подтверждает запись с запрошенным journal
  5. Query возвращает сохранённую заявку для экрана

Самая короткая полезная операция

$wc = new MongoDB\Driver\WriteConcern(1, 1500, true);
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert(['_id'=>101, 'request'=>'Заменить лампу', 'status'=>'accepted']);
$result = $manager->executeBulkWrite($namespace, $bulk, ['writeConcern'=>$wc]);
echo $result->isAcknowledged() ? 'Заявка подтверждена' : 'Нет подтверждения';

Фрагмент использует $manager, $namespace и исходные данные полной программы ниже. new создаёт объект; -> вызывает его метод, => связывает ключ со значением в массиве настроек. Слова majority, primary и snapshot относятся к MongoDB, а не являются ключевыми словами PHP.

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

IDЗаявкаСтатусПодтвержденоwЗапрошен журнал
101Заменить лампуacceptedда1да

Как это работает

WriteConcern не выполняет запись: это неизменяемый объект настроек. Его передают в options executeBulkWrite. w = 1 требует подтверждения primary, journal = true — применения записи к журналу перед подтверждением. Журнал помогает восстановлению работы сервера после сбоя; он не является отдельной резервной копией. ReadConcern(local) задаёт допустимые данные чтения, ReadPreference(primary) выбирает узел. Эти два объекта передаются в разные места API: readConcern входит в Query, readPreference — в options executeQuery. Таблица показывает реально сохранённый документ и подтверждённый WriteResult. Диагностика дополнительно показывает getW/getJournal/getWtimeout/isDefault: это проверка того, что мы действительно отправили выбранную политику.

Границы и ошибки

Здесь один узел, и мы не выключаем питание, не повреждаем диск, не измеряем восстановление. Успех записи не доказывает устойчивость к потере машины. wtimeout относится к ожиданию write concern, а не является общим таймером PHP/HTTP; для w = 1 он не заменяет socket/connect/serverSelection timeout. Отказ подтверждения сам по себе не означает, что документ не записался. Не повторяйте важную заявку вслепую после сетевого сбоя — нужен прикладной ключ идемпотентности.

Полная самостоятельная программа, helper и архив
Сохранить заявку с явным подтверждением журналаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mc118-helper.php';
[$manager, $database] = mc118Environment();
$server = mc118Command($manager, $database, ['buildInfo'=>1])[0]['version'];
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>$server];
$collection = 'mongodb118_consistency_' . bin2hex(random_bytes(8));
$namespace = $database . '.' . $collection;
$owned = false; $details = [];
try {
    if (mc118Exists($manager,$database,$collection)) { throw new RuntimeException('Fixture exists'); }
    mc118Command($manager,$database,['create'=>$collection],true);
    $owned = true;
$wc = new MongoDB\Driver\WriteConcern(1, 1500, true);
    $rc = new MongoDB\Driver\ReadConcern('local');
    $rp = new MongoDB\Driver\ReadPreference('primary');
    $bulk = new MongoDB\Driver\BulkWrite();
    $bulk->insert(['_id'=>101,'request'=>'Заменить лампу','status'=>'accepted']);
    $result = $manager->executeBulkWrite($namespace,$bulk,['writeConcern'=>$wc]);
    $saved = mc118Read($manager,$namespace,['_id'=>101],['readConcern'=>$rc,'projection'=>['_id'=>1,'request'=>1,'status'=>1],'limit'=>1],['readPreference'=>$rp]);
    $policy = mc118Policies($wc,$rc,$rp);
    $rows = [[$saved[0]['_id'],$saved[0]['request'],$saved[0]['status'],$result->isAcknowledged() ? 'да':'нет',$policy['write']['w'],$policy['write']['journal'] ? 'да':'нет']];
    $checks = ['request_saved'=>count($saved)===1 && $saved[0]['status']==='accepted',
        'acknowledged_insert'=>$result->isAcknowledged() && $result->getInsertedCount()===1,
        'explicit_journal_policy'=>$policy['write']===['w'=>1,'journal'=>true,'timeout_ms'=>1500,'default'=>false],
        'explicit_local_primary'=>$policy['read']===['level'=>'local','default'=>false] && $policy['route']===['mode'=>'primary','tags'=>[],'max_staleness_seconds'=>-1]];
    $details = ['Явные политики клиента'=>$policy,'Сохранённая заявка'=>$saved,'Граница доказательства'=>['single_node'=>true,'crash_test'=>false,'disk_failure_test'=>false]];
} finally {
    if ($owned) { mc118Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed'] = !mc118Exists($manager,$database,$collection);
$details = mc118Public($details,$collection,$database);
mc118Report('mongodb118-consistency-journaled-write',['ID', 'Заявка', 'Статус', 'Подтверждено', 'w', 'Запрошен журнал'],$rows,$checks,$runtime,$details);
echo 'mongodb118-consistency-journaled-write: saved result.html and result.json
';
Ожидаемый результат
mongodb118-consistency-journaled-write: saved result.html and result.json

Запустите php example.php и откройте result.html. result.json повторяет таблицу, metrics.json содержит проверки, политики и версии. Архив включает mc118-helper.php и полный README подготовки replica set.

Открыть фактический HTML-результат · Скачать итоговую таблицу JSON

Попробуйте сами. Для другой учебной заявки потребуйте WriteConcern("majority",1500,true). На нашем одном узле она тоже будет подтверждена; это не эксперимент с несколькими копиями. Сравните поле w в сохранённой диагностике и перейдите к следующему примеру.

Подтвердить публикацию цены и прочитать majority-данные

Редактор публикует новую цену товара. Нам нужна явно выбранная политика подтверждения записи и чтение majority-данных для экрана проверки. При этом majority нельзя объяснять как магическое «всегда самый новый документ на любой машине».

Исходные данные

Создаём CUP с ценой 500 и revision = 1, затем меняем цену на 550 и увеличиваем revision. Для обоих запросов записи используем w = majority и journal = true, для чтения — ReadConcern majority и узел primary.

Как получается результат
  1. Вставляем исходную карточку с majority
  2. Публикуем цену 550 отдельным update
  3. Получаем подтверждённый результат изменения
  4. Читаем нужный ID с majority на primary
  5. Выводим цену, редакцию и настройки политики

Самая короткая полезная операция

$wc = new MongoDB\Driver\WriteConcern('majority',1500,true);
$manager->executeBulkWrite($namespace,$update,['writeConcern'=>$wc]);
$query = new MongoDB\Driver\Query(['_id'=>201],[
    'readConcern'=>new MongoDB\Driver\ReadConcern('majority'), 'limit'=>1,
]);
$cursor = $manager->executeQuery($namespace,$query,[
    'readPreference'=>new MongoDB\Driver\ReadPreference('primary'),
]);

Фрагмент использует $manager, $namespace и исходные данные полной программы ниже. new создаёт объект; -> вызывает его метод, => связывает ключ со значением в массиве настроек. Слова majority, primary и snapshot относятся к MongoDB, а не являются ключевыми словами PHP.

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

SKUЦена, руб.РедакцияWrite concernRead concernЧитаем с
CUP5502majoritymajorityprimary

Как это работает

ReadConcern описывает видимость данных, WriteConcern — подтверждение записи. majority учитывает рассчитанное большинство голосующих участников с данными; это не всегда число всех машин и не обязательно число 2. В нашем replica set один участник, поэтому требуемое большинство тоже один. В обычной схеме из трёх голосующих узлов с данными большинство — два. Сервер MongoDB 8.0 связывает majority-подтверждение с записью в oplog; дальнейшее применение на вторичных узлах может отставать. Поэтому отдельный majority-read на secondary без связи с предыдущей операцией не следует считать гарантией чтения именно последней своей записи. Следующий урок добавляет эту связь сессией.

Границы и ошибки

Одинаковый результат w = 1 и majority на стенде не делает их одинаковыми политиками для многосерверной системы. Никакие настройки global default здесь не меняются. Если read concern не указан, учитываются настройки окружения — не следует по слову default угадывать local или majority. majority не объединяет два update в транзакцию и не создаёт snapshot для произвольной серии чтений. Падение узла и задержка репликации в этом примере не моделируются.

Полная самостоятельная программа, helper и архив
Подтвердить публикацию цены и прочитать majority-данныеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mc118-helper.php';
[$manager, $database] = mc118Environment();
$server = mc118Command($manager, $database, ['buildInfo'=>1])[0]['version'];
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>$server];
$collection = 'mongodb118_consistency_' . bin2hex(random_bytes(8));
$namespace = $database . '.' . $collection;
$owned = false; $details = [];
try {
    if (mc118Exists($manager,$database,$collection)) { throw new RuntimeException('Fixture exists'); }
    mc118Command($manager,$database,['create'=>$collection],true);
    $owned = true;
$wc = new MongoDB\Driver\WriteConcern('majority',1500,true);
    $rc = new MongoDB\Driver\ReadConcern('majority');
    $rp = new MongoDB\Driver\ReadPreference('primary');
    $bulk = new MongoDB\Driver\BulkWrite();
    $bulk->insert(['_id'=>201,'sku'=>'CUP','price'=>500,'revision'=>1]);
    $inserted = $manager->executeBulkWrite($namespace,$bulk,['writeConcern'=>$wc]);
    $update = new MongoDB\Driver\BulkWrite();
    $update->update(['_id'=>201],['$set'=>['price'=>550],'$inc'=>['revision'=>1]],['multi'=>false,'upsert'=>false]);
    $result = $manager->executeBulkWrite($namespace,$update,['writeConcern'=>$wc]);
    $saved = mc118Read($manager,$namespace,['_id'=>201],['readConcern'=>$rc,'limit'=>1],['readPreference'=>$rp])[0];
    $policy = mc118Policies($wc,$rc,$rp);
    $rows = [[$saved['sku'],$saved['price'],$saved['revision'],$policy['write']['w'],$policy['read']['level'],$policy['route']['mode']]];
    $checks = ['price_published'=>$saved['price']===550 && $saved['revision']===2,
        'both_writes_confirmed'=>$inserted->isAcknowledged() && $result->isAcknowledged() && $result->getModifiedCount()===1,
        'majority_read_write'=>$policy['write']===['w'=>'majority','journal'=>true,'timeout_ms'=>1500,'default'=>false] && $policy['read']===['level'=>'majority','default'=>false],
        'primary_no_tag_or_staleness'=>$policy['route']===['mode'=>'primary','tags'=>[],'max_staleness_seconds'=>-1]];
    $details = ['Настройки публикации и чтения'=>$policy,'Опубликованная карточка'=>$saved,'Топология'=>['members'=>1,'majority_members_in_this_fixture'=>1,'replication_lag_test'=>false]];
} finally {
    if ($owned) { mc118Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed'] = !mc118Exists($manager,$database,$collection);
$details = mc118Public($details,$collection,$database);
mc118Report('mongodb118-consistency-majority-confirmation',['SKU', 'Цена, руб.', 'Редакция', 'Write concern', 'Read concern', 'Читаем с'],$rows,$checks,$runtime,$details);
echo 'mongodb118-consistency-majority-confirmation: saved result.html and result.json
';
Ожидаемый результат
mongodb118-consistency-majority-confirmation: saved result.html and result.json

Запустите php example.php и откройте result.html. result.json повторяет таблицу, metrics.json содержит проверки, политики и версии. Архив включает mc118-helper.php и полный README подготовки replica set.

Открыть фактический HTML-результат · Скачать итоговую таблицу JSON

Попробуйте сами. Добавьте ещё одну подтверждённую правку цены 600 и revision = 3 до чтения. Экран покажет 600/3. Если нужно сохранить согласованный образ нескольких чтений, одной строки readConcern = majority недостаточно — изучите snapshot-отчёт ниже.

Связать сохранение адреса и его последующее чтение одной сессией

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

Исходные данные

Адрес 301 сначала равен «Старая улица, 1». Изменяем его на «Новая улица, 8», revision становится 2. Запись и оба последующих чтения получают один и тот же объект session. Read/write concern явно majority.

Как получается результат
  1. Manager создаёт causal-сессию
  2. Update адреса передаётся вместе с session
  3. Та же session сопровождает чтение формы
  4. Следующее чтение связано с предыдущими операциями
  5. В finally сессия закрывается

Самая короткая полезная операция

$session = $manager->startSession(['causalConsistency'=>true]);
try {
    $manager->executeBulkWrite($namespace,$bulk,[
        'session'=>$session, 'writeConcern'=>new MongoDB\Driver\WriteConcern('majority',1500,true),
    ]);
    $query = new MongoDB\Driver\Query(['_id'=>301],[
        'readConcern'=>new MongoDB\Driver\ReadConcern('majority'), 'limit'=>1,
    ]);
    $cursor = $manager->executeQuery($namespace,$query,['session'=>$session]);
    $documents = $cursor->toArray();
} finally { $session->endSession(); }

Фрагмент использует $manager, $namespace и исходные данные полной программы ниже. new создаёт объект; -> вызывает его метод, => связывает ключ со значением в массиве настроек. Слова majority, primary и snapshot относятся к MongoDB, а не являются ключевыми словами PHP.

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

ШагДокументовАдресРедакция
Сохранение1Новая улица, 82
Чтение формы1Новая улица, 82
Повторное чтение1Новая улица, 82

Как это работает

Сессия хранит причинную связь между обращениями; это не PHP-сессия пользователя из $_SESSION и не начало транзакции. Просто создать объект Session недостаточно — его надо передать каждой связанной операции через options. Сочетание causalConsistency = true и majority для чтения/записи даёт документированные причинные гарантии. В нашем маршруте оба чтения видят адрес новой редакции. При этом другие пользователи могут продолжать изменять документы: сессия не замораживает всё состояние базы. Нельзя использовать Session с другим Manager или одновременно выполнять независимые операции в одной сессии, рассчитывая на последовательный причинный порядок.

Границы и ошибки

Тест проходит на primary единственного узла. Он подтверждает работающий код и видимый результат, но не воспроизводит отставание secondary, сетевое разделение или выбор нового primary. Пример без session на таком стенде тоже мог бы вернуть свежий адрес; это не отменяет необходимости корректного контракта в реальной топологии. Causal-сессия не обеспечивает атомарность нескольких документов. Для записи нескольких связанных сущностей как одного целого нужна транзакция, разобранная отдельно.

Полная самостоятельная программа, helper и архив
Связать сохранение адреса и его последующее чтение одной сессиейСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mc118-helper.php';
[$manager, $database] = mc118Environment();
$server = mc118Command($manager, $database, ['buildInfo'=>1])[0]['version'];
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>$server];
$collection = 'mongodb118_consistency_' . bin2hex(random_bytes(8));
$namespace = $database . '.' . $collection;
$owned = false; $details = [];
try {
    if (mc118Exists($manager,$database,$collection)) { throw new RuntimeException('Fixture exists'); }
    mc118Command($manager,$database,['create'=>$collection],true);
    $owned = true;
$wc = new MongoDB\Driver\WriteConcern('majority',1500,true);
    $rc = new MongoDB\Driver\ReadConcern('majority');
    $rp = new MongoDB\Driver\ReadPreference('primary');
    mc118Insert($manager,$namespace,[['_id'=>301,'address'=>'Старая улица, 1','revision'=>1]]);
    $session = $manager->startSession(['causalConsistency'=>true]);
    try {
        $bulk = new MongoDB\Driver\BulkWrite();
        $bulk->update(['_id'=>301],['$set'=>['address'=>'Новая улица, 8'],'$inc'=>['revision'=>1]],['multi'=>false,'upsert'=>false]);
        $result = $manager->executeBulkWrite($namespace,$bulk,['writeConcern'=>$wc,'session'=>$session]);
        $after = mc118Read($manager,$namespace,['_id'=>301],['readConcern'=>$rc,'limit'=>1],['readPreference'=>$rp,'session'=>$session])[0];
        $confirm = mc118Read($manager,$namespace,['_id'=>301],['readConcern'=>$rc,'limit'=>1],['readPreference'=>$rp,'session'=>$session])[0];
        $policy = mc118Policies($wc,$rc,$rp);
        $rows = [['Сохранение',$result->getModifiedCount(),'Новая улица, 8',2],['Чтение формы',1,$after['address'],$after['revision']],['Повторное чтение',1,$confirm['address'],$confirm['revision']]];
        $checks = ['address_written'=>$result->isAcknowledged() && $result->getModifiedCount()===1,
            'read_own_write'=>$after['address']==='Новая улица, 8' && $after['revision']===2,
            'subsequent_read_not_older'=>$confirm===$after,
            'majority_policies'=>$policy['write']['w']==='majority' && $policy['read']['level']==='majority'];
        $details = ['Политики причинно связанного маршрута'=>$policy,'Адрес после записи'=>$after,
            'Граница проверки'=>['same_session_for_write_and_reads'=>true,'secondary_nodes'=>0,'failover_test'=>false]];
    } finally { $session->endSession(); }
} finally {
    if ($owned) { mc118Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed'] = !mc118Exists($manager,$database,$collection);
$details = mc118Public($details,$collection,$database);
mc118Report('mongodb118-consistency-causal-read',['Шаг', 'Документов', 'Адрес', 'Редакция'],$rows,$checks,$runtime,$details);
echo 'mongodb118-consistency-causal-read: saved result.html and result.json
';
Ожидаемый результат
mongodb118-consistency-causal-read: saved result.html and result.json

Запустите php example.php и откройте result.html. result.json повторяет таблицу, metrics.json содержит проверки, политики и версии. Архив включает mc118-helper.php и полный README подготовки replica set.

Открыть фактический HTML-результат · Скачать итоговую таблицу JSON

Попробуйте сами. Добавьте в этой же сессии второе изменение адреса до второго чтения, увеличив revision ещё раз. Второе чтение увидит новую редакцию 3. Оно не обязано оставаться на версии 2, потому что causal consistency и неизменный snapshot решают разные задачи.

Практический вопрос: Read own writes in a MongoDB replicaset. Casual consistency not working?. Автор задаёт majority для чтения и записи, но читает устаревший результат с secondary без явной связанной сессии. Урок явно передаёт одну Session всем зависимым операциям; отсутствие secondary на стенде честно обозначено. Вопрос задаёт ситуацию; технические правила проверены по первичным источникам, исходные данные и реализация написаны для этого урока.

Собрать две части отчёта из одного снимка данных

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

Исходные данные

CUP имеет stock = 3, LAMP — stock = 2; начальная сумма 5. Первый find в snapshot-сессии MongoDB фиксирует момент чтения. Она связывает обращения к базе; PHP-сессия посетителя сайта решает другую задачу — хранит его состояние между HTTP-запросами. Затем отдельная запись без этой сессии меняет LAMP на 9. Второй find внутри снимка должен по-прежнему показать 2, а свежее чтение вне него — 9.

Как получается результат
  1. Первый find snapshot-сессии выбирает момент снимка
  2. Общая сумма в снимке равна 5
  3. Внешняя majority-запись меняет LAMP: 2 → 9
  4. Второй find той же сессии возвращает прежние 2
  5. Отдельное чтение вне снимка видит уже 9

Самая короткая полезная операция

$session = $manager->startSession(['snapshot'=>true,'causalConsistency'=>false]);
try {
    // Snapshot-сессия сама добавляет уровень snapshot и общий timestamp.
    $query = new MongoDB\Driver\Query([],['limit'=>10]);
    $first = $manager->executeQuery($namespace,$query,['session'=>$session])->toArray();
    // Между чтениями другой запрос меняет LAMP: 2 → 9, без этой сессии.
    $query = new MongoDB\Driver\Query(['_id'=>402],['limit'=>1]);
    $detail = $manager->executeQuery($namespace,$query,['session'=>$session])->toArray();
    echo $detail[0]->stock; // 2, соответствует первому снимку.
} finally { $session->endSession(); }

Фрагмент использует $manager, $namespace и исходные данные полной программы ниже. new создаёт объект; -> вызывает его метод, => связывает ключ со значением в массиве настроек. Слова majority, primary и snapshot относятся к MongoDB, а не являются ключевыми словами PHP.

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

Часть отчётаТоварОстаток
Начало отчётаCUP + LAMP5
Деталь того же снимкаLAMP2
Свежее чтение вне снимкаLAMP9

Как это работает

snapshot = true — опция Session. Такая сессия сама назначает чтениям уровень snapshot и сохраняет timestamp первого поддерживаемого чтения для последующих. Поэтому в Query внутри этой сессии не добавляем readConcern повторно: на данном драйвере это приводит к дублированию BSON-поля readConcern.level. Создание сессии само по себе ещё не выбирает снимок. causalConsistency здесь false: две эти модели сессии нельзя включить одновременно. Мы не вызываем startTransaction — snapshot-чтения вне транзакции доступны с MongoDB 5.0 для find, aggregate и distinct на нешардированной коллекции. Запись выполняется отдельно, без snapshot-session, поэтому в момент второго чтения в базе действительно есть 9. Последнее свежее чтение тоже идёт без этой сессии и получает отдельный явный ReadConcern("majority"). Таблица разделяет старый образ отчёта и текущее состояние.

Границы и ошибки

Snapshot-сессия предназначена для чтения: в неё нельзя добавлять запись или транзакцию. История снимков ограничена: слишком долгий отчёт может получить ошибку, а не вечное хранение старой версии. Два независимых ReadConcern(snapshot) без общей сессии не означают один и тот же timestamp. Не путайте современный snapshot с удалённой старой опцией Query snapshot. Условия и выбранные поля по-прежнему следует ограничивать на сервере; снимок не оправдывает загрузку всей базы в PHP.

Совместная запись нескольких документов — другой механизм: транзакции заказа и остатков. Snapshot-чтение текущего примера не вызывает commit и ничего не записывает внутри сессии.

Полная самостоятельная программа, helper и архив
Собрать две части отчёта из одного снимка данныхСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mc118-helper.php';
[$manager, $database] = mc118Environment();
$server = mc118Command($manager, $database, ['buildInfo'=>1])[0]['version'];
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>$server];
$collection = 'mongodb118_consistency_' . bin2hex(random_bytes(8));
$namespace = $database . '.' . $collection;
$owned = false; $details = [];
try {
    if (mc118Exists($manager,$database,$collection)) { throw new RuntimeException('Fixture exists'); }
    mc118Command($manager,$database,['create'=>$collection],true);
    $owned = true;
mc118Insert($manager,$namespace,[['_id'=>401,'sku'=>'CUP','stock'=>3],['_id'=>402,'sku'=>'LAMP','stock'=>2]]);
    $wc = new MongoDB\Driver\WriteConcern('majority',1500,true);
    $rc = new MongoDB\Driver\ReadConcern('majority'); // Только для свежего чтения вне snapshot-сессии.
    $rp = new MongoDB\Driver\ReadPreference('primary');
    $snapshotOptions = ['snapshot'=>true,'causalConsistency'=>false];
    $session = $manager->startSession($snapshotOptions);
    try {
        $before = mc118Read($manager,$namespace,[],['sort'=>['_id'=>1],'limit'=>10],['readPreference'=>$rp,'session'=>$session]);
        $outside = new MongoDB\Driver\BulkWrite();
        $outside->update(['_id'=>402],['$set'=>['stock'=>9]],['multi'=>false,'upsert'=>false]);
        $written = $manager->executeBulkWrite($namespace,$outside,['writeConcern'=>$wc]);
        $sameSnapshot = mc118Read($manager,$namespace,['_id'=>402],['limit'=>1],['readPreference'=>$rp,'session'=>$session])[0];
        $fresh = mc118Read($manager,$namespace,['_id'=>402],['readConcern'=>$rc,'limit'=>1],['readPreference'=>$rp])[0];
        $policy = mc118Policies($wc,$rc,$rp);
        $rows = [['Начало отчёта','CUP + LAMP',array_sum(array_column($before,'stock'))],['Деталь того же снимка','LAMP',$sameSnapshot['stock']],['Свежее чтение вне снимка','LAMP',$fresh['stock']]];
        $checks = ['initial_total_five'=>array_sum(array_column($before,'stock'))===5,
            'snapshot_kept_old_detail'=>$sameSnapshot['stock']===2,
            'outside_change_visible_fresh'=>$written->getModifiedCount()===1 && $fresh['stock']===9,
            'snapshot_explicit'=>$snapshotOptions['snapshot']===true && $snapshotOptions['causalConsistency']===false && $policy['read']===['level'=>'majority','default'=>false]];
        $details = ['Переданные настройки snapshot-сессии'=>$snapshotOptions,'Политика свежего чтения вне снимка'=>$policy,'Первое чтение, задающее снимок'=>$before,'Второе чтение того же снимка'=>$sameSnapshot,'Отдельное свежее чтение'=>$fresh,
            'Порядок'=>['first_read','outside_write_without_session','second_read_same_snapshot','fresh_read_without_session']];
    } finally { $session->endSession(); }
} finally {
    if ($owned) { mc118Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed'] = !mc118Exists($manager,$database,$collection);
$details = mc118Public($details,$collection,$database);
mc118Report('mongodb118-consistency-snapshot-report',['Часть отчёта', 'Товар', 'Остаток'],$rows,$checks,$runtime,$details);
echo 'mongodb118-consistency-snapshot-report: saved result.html and result.json
';
Ожидаемый результат
mongodb118-consistency-snapshot-report: saved result.html and result.json

Запустите php example.php и откройте result.html. result.json повторяет таблицу, metrics.json содержит проверки, политики и версии. Архив включает mc118-helper.php и полный README подготовки replica set.

Открыть фактический HTML-результат · Скачать итоговую таблицу JSON

Попробуйте сами. После первого чтения добавьте вне snapshot-сессии ещё один товар со stock = 4. Повторная выборка внутри сессии останется на двух товарах и сумме 5. Новое чтение вне снимка увидит три товара; учитывая изменение LAMP до 9, общая сумма будет 16.

Практический вопрос: Changing MongoDB Isolation level when Mongo Sessions involved. Пользователь сталкивается с противоречивыми объяснениями изоляции и настроек сессии. Урок разделяет causal-сессию, snapshot-сессию чтения и отдельную транзакцию; показывает неизменную деталь отчёта при внешней записи. Вопрос задаёт ситуацию; технические правила проверены по первичным источникам, исходные данные и реализация написаны для этого урока.

Отделить несовместимые настройки от ошибки данных

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

Исходные данные

В коллекции есть готовый daily-отчёт 501. Четыре отдельные попытки создать неправильные настройки: snapshot + causal, теги с primary, maxStalenessSeconds = 10 и отрицательный wtimeout. После отказов выполняется нормальное majority-чтение того же документа.

Как получается результат
  1. Проверяем несовместимую пару опций Session
  2. Проверяем теги с primary
  3. Проверяем недопустимую минимальную давность secondary
  4. Проверяем отрицательный таймаут подтверждения
  5. Исправленные настройки читают исходный документ

Самая короткая полезная операция

try {
    $session = $manager->startSession(['snapshot'=>true,'causalConsistency'=>true]);
} catch (MongoDB\Driver\Exception\InvalidArgumentException $error) {
    echo 'Выберите снимок для отчёта или причинный порядок операций';
}
$rc = new MongoDB\Driver\ReadConcern();
var_dump($rc->getLevel(), $rc->isDefault()); // null, true: уровень не задан.

Фрагмент использует $manager, $namespace и исходные данные полной программы ниже. new создаёт объект; -> вызывает его метод, => связывает ключ со значением в массиве настроек. Слова majority, primary и snapshot относятся к MongoDB, а не являются ключевыми словами PHP.

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

НастройкаРезультат
snapshot + causalInvalidArgumentException
primary + tagsInvalidArgumentException
secondary + 10 secondsInvalidArgumentException
negative wtimeoutInvalidArgumentException

Как это работает

Каждая ожидаемая ошибка — InvalidArgumentException, а не WriteError сервера. Snapshot хранит один момент, causal связывает операции после предыдущих: драйвер запрещает обе опции true. Теги и ограничение давности относятся к выбору подходящих участников, но несовместимы с primary. Минимальная положительная maxStalenessSeconds равна 90; это грубая оценка задержки, не TTL документа и не таймаут запроса. Отрицательный wtimeout недопустим. Отдельное сравнение показывает: new ReadConcern() имеет getLevel() = null и isDefault() = true, явный majority — строку и false. «Не задано» не равно «измерено на сервере».

Границы и ошибки

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

Полная самостоятельная программа, helper и архив
Отделить несовместимые настройки от ошибки данныхСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mc118-helper.php';
[$manager, $database] = mc118Environment();
$server = mc118Command($manager, $database, ['buildInfo'=>1])[0]['version'];
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>$server];
$collection = 'mongodb118_consistency_' . bin2hex(random_bytes(8));
$namespace = $database . '.' . $collection;
$owned = false; $details = [];
try {
    if (mc118Exists($manager,$database,$collection)) { throw new RuntimeException('Fixture exists'); }
    mc118Command($manager,$database,['create'=>$collection],true);
    $owned = true;
mc118Insert($manager,$namespace,[['_id'=>501,'report'=>'daily','state'=>'ready']]);
    $attempts = [
        ['name'=>'snapshot + causal','run'=>function() use ($manager) {
            $session = $manager->startSession(['snapshot'=>true,'causalConsistency'=>true]); $session->endSession();
        }],
        ['name'=>'primary + tags','run'=>fn()=>new MongoDB\Driver\ReadPreference('primary',[['dc'=>'lab']])],
        ['name'=>'secondary + 10 seconds','run'=>fn()=>new MongoDB\Driver\ReadPreference('secondary',null,['maxStalenessSeconds'=>10])],
        ['name'=>'negative wtimeout','run'=>fn()=>new MongoDB\Driver\WriteConcern('majority',-1,true)],
    ];
    $rows=[]; $errors=[];
    foreach ($attempts as $attempt) {
        $caught=false;
        try { ($attempt['run'])(); }
        catch (MongoDB\Driver\Exception\InvalidArgumentException $error) {
            $caught=true; $errors[]=['configuration'=>$attempt['name'],'class'=>get_class($error),'message'=>$error->getMessage()];
        }
        $rows[]=[$attempt['name'],$caught ? 'InvalidArgumentException':'неожиданно принято'];
    }
    $defaultRc = new MongoDB\Driver\ReadConcern();
    $explicitRc = new MongoDB\Driver\ReadConcern('majority');
    $wc = new MongoDB\Driver\WriteConcern('majority',1500,true);
    $rp = new MongoDB\Driver\ReadPreference('primary');
    $saved=mc118Read($manager,$namespace,['_id'=>501],['readConcern'=>$explicitRc,'limit'=>1],['readPreference'=>$rp]);
    $checks=['four_configuration_errors'=>count($errors)===4,
        'data_unchanged'=>$saved===[['_id'=>501,'report'=>'daily','state'=>'ready']],
        'unset_is_not_explicit'=>$defaultRc->isDefault() && $defaultRc->getLevel()===null && !$explicitRc->isDefault() && $explicitRc->getLevel()==='majority',
        'valid_read_still_works'=>count($saved)===1];
    $details=['Ошибка настройки, не ошибка документа'=>$errors,'Не задано против явно majority'=>[
        'unset'=>['level'=>$defaultRc->getLevel(),'default'=>$defaultRc->isDefault()],
        'explicit'=>['level'=>$explicitRc->getLevel(),'default'=>$explicitRc->isDefault()]],
        'Исправленные политики'=>mc118Policies($wc,$explicitRc,$rp),'Документ после отказов'=>$saved];
} finally {
    if ($owned) { mc118Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed'] = !mc118Exists($manager,$database,$collection);
$details = mc118Public($details,$collection,$database);
mc118Report('mongodb118-consistency-invalid-policies',['Настройка', 'Результат'],$rows,$checks,$runtime,$details);
echo 'mongodb118-consistency-invalid-policies: saved result.html and result.json
';
Ожидаемый результат
mongodb118-consistency-invalid-policies: saved result.html and result.json

Запустите php example.php и откройте result.html. result.json повторяет таблицу, metrics.json содержит проверки, политики и версии. Архив включает mc118-helper.php и полный README подготовки replica set.

Открыть фактический HTML-результат · Скачать итоговую таблицу JSON

Попробуйте сами. Для maxStalenessSeconds поставьте 90, для wtimeout — 1500, а в snapshot-сессии отключите causalConsistency. Конструкторы этих трёх настроек пройдут. Primary с непустыми тегами всё равно нужно изменить: убрать теги либо выбрать подходящий режим чтения.

Объяснить отказ secondary и явный переход secondaryPreferred на primary

Фоновый отчёт не должен незаметно нагружать primary, если вторичного узла нет. Другой экран, напротив, должен продолжить работу через primary. Эти требования задаются разными режимами: secondary и secondaryPreferred.

Исходные данные

Учебный replica set содержит только primary. В нём есть CUP с ценой 550. Для строгого secondary нет подходящего узла, поэтому подбор завершится ожидаемой ограниченной ошибкой. Для secondaryPreferred допустим переход на primary; проверяем реально выбранный сервер через Cursor.

Как получается результат
  1. Строгий secondary ищет вторичный узел
  2. 700 мс — настроенный предел подбора, не измерение скорости
  3. Подходящего узла нет: ConnectionTimeoutException
  4. secondaryPreferred допускает primary при отсутствии secondary
  5. Cursor сообщает реально выбранный primary и цену 550

Самая короткая полезная операция

$strict = new MongoDB\Driver\ReadPreference('secondary');
$preferred = new MongoDB\Driver\ReadPreference('secondaryPreferred');
// На односерверном replica set первый вариант не найдёт secondary.
$query = new MongoDB\Driver\Query(['_id'=>601],['limit'=>1]);
$cursor = $manager->executeQuery($namespace,$query,['readPreference'=>$preferred]);
echo $cursor->getServer()->isPrimary() ? 'primary' : 'secondary';

Фрагмент использует $manager, $namespace и исходные данные полной программы ниже. new создаёт объект; -> вызывает его метод, => связывает ключ со значением в массиве настроек. Слова majority, primary и snapshot относятся к MongoDB, а не являются ключевыми словами PHP.

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

Read preferenceЧто произошлоЦена, руб.
secondaryподходящего узла нетNULL
secondaryPreferredprimary550

Как это работает

ReadPreference выбирает, куда направить чтение. secondary не означает «попробовать secondary, а потом любой узел»; это поведение другого режима. Пустой список tagSets = [] не вводит фильтр по тегам, а список [ [] ] содержит явный пустой набор, подходящий любому кандидату. В варианте secondaryPreferred при отсутствии secondary допускается primary независимо от фильтра вторичных узлов. maxStalenessSeconds = 90 ограничивает допустимую оценку отставания secondary; на стенде без secondary нечего измерять. Getter возвращает настройку 90, а не фактическую задержку. Тип выбранного сервера проверяется отдельно, а не выводится из названия режима.

Границы и ошибки

Мы не отключаем узлы и не тестируем failover: secondary изначально нет. directConnection=true намеренно отсутствует в URI приложения, иначе это был бы другой режим подключения. Все записи по-прежнему идут на primary; read preference не отправляет update на secondary. Выбор режима nearest тоже не обещает secondary — он может выбрать primary по подходящей сетевой задержке. Read preference не заменяет read concern или причинную связь с предыдущей записью.

Полная самостоятельная программа, helper и архив
Объяснить отказ secondary и явный переход secondaryPreferred на primaryСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mc118-helper.php';
[$manager, $database] = mc118Environment();
$server = mc118Command($manager, $database, ['buildInfo'=>1])[0]['version'];
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>$server];
$collection = 'mongodb118_consistency_' . bin2hex(random_bytes(8));
$namespace = $database . '.' . $collection;
$owned = false; $details = [];
try {
    if (mc118Exists($manager,$database,$collection)) { throw new RuntimeException('Fixture exists'); }
    mc118Command($manager,$database,['create'=>$collection],true);
    $owned = true;
mc118Insert($manager,$namespace,[['_id'=>601,'sku'=>'CUP','price'=>550]]);
    $wc = new MongoDB\Driver\WriteConcern('majority',1500,true);
    $rc = new MongoDB\Driver\ReadConcern('majority');
    $strict = new MongoDB\Driver\ReadPreference('secondary',null,['maxStalenessSeconds'=>90]);
    $fallback = new MongoDB\Driver\ReadPreference('secondaryPreferred',[[]],['maxStalenessSeconds'=>90]);
    $failed=false; $failureClass=null;
    try {
        mc118Read($manager,$namespace,['_id'=>601],['readConcern'=>$rc,'limit'=>1],['readPreference'=>$strict]);
    } catch (MongoDB\Driver\Exception\ConnectionTimeoutException $error) {
        $failed=true; $failureClass=get_class($error);
    }
    $query = new MongoDB\Driver\Query(['_id'=>601],['readConcern'=>$rc,'limit'=>1,'maxTimeMS'=>1500]);
    $cursor = $manager->executeQuery($namespace,$query,['readPreference'=>$fallback]);
    $selectedPrimary = $cursor->getServer()->isPrimary();
    $cursor->setTypeMap(['root'=>'array','document'=>'array','array'=>'array']);
    $saved = $cursor->toArray();
    $strictPolicy=mc118Policies($wc,$rc,$strict); $fallbackPolicy=mc118Policies($wc,$rc,$fallback);
    $rows=[[$strictPolicy['route']['mode'],'подходящего узла нет',null],[$fallbackPolicy['route']['mode'],$selectedPrimary ? 'primary':'другой узел',$saved[0]['price']]];
    $checks=['strict_secondary_rejected'=>$failed && $failureClass===MongoDB\Driver\Exception\ConnectionTimeoutException::class,
        'fallback_uses_primary'=>$selectedPrimary && count($saved)===1 && $saved[0]['_id']===601,
        'policy_modes_differ'=>$strict->getModeString()==='secondary' && $fallback->getModeString()==='secondaryPreferred',
        'tags_and_staleness_recorded'=>$strict->getTagSets()===[] && $fallback->getTagSets()===[[]] && $strict->getMaxStalenessSeconds()===90 && $fallback->getMaxStalenessSeconds()===90];
    $details=['Строгая политика'=>$strictPolicy,'Разрешённый переход на primary'=>$fallbackPolicy,'Ожидаемый отказ выбора'=>$failureClass,'Прочитанный товар'=>$saved,
        'Границы'=>['members'=>1,'secondary_count'=>0,'server_selection_timeout_ms'=>700,'failover_test'=>false,'replication_lag_measured'=>false]];
} finally {
    if ($owned) { mc118Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed'] = !mc118Exists($manager,$database,$collection);
$details = mc118Public($details,$collection,$database);
mc118Report('mongodb118-consistency-secondary-selection',['Read preference', 'Что произошло', 'Цена, руб.'],$rows,$checks,$runtime,$details);
echo 'mongodb118-consistency-secondary-selection: saved result.html and result.json
';
Ожидаемый результат
mongodb118-consistency-secondary-selection: saved result.html and result.json

Запустите php example.php и откройте result.html. result.json повторяет таблицу, metrics.json содержит проверки, политики и версии. Архив включает mc118-helper.php и полный README подготовки replica set.

Открыть фактический HTML-результат · Скачать итоговую таблицу JSON

Попробуйте сами. Если отчёт обязан работать только на secondary, оставьте строгий режим и обработайте недоступность как состояние сервиса, не подменяя его молча secondaryPreferred. В отдельном многосерверном стенде можно добавить secondary и заново проверить выбранный узел; текущий helper специально требует один участник, поэтому этот архив не выдаёт такую проверку за уже выполненную.

Практический вопрос: Direct connection to mongo hidden instance in a replica set. Отчётный клиент неожиданно переходит на другой узел, хотя автор хотел ограниченный маршрут. Здесь отдельно показаны строгий secondary и разрешённый fallback secondaryPreferred; конкретный hidden-node deployment не воспроизводится. Вопрос задаёт ситуацию; технические правила проверены по первичным источникам, исходные данные и реализация написаны для этого урока.

Двенадцать методов политик: полный контракт и реальные применения

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

МетодЗачем нужен
MongoDB\Driver\ReadConcern::__constructСоздать неизменяемое описание уровня чтения.
MongoDB\Driver\ReadConcern::getLevelУзнать заданный уровень, не угадывая его из результата запроса.
MongoDB\Driver\ReadConcern::isDefaultПроверить, оставлен ли уровень чтения незаданным.
MongoDB\Driver\WriteConcern::__constructЗадать ожидаемое подтверждение записи, журнал и время ожидания.
MongoDB\Driver\WriteConcern::getWПрочитать требуемое число/режим подтверждения.
MongoDB\Driver\WriteConcern::getJournalУзнать, потребовали ли подтверждение журнала.
MongoDB\Driver\WriteConcern::getWtimeoutУзнать ограничение ожидания write concern в миллисекундах.
MongoDB\Driver\WriteConcern::isDefaultОтличить явный WriteConcern от настроек по умолчанию.
MongoDB\Driver\ReadPreference::__constructСоздать правила выбора узла для чтения.
MongoDB\Driver\ReadPreference::getModeStringПоказать настроенный маршрут чтения.
MongoDB\Driver\ReadPreference::getTagSetsПрочитать список ограничений по меткам узлов.
MongoDB\Driver\ReadPreference::getMaxStalenessSecondsПрочитать допустимую оценку отставания secondary.

Выбрать контракт своего экрана и следующую тему

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

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

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

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

MongoDB\Driver\ReadConcern::__constructСоздать неизменяемое описание уровня чтения.

С mongodb 1.0.0. Read concern поддерживается сервером с MongoDB 3.2; snapshot вне транзакции — с 5.0 для поддерживаемых чтений. · mongodb

final public MongoDB\Driver\ReadConcern::__construct(?string $level = null)

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

final public MongoDB\Driver\ReadConcern::__construct(?string $level = null)

Параметры

ПараметрНазначение
$level: ?string = nullУровень read concern либо отсутствие явного значения. Принимается строка; допустимость уровня и операции зависит от сервера.

Результат

Конструктор создаёт объект ReadConcern; сам запрос ещё не выполнен. В Query он передаётся ключом readConcern, для команды — соответствующей опцией её исполнения.

Ошибки и границы

InvalidArgumentException при неверных аргументах. Принятая конструктором строка не гарантирует поддержку уровня выбранным сервером или конкретной операцией.

Версии

С mongodb 1.0.0. Read concern поддерживается сервером с MongoDB 3.2; snapshot вне транзакции — с 5.0 для поддерживаемых чтений.

Основные уровни: local — данные выбранного узла без majority-гарантии; available — доступные данные с особыми ограничениями для шардирования; majority — majority-подтверждённое состояние; linearizable — усиленная согласованность чтения с дополнительными ограничениями; snapshot — состояние одного момента. Константы класса помогают записывать имена, но API не ограничен только ими. Подробности режима проверяйте по версии сервера; это не шкала от «медленно» до «быстро».

Проверить политику опубликованной цены

$rc = new MongoDB\Driver\ReadConcern('majority');
$query = new MongoDB\Driver\Query(['_id'=>201],['readConcern'=>$rc,'limit'=>1]);

Политика применяется к реальному чтению цены 550; это не просто вывод созданного объекта.

Полный сценарий с исходными данными · Результат реального запуска

Отдельно прочитать текущее состояние за пределами снимка

$rc = new MongoDB\Driver\ReadConcern('majority');
$query = new MongoDB\Driver\Query(['_id'=>402],['readConcern'=>$rc,'limit'=>1]);
$cursor = $manager->executeQuery($namespace,$query); // Без snapshot-сессии.

Свежее чтение показывает 9, тогда как общая snapshot-сессия продолжает видеть 2. Уровень snapshot внутри неё назначается автоматически; повторный readConcern в Query не нужен.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\ReadConcern::getLevelУзнать заданный уровень, не угадывая его из результата запроса.

С mongodb 1.0.0. · mongodb

final public MongoDB\Driver\ReadConcern::getLevel(): ?string

Узнать заданный уровень, не угадывая его из результата запроса.

final public MongoDB\Driver\ReadConcern::getLevel(): ?string

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

Строка настроенного уровня или null, если уровень не задан. Это свойство объекта политики, не измерение состояния репликации.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.0.0.

Записать политику экрана проверки цены

$level = $rc->getLevel(); // majority.

В диагностике указан majority вместе с фактической ценой и редакцией.

Полный сценарий с исходными данными · Результат реального запуска

Отличить отсутствие выбора от явного majority

$unset = new MongoDB\Driver\ReadConcern();
$explicit = new MongoDB\Driver\ReadConcern('majority');
var_dump($unset->getLevel(),$explicit->getLevel());

Получаем null и "majority". Null оставляет настройку окружению, а не обещает local.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\ReadConcern::isDefaultПроверить, оставлен ли уровень чтения незаданным.

С mongodb 1.3.0. · mongodb

final public MongoDB\Driver\ReadConcern::isDefault(): bool

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

final public MongoDB\Driver\ReadConcern::isDefault(): bool

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

true для объекта с настройками по умолчанию, false при явной политике. Метод не спрашивает глобальную конфигурацию сервера.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.3.0.

Обнаружить пустую настройку отчёта

$defaultRc = new MongoDB\Driver\ReadConcern();
var_dump($defaultRc->isDefault()); // true.

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

Полный сценарий с исходными данными · Результат реального запуска

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

$rc = new MongoDB\Driver\ReadConcern('majority');
var_dump($rc->isDefault()); // false.

Полный пример сохраняет эту проверку рядом с результатом снимка 2 против свежего 9.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\WriteConcern::__constructЗадать ожидаемое подтверждение записи, журнал и время ожидания.

С mongodb 1.0.0; с 1.7.0 wtimeout поддерживает 64-битные значения. Учитывайте разрядность PHP. Начиная с ext 2.0 WriteConcern передаётся в массиве options, а не напрямую третьим аргументом executeBulkWrite. · mongodb

final public MongoDB\Driver\WriteConcern::__construct(string|int $w, ?int $wtimeout = null, ?bool $journal = null)

Задать ожидаемое подтверждение записи, журнал и время ожидания.

final public MongoDB\Driver\WriteConcern::__construct(string|int $w, ?int $wtimeout = null, ?bool $journal = null)

Параметры

ПараметрНазначение
$w: string|int0 — без подтверждения; 1 — primary; число > 1 — заданное число участников; "majority" — рассчитанное большинство; другая строка — именованный пользовательский режим write concern, определённый конфигурацией replica set.
$wtimeout: ?int = nullНеотрицательное ожидание write concern в миллисекундах. При отсутствии ограничения/0 ожидание не ограничено этим параметром. Не является полным дедлайном запроса.
$journal: ?bool = nulltrue требует применения записи к журналу перед подтверждением; false явно не требует; null оставляет параметр незаданным.

Результат

Новый неизменяемый объект WriteConcern. Передача его executeBulkWrite не заменяет проверку WriteResult и обработку исключений.

Ошибки и границы

InvalidArgumentException при неверных аргументах, недопустимом w или отрицательном/непредставимом таймауте. Если сервер не может выполнить требование write concern, ошибка возникает уже при записи. Истечение wtimeout не откатывает выполненные изменения.

Версии

С mongodb 1.0.0; с 1.7.0 wtimeout поддерживает 64-битные значения. Учитывайте разрядность PHP. Начиная с ext 2.0 WriteConcern передаётся в массиве options, а не напрямую третьим аргументом executeBulkWrite.

Успех majority на одном узле не доказывает репликацию на другие машины. Дефолт сервера нельзя выводить из старого примера с w=1: современные установки могут наследовать majority. Пользовательский строковый режим — не массив read-preference tags. w=0 не делает все сетевые ошибки невозможными и не подходит для получения надёжных счётчиков.

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

$wc = new MongoDB\Driver\WriteConcern(1,1500,true);
$result = $manager->executeBulkWrite($namespace,$bulk,['writeConcern'=>$wc]);

Заявка 101 действительно вставлена, результат подтверждён.

Полный сценарий с исходными данными · Результат реального запуска

Опубликовать цену с majority

$wc = new MongoDB\Driver\WriteConcern('majority',1500,true);
$result = $manager->executeBulkWrite($namespace,$update,['writeConcern'=>$wc]);

Цена изменена с 500 на 550; политика отличается от w=1 даже при одинаковом числе узлов стенда.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\WriteConcern::getWПрочитать требуемое число/режим подтверждения.

С mongodb 1.0.0. · mongodb

final public MongoDB\Driver\WriteConcern::getW(): string|int|null

Прочитать требуемое число/режим подтверждения.

final public MongoDB\Driver\WriteConcern::getW(): string|int|null

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

Возвращает w как int, строковый режим или null, если он не задан. Не возвращает фактическое число ответивших серверов.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.0.0.

Проверить политику приёма заявки

$value = $wc->getW(); // 1.

Диагностика относится к объекту, переданному реальной вставке заявки 101.

Полный сценарий с исходными данными · Результат реального запуска

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

$value = $wc->getW(); // majority.

Вторая политика применяется при изменении цены CUP; сохранённая карточка и подтверждение проверяются отдельно.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\WriteConcern::getJournalУзнать, потребовали ли подтверждение журнала.

С mongodb 1.0.0. · mongodb

final public MongoDB\Driver\WriteConcern::getJournal(): ?bool

Узнать, потребовали ли подтверждение журнала.

final public MongoDB\Driver\WriteConcern::getJournal(): ?bool

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

true/false — явно заданный journal; null — параметр не задан. Метод не выполняет проверку диска и не доказывает успешное восстановление.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.0.0.

Проверить политику приёма заявки

$value = $wc->getJournal(); // true.

Диагностика относится к объекту, переданному реальной вставке заявки 101.

Полный сценарий с исходными данными · Результат реального запуска

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

$value = $wc->getJournal(); // true; вместе с majority.

Вторая политика применяется при изменении цены CUP; сохранённая карточка и подтверждение проверяются отдельно.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\WriteConcern::getWtimeoutУзнать ограничение ожидания write concern в миллисекундах.

С mongodb 1.0.0. С 1.7.0 на 32-битной системе слишком большое значение усекается с предупреждением; примеры используют безопасное 1500. · mongodb

final public MongoDB\Driver\WriteConcern::getWtimeout(): int

Узнать ограничение ожидания write concern в миллисекундах.

final public MongoDB\Driver\WriteConcern::getWtimeout(): int

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

Целое значение wtimeout; 0 означает отсутствие этого ограничения. Это настройка, а не измеренная длительность записи.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.0.0. С 1.7.0 на 32-битной системе слишком большое значение усекается с предупреждением; примеры используют безопасное 1500.

Проверить политику приёма заявки

$value = $wc->getWtimeout(); // 1500 мс.

Диагностика относится к объекту, переданному реальной вставке заявки 101.

Полный сценарий с исходными данными · Результат реального запуска

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

$value = $wc->getWtimeout(); // 1500 мс, не общий HTTP timeout.

Вторая политика применяется при изменении цены CUP; сохранённая карточка и подтверждение проверяются отдельно.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\WriteConcern::isDefaultОтличить явный WriteConcern от настроек по умолчанию.

С mongodb 1.3.0. · mongodb

final public MongoDB\Driver\WriteConcern::isDefault(): bool

Отличить явный WriteConcern от настроек по умолчанию.

final public MongoDB\Driver\WriteConcern::isDefault(): bool

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

true для объекта с настройками по умолчанию; false для явного выбора. Это не вопрос «совпало ли с default текущего сервера».

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.3.0.

Проверить политику приёма заявки

$value = $wc->isDefault(); // false: заданы w, timeout и journal.

Диагностика относится к объекту, переданному реальной вставке заявки 101.

Полный сценарий с исходными данными · Результат реального запуска

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

$value = $wc->isDefault(); // false: majority задан явно.

Вторая политика применяется при изменении цены CUP; сохранённая карточка и подтверждение проверяются отдельно.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\ReadPreference::__constructСоздать правила выбора узла для чтения.

С mongodb 1.0.0; options/maxStalenessSeconds с 1.2.0; строки mode с 1.3.0; hedge с 1.8.0. Целочисленные режимы deprecated с 1.20 и не принимаются с 2.0. Используйте современные строковые константы PRIMARY/SECONDARY и т. п., не удалённые RP_*. · mongodb

final public MongoDB\Driver\ReadPreference::__construct(string $mode, ?array $tagSets = null, ?array $options = null)

Создать правила выбора узла для чтения.

final public MongoDB\Driver\ReadPreference::__construct(string $mode, ?array $tagSets = null, ?array $options = null)

Параметры

ПараметрНазначение
$mode: stringprimary — только primary; primaryPreferred — primary с fallback на secondary; secondary — только secondary; secondaryPreferred — secondary с fallback на primary; nearest — подходящий узел по сетевой задержке, включая primary.
$tagSets: ?array = nullУпорядоченный список документов-тегов. Каждый набор требует всех своих тегов; если нет подходящего узла, проверяется следующий. Пустой набор [] внутри списка подходит любому кандидату. Не применяется к primary.
$options: ?array = nullДополнительные настройки; поддерживаемые ключи ниже.
options[maxStalenessSeconds]: intДопустимая оценка отставания secondary. По умолчанию NO_MAX_STALENESS = -1, без ограничения. Положительное значение от 90 до границы signed int32; несовместимо с primary, требует MongoDB 3.4+.
options[hedge]: array|objectОпции серверных hedged reads, например enabled. Поддерживаются для не-primary режимов чтения в шардированных запросах MongoDB 4.4+. Hedged reads устарели в MongoDB 8.0; в этой главе не включаются.

Результат

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

Ошибки и границы

InvalidArgumentException: неверные аргументы/режим, некорректные tagSets, теги с primary, maxStalenessSeconds вне диапазона или с primary. Несовместимая версия сервера и отсутствие подходящего узла проявляются при исполнении, а не обязательно в конструкторе.

Версии

С mongodb 1.0.0; options/maxStalenessSeconds с 1.2.0; строки mode с 1.3.0; hedge с 1.8.0. Целочисленные режимы deprecated с 1.20 и не принимаются с 2.0. Используйте современные строковые константы PRIMARY/SECONDARY и т. п., не удалённые RP_*.

Read preference не задаёт свежесть снимка и не выбирает узел для обычного update — записи направляются primary. Теги — метки участников replica set, не поля товара. Близкий по задержке узел не обязательно содержит самую свежую прикладную запись.

Читать подтверждённую заявку на primary

$rp = new MongoDB\Driver\ReadPreference('primary');
$cursor = $manager->executeQuery($namespace,$query,['readPreference'=>$rp]);

Читается заявка 101. Настройка относится к выбору узла чтения, не к отправке записи.

Полный сценарий с исходными данными · Результат реального запуска

Разрешить явный переход на primary

$rp = new MongoDB\Driver\ReadPreference('secondaryPreferred',[[]],['maxStalenessSeconds'=>90]);
$cursor = $manager->executeQuery($namespace,$query,['readPreference'=>$rp]);

В стенде без secondary чтение возвращает цену 550 с primary; строгий secondary рядом показывает ожидаемый отказ.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\ReadPreference::getModeStringПоказать настроенный маршрут чтения.

С mongodb 1.7.0. Старый getMode() и числовые RP_* удалены в ext 2.0. · mongodb

final public MongoDB\Driver\ReadPreference::getModeString(): string

Показать настроенный маршрут чтения.

final public MongoDB\Driver\ReadPreference::getModeString(): string

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

Строковый mode, например primary или secondaryPreferred. Он не сообщает, какой сервер фактически выбран: это проверяется отдельно по Cursor/Server.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.7.0. Старый getMode() и числовые RP_* удалены в ext 2.0.

Проверить маршрут чтения заявки

$mode = $rp->getModeString(); // primary.

Настройка записана рядом с реально сохранённой заявкой.

Полный сценарий с исходными данными · Результат реального запуска

Отличить строгий режим от разрешённого fallback

echo $strict->getModeString(); // secondary.
echo $fallback->getModeString(); // secondaryPreferred.

Второй режим фактически выбрал primary; имя режима не заменяет эту проверку.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\ReadPreference::getTagSetsПрочитать список ограничений по меткам узлов.

С mongodb 1.0.0. · mongodb

final public MongoDB\Driver\ReadPreference::getTagSets(): array

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

final public MongoDB\Driver\ReadPreference::getTagSets(): array

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

Массив наборов тегов. Пустой внешний [] означает отсутствие наборов; [ [] ] содержит явный пустой набор. Значения не являются фильтром документов коллекции.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.0.0.

Убедиться, что primary не снабжён фильтром меток

$tags = $rp->getTagSets(); // [].

Политика primary валидна и читает заявку; непустые теги с ней отдельно проверены как ошибка.

Полный сценарий с исходными данными · Результат реального запуска

Сохранить политику допуска любых кандидатов

$tags = $fallback->getTagSets(); // [[]].

Пустой набор не создаёт secondary. Если вторичного узла нет, именно secondaryPreferred разрешает primary.

Полный сценарий с исходными данными · Результат реального запуска

MongoDB\Driver\ReadPreference::getMaxStalenessSecondsПрочитать допустимую оценку отставания secondary.

С mongodb 1.2.0. Положительная настройка требует MongoDB 3.4+; минимальный допустимый предел 90 секунд. · mongodb

final public MongoDB\Driver\ReadPreference::getMaxStalenessSeconds(): int

Прочитать допустимую оценку отставания secondary.

final public MongoDB\Driver\ReadPreference::getMaxStalenessSeconds(): int

Параметры

Параметров нет; вызов выполняется у уже созданного объекта политики.

Результат

Число секунд настройки либо NO_MAX_STALENESS (-1), если ограничение не задано. Это не фактический lag, не возраст документа и не время исполнения запроса.

Ошибки и границы

Параметров нет. Документирован InvalidArgumentException при ошибке разбора аргументов.

Версии

С mongodb 1.2.0. Положительная настройка требует MongoDB 3.4+; минимальный допустимый предел 90 секунд.

Проверить отсутствие неприменимого ограничения primary

$seconds = $rp->getMaxStalenessSeconds(); // -1.

Для primary ограничение задержки secondary не задано.

Полный сценарий с исходными данными · Результат реального запуска

Проверить настройку отчётного чтения

$seconds = $fallback->getMaxStalenessSeconds(); // 90.

Политика действительно хранит 90, но на стенде нет secondary: измерение лагов не выполнялось.

Полный сценарий с исходными данными · Результат реального запуска

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

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

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

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

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