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

Дерево руководства
Глава 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 доступны, но резервного узла нет.
| Вопрос приложения | Механизм | Пример |
|---|---|---|
| Какого подтверждения ждать от записи? | WriteConcern | w = majority; journal = true |
| Какие данные допустимо прочитать? | ReadConcern | majority или снимок snapshot |
| С какого узла разрешено читать? | ReadPreference | primary, secondary, secondaryPreferred |
| Как связать свои обращения во времени? | Session | causal-порядок или общий snapshot |
- Приложение выбирает WriteConcern и пишет заявку
- Сервер подтверждает выполнение выбранного требования
- ReadPreference выбирает подходящий узел чтения
- ReadConcern ограничивает допустимое состояние данных
- 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. Все данные живут в отдельной учебной коллекции.
- PHP создаёт описание заявки
- BulkWrite ставит insert в очередь
- WriteConcern задаёт требуемое подтверждение
- Primary подтверждает запись с запрошенным journal
- 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.
- Вставляем исходную карточку с majority
- Публикуем цену 550 отдельным update
- Получаем подтверждённый результат изменения
- Читаем нужный ID с majority на primary
- Выводим цену, редакцию и настройки политики
Самая короткая полезная операция
$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 concern | Read concern | Читаем с |
|---|---|---|---|---|---|
| CUP | 550 | 2 | majority | majority | primary |
Как это работает
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 и архив
<?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.
- Manager создаёт causal-сессию
- Update адреса передаётся вместе с session
- Та же session сопровождает чтение формы
- Следующее чтение связано с предыдущими операциями
- В 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 | Новая улица, 8 | 2 |
| Чтение формы | 1 | Новая улица, 8 | 2 |
| Повторное чтение | 1 | Новая улица, 8 | 2 |
Как это работает
Сессия хранит причинную связь между обращениями; это не 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.
- Первый find snapshot-сессии выбирает момент снимка
- Общая сумма в снимке равна 5
- Внешняя majority-запись меняет LAMP: 2 → 9
- Второй find той же сессии возвращает прежние 2
- Отдельное чтение вне снимка видит уже 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 + LAMP | 5 |
| Деталь того же снимка | LAMP | 2 |
| Свежее чтение вне снимка | LAMP | 9 |
Как это работает
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-чтение того же документа.
- Проверяем несовместимую пару опций Session
- Проверяем теги с primary
- Проверяем недопустимую минимальную давность secondary
- Проверяем отрицательный таймаут подтверждения
- Исправленные настройки читают исходный документ
Самая короткая полезная операция
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 + causal | InvalidArgumentException |
| primary + tags | InvalidArgumentException |
| secondary + 10 seconds | InvalidArgumentException |
| negative wtimeout | InvalidArgumentException |
Как это работает
Каждая ожидаемая ошибка — 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.
- Строгий secondary ищет вторичный узел
- 700 мс — настроенный предел подбора, не измерение скорости
- Подходящего узла нет: ConnectionTimeoutException
- secondaryPreferred допускает primary при отсутствии secondary
- 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 |
| secondaryPreferred | primary | 550 |
Как это работает
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 и архив
<?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|int | 0 — без подтверждения; 1 — primary; число > 1 — заданное число участников; "majority" — рассчитанное большинство; другая строка — именованный пользовательский режим write concern, определённый конфигурацией replica set. |
| $wtimeout: ?int = null | Неотрицательное ожидание write concern в миллисекундах. При отсутствии ограничения/0 ожидание не ограничено этим параметром. Не является полным дедлайном запроса. |
| $journal: ?bool = null | true требует применения записи к журналу перед подтверждением; 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: string | primary — только 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: измерение лагов не выполнялось.
Полный сценарий с исходными данными · Результат реального запуска
Источники и обсуждения
- PHP: ReadConcern::__construct
- PHP: ReadConcern::getLevel
- PHP: ReadConcern::isDefault
- PHP: WriteConcern::__construct
- PHP: WriteConcern::getW
- PHP: WriteConcern::getJournal
- PHP: WriteConcern::getWtimeout
- PHP: WriteConcern::isDefault
- PHP: ReadPreference::__construct
- PHP: ReadPreference::getModeString
- PHP: ReadPreference::getTagSets
- PHP: ReadPreference::getMaxStalenessSeconds
- PHP: Manager::startSession
- PHP: Manager::executeQuery
- PHP: Query::__construct
- PHP: Manager::executeBulkWrite
- MongoDB: Read Concern
- MongoDB: Write Concern
- MongoDB: Causal Consistency
- MongoDB: snapshot
- MongoDB: Read Preference
- MongoDB: Write Concern for Replica Sets
- PHP: ReadPreference class
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


