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

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

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

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

Глава 155 · После сборки BulkWriteCommand и основ BSON

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

Как связать ответы bulkWrite со строками прайса, различить matched и modified, получить upsertedId, разобрать ordered/unordered ошибки и сверить данные перед повтором.

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

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

Сначала изучите сборку пакета и namespace, документ, коллекцию и фильтр, BSON Document. Если запись $object->method() незнакома: объект и вызов его метода. Здесь $bulk хранит поручения, $result — ответ сервера, индекс — номер поручения начиная с нуля. Namespace MongoDB имеет вид имяБазы.имяКоллекции и обозначает адрес коллекции; это другое понятие, чем пространство имён PHP. Результат не равен самим документам базы: для проверки текущего состояния нужен отдельный запрос.

От строки прайса к понятному статусу
  1. Строка 101: CUP, цена 500
  2. Индекс 0: insertOne в учебную коллекцию
  3. executeBulkWriteCommand отправляет поручения
  4. Подтверждение: insertedCount=1
  5. verbose-ключ 0: insertedId товара
  6. Квитанция: строка 101 → CUP → подтверждённый _id

Минимальная последовательность для одной карточки, когда $manager и собственный $namespace уже подготовлены. new создаёт пакет, -> добавляет или вызывает действие, квадратные скобки задают поля документа. executeBulkWriteCommand возвращает результат после обращения к серверу; echo показывает число подтверждённых insert.

$bulk = new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
$bulk->insertOne($namespace, ['sku'=>'CUP', 'price'=>500]);
$result = $manager->executeBulkWriteCommand($bulk);
echo $result->getInsertedCount(); // 1

Это короткий фрагмент с подготовленным соединением. В полных программах ниже есть все данные, создание отдельной коллекции, явный WriteConcern(1), ожидаемые отказы и очистка. Такой уровень подтверждения означает ответ единственного сервера; он не делает пакет транзакцией и не проверяет устойчивость к потере узла.

Различать ноль, NULL, частичный успех и ошибку

Одинаковые числа могут означать разные действия. Поэтому отделяем сводку, подробности и ошибки, а не сводим весь процесс к одному true/false.

НаблюдениеЧто оно означаетЧто делать
matched=1, modified=0Документ найден, нового изменения нетПоказать уже совпадающее значение; проверить нужную бизнес-ветку
matched=0Фильтр update не нашёл документОтдельно показать отсутствие или конфликт версии; это не WriteError
get*Results() = NULLПодробности не запросили при verboseResults=falseИспользовать сводку; для нового запуска заранее выбрать true
getPartialResult() = NULLНет установленного подтверждённого успехаНе вызывать методы у NULL; неизвестный исход сверять
writeErrors[1] = 11000Операция 1 нарушила уникальностьСвязать с входом и проверить конфликтующие значения
writeConcernErrors не пустЕсть проблема требуемого подтвержденияНе считать запись автоматически отменённой
isAcknowledged() = falseВыбран неподтверждённый режимНе читать недоступные counters/details; это не квитанция

Подробные результаты — BSON-документы с ключами исходных операций. После toPHP числовые строковые ключи могут стать PHP int; их смысл остаётся прежним. Не перенумеровывайте их отдельно по insert/update/delete. Сводные counters относятся к документам и исполненным действиям, поэтому count($bulk), getInsertedCount и getMatchedCount отвечают на разные вопросы.

Успех операции с нулём совпадений отличается от отсутствия её подтверждения. Unordered может продолжить независимые операции после индивидуального отказа, но ошибка всё равно приходит исключением. Ни ordered, ни unordered не обеспечивают откат всего пакета. Многодокументные транзакции разобраны в отдельной главе на replica set; этот стенд — standalone.

Подготовка сервера и запуск примеров

Установите PHP 8.4+ CLI, ext-mongodb 2.5+ и отдельный MongoDB 8.0+. Composer не нужен. В README каждого ZIP есть ссылки на установку расширения и сервера, команда запуска mongod на 127.0.0.1:27075 и объяснение совпадения DLL со сборкой PHP. Все восемь программ действительно работают с базой, а не показывают заранее заготовленный ответ.

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

Распакуйте каждый пример в отдельную папку вместе с mr121-helper.php. Откройте result.html: там реальная таблица и промежуточные данные. result.json содержит её строки, metrics.json — версии, контрольные проверки и SHA256. Каждая программа создаёт собственное случайное имя коллекции и удаляет только её в finally. При неизвестной ошибке выполнение прерывается, а очистка всё равно выполняется, если процесс не был принудительно убит.

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

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

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

Две строки источника: 101 → CUP/_id=501/price=500 и 102 → LAMP/_id=502/price=1200. В пакете между вставками стоит изменение цены CUP на 550. Поэтому вставки получают индексы операций 0 и 2, а не 0 и 1. Номера 101/102 — условные строки прайса, не _id MongoDB.

Путь данных и результата
  1. Строка 101 → операция 0: вставить CUP
  2. Операция 1: установить CUP.price=550
  3. Строка 102 → операция 2: вставить LAMP
  4. Сервер подтверждает 2 вставки
  5. getInsertResults: ключи 0/2 → insertedId 501/502
  6. Таблица связывает исходную строку, артикул и _id

Короткое действие

$result = $manager->executeBulkWriteCommand($bulk);
if ($result->isAcknowledged()) {
    echo $result->getInsertedCount(); // 2
    $byOperation = $result->getInsertResults(); // ключи 0 и 2
}

Видимый результат

Строка прайсаИндекс операцииАртикулВставленный _idСтатус
1010CUP501подтверждено
1022LAMP502подтверждено

Разбор результата

BulkWriteCommand — список поручений, а BulkWriteCommandResult — ответ на его исполнение. isAcknowledged проверяет наличие подтверждения; после него getInsertedCount даёт сводку, а getInsertResults — BSON-документ с подробностями. В программе хранится собственная карта operationToLine. Если применить array_values к результату, ключ 2 станет 1 и связь с исходным поручением потеряется. insertOne возвращает _id уже при добавлении в пакет, но только подтверждённый результат позволяет сказать, что запись состоялась. Контрольное чтение показывает настоящую цену CUP=550 после отдельного update.

Ошибки и границы применения

Номер операции — не порядок физической записи при unordered и не номер строки CSV сам по себе. Связь с источником хранит приложение. Если _id не передан, драйвер может сгенерировать ObjectId; он ещё не доказывает успех сервера. Этот маленький ordered-пакет не является транзакцией.

Полная программа, helper и архив
Связать подтверждённую вставку со строкой исходного прайсаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
$source=[101=>['sku'=>'CUP','_id'=>501,'price'=>500],102=>['sku'=>'LAMP','_id'=>502,'price'=>1200]];
$bulk=new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
$ids=[]; $ids[0]=$bulk->insertOne($namespace,$source[101]);
$bulk->updateOne($namespace,['_id'=>501],['$set'=>['price'=>550]]); // index 1 is not an insert
$ids[2]=$bulk->insertOne($namespace,$source[102]);
$result=mr121Run($manager,$bulk); $insert=mr121Plain($result->getInsertResults());
$operationToLine=[0=>101,2=>102]; $rows=[];
foreach ($operationToLine as $operation=>$line) {
    $rows[]=[$line,$operation,$source[$line]['sku'],$insert[$operation]['insertedId'],'подтверждено'];
}
$saved=mr121Find($manager,$namespace);
$checks=['acknowledged'=>$result->isAcknowledged(),'inserted_two'=>$result->getInsertedCount()===2,
    'global_indices'=>array_keys($insert)===[0,2],
    'ids_match_enqueue'=>$insert[0]['insertedId']===$ids[0] && $insert[2]['insertedId']===$ids[2],
    'actual_documents'=>$saved===[['_id'=>501,'sku'=>'CUP','price'=>550],['_id'=>502,'sku'=>'LAMP','price'=>1200]],
    'no_upserts'=>$result->getUpsertedCount()===0];
$details=['Строки прайса'=>$source,'Операции вставки'=>$insert,'После пакета'=>$saved,
    'Отдельная операция 1'=>mr121Plain($result->getUpdateResults())];
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-import-ledger',['Строка прайса', 'Индекс операции', 'Артикул', 'Вставленный _id', 'Статус'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-import-ledger: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-import-ledger: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Добавьте третью строку прайса после ещё одного update. Запишите индекс новой вставки в operationToLine и проверьте, что квитанция использует именно его. Не перенумеровывайте карту подробных результатов.

Отличить прежнюю цену, изменённую цену и ненайденный товар

Поставщик прислал новые цены, но итог «изменено 1 из 3» непонятен. Нужно отличить товар с уже правильной ценой от отсутствующего артикула и от действительно изменившегося товара.

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

В базе CUP=500, LAMP=1000. Запросы требуют CUP=500, LAMP=1200 и MISSING=900. upsert не включён: отсутствующий товар не должен появиться без полной карточки.

Путь данных и результата
  1. CUP найден, но цена 500 уже стоит
  2. LAMP найден: 1000 → 1200
  3. MISSING не найден: 0 совпадений
  4. Сводка: matched=2, modified=1
  5. Подробности по индексам 0/1/2 объясняют все три строки

Короткое действие

$bulk->updateOne($namespace, ['sku'=>'CUP'], ['$set'=>['price'=>500]]);
// При прежней цене 500 операция успешна: matchedCount=1, modifiedCount=0.

Видимый результат

ОперацияАртикулЖелаемая ценаMatchedModifiedВывод
0CUP50010цена уже такая
1LAMP120011цена изменена
2MISSING90000товар не найден

Разбор результата

Matched — сколько документов подошло под фильтры обновлений, modified — сколько существующих документов действительно изменено. Успешный update может ничего не поменять. При отсутствии товара matchedCount=0, modifiedCount=0, но это также не write error: команда корректна и сервер её выполнил. Поэтому такую строку ищут в getUpdateResults, а не getWriteErrors. Каждый подробный счётчик здесь декодирован из BSON Int64; helper переводит только маленькие значения учебного набора в PHP int. Нельзя тем же приведением бездумно обрабатывать произвольные 64-битные значения на любой платформе.

Ошибки и границы применения

Сводные 2/1 не говорят, какие артикулы отличаются. Для построчного отчёта запрашивают verboseResults до исполнения. У updateMany одно поручение может дать matched больше 1. Нулевой modified сам по себе не означает сбой, потерю соединения или ошибку прав.

Полная программа, helper и архив
Отличить прежнюю цену, изменённую цену и ненайденный товарСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
mr121Seed($manager,$namespace,[['_id'=>1,'sku'=>'CUP','price'=>500],['_id'=>2,'sku'=>'LAMP','price'=>1000]]);
$requests=[['sku'=>'CUP','price'=>500],['sku'=>'LAMP','price'=>1200],['sku'=>'MISSING','price'=>900]];
$bulk=new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
foreach ($requests as $request) { $bulk->updateOne($namespace,['sku'=>$request['sku']],['$set'=>['price'=>$request['price']]]); }
$result=mr121Run($manager,$bulk); $updates=mr121Plain($result->getUpdateResults()); $rows=[];
foreach ($requests as $index=>$request) {
    $matched=mr121Number($updates[$index]['matchedCount']); $modified=mr121Number($updates[$index]['modifiedCount']);
    $status=$matched===0?'товар не найден':($modified===0?'цена уже такая':'цена изменена');
    $rows[]=[$index,$request['sku'],$request['price'],$matched,$modified,$status];
}
$saved=mr121Find($manager,$namespace);
$checks=['matched_two'=>$result->getMatchedCount()===2,'modified_one'=>$result->getModifiedCount()===1,
    'all_successful_operations'=>array_keys($updates)===[0,1,2],
    'zero_match_not_error'=>mr121Number($updates[2]['matchedCount'])===0,
    'actual_prices'=>$saved===[['_id'=>1,'sku'=>'CUP','price'=>500],['_id'=>2,'sku'=>'LAMP','price'=>1200]],
    'no_upserts'=>$result->getUpsertedCount()===0];
$details=['Запрошенные цены'=>$requests,'Подробные результаты'=>$updates,'Фактические товары'=>$saved];
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-price-noop',['Операция', 'Артикул', 'Желаемая цена', 'Matched', 'Modified', 'Вывод'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-price-noop: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-price-noop: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Поменяйте желаемую цену CUP на 600. Ожидайте общий matched=2, modified=2 и строку CUP 1/1. Затем добавьте ещё один отсутствующий артикул: сводка не увеличится, но подробный результат нового индекса должен присутствовать.

Практический вопрос: Use MongoDB field value to fill in separate blank field. Читатель получает много совпадений и мало изменений и принимает это за сбой. Наш набор цен отдельно показывает настоящее изменение, прежнее значение и отсутствие товара; не переносит синтаксис другого драйвера. Обсуждение используется для выбора задачи; контракт текущего PHP API проверен по официальным источникам.

Практический вопрос: MongoDB Bulkwrite: which queries failed at match step?. Нужно определить индекс update, который не нашёл документ. Нулевое совпадение не равно write error: современный verbose-ответ связывает matchedCount=0 с исходным поручением. Предложение искать такие случаи только в writeErrors здесь неприменимо. Обсуждение используется для выбора задачи; контракт текущего PHP API проверен по официальным источникам.

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

При синхронизации каталога одни артикулы уже есть, другие появляются впервые. Экран импорта должен показать новые карточки и их идентификаторы, не объявляя обновлённые товары «вставленными».

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

В базе CUP/_id=601/price=500. Требуются CUP=550 и LAMP=1200. На sku создан уникальный индекс. Для нового LAMP задан _id=602 через $setOnInsert; существующему CUP идентификатор не меняется.

Путь данных и результата
  1. Фильтр sku=CUP находит существующий товар
  2. CUP.price становится 550
  3. Фильтр sku=LAMP не находит товар
  4. upsert создаёт LAMP с _id=602
  5. getUpdateResults[1].upsertedId обозначает новую карточку
  6. Сводка: inserted=0, upserted=1, matched=1, modified=1

Короткое действие

$bulk->updateOne($namespace, ['sku'=>'LAMP'],
    ['$set'=>['price'=>1200], '$setOnInsert'=>['_id'=>602]], ['upsert'=>true]);
// После исполнения upsertedId находится в getUpdateResults(), не getInsertResults().

Видимый результат

ОперацияАртикулРезультатНовый upsertedId
0CUPобновлёнNULL
1LAMPсоздан602

Разбор результата

Upsert — обновление с разрешением вставки, если фильтр ничего не нашёл. Такая новая запись учитывается в getUpsertedCount, а не getInsertedCount. Для её идентификатора у BulkWriteCommandResult нет getUpsertedIds: нужно читать upsertedId в подробном результате обновления. Ветка определяется наличием этого поля; не стоит угадывать создание по modifiedCount=0. $setOnInsert применяется только при вставке. В таблице NULL у CUP означает, что новый идентификатор не выдавался; фактический прежний _id=601 есть в контрольном чтении.

Ошибки и границы применения

Уникальность sku задаётся отдельным индексом, а не самим upsert. Конкурирующие записи и сложные условия фильтра требуют отдельной стратегии; этот пример не проверяет гонку. Подробные поля отдельных ответов не следует механически суммировать вместо официальных агрегатных методов. В частности, ветку upsert надёжнее распознавать по upsertedId.

Полная программа, helper и архив
Показать отдельно созданный товар и изменение существующегоСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
mr121Seed($manager,$namespace,[['_id'=>601,'sku'=>'CUP','price'=>500]]);
mr121Command($manager,$database,['createIndexes'=>$collection,'indexes'=>[['key'=>['sku'=>1],'name'=>'sku_unique','unique'=>true]]],true);
$bulk=new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
$bulk->updateOne($namespace,['sku'=>'CUP'],['$set'=>['price'=>550]],['upsert'=>true]);
$bulk->updateOne($namespace,['sku'=>'LAMP'],['$set'=>['price'=>1200],'$setOnInsert'=>['_id'=>602]],['upsert'=>true]);
$result=mr121Run($manager,$bulk); $updates=mr121Plain($result->getUpdateResults()); $rows=[];
foreach (['CUP','LAMP'] as $index=>$sku) {
    $created=array_key_exists('upsertedId',$updates[$index]);
    $rows[]=[$index,$sku,$created?'создан':'обновлён',$created?$updates[$index]['upsertedId']:null];
}
$saved=mr121Find($manager,$namespace);
$checks=['one_upsert'=>$result->getUpsertedCount()===1,'not_insert_count'=>$result->getInsertedCount()===0,
    'matched_existing'=>$result->getMatchedCount()===1,'modified_existing'=>$result->getModifiedCount()===1,
    'id_only_for_created'=>!array_key_exists('upsertedId',$updates[0]) && $updates[1]['upsertedId']===602,
    'actual_products'=>$saved===[['_id'=>601,'sku'=>'CUP','price'=>550],['_id'=>602,'sku'=>'LAMP','price'=>1200]]];
$details=['Результаты обновлений'=>$updates,'Сохранённые товары'=>$saved,
    'Сводка'=>['inserted'=>$result->getInsertedCount(),'upserted'=>$result->getUpsertedCount(),'matched'=>$result->getMatchedCount(),'modified'=>$result->getModifiedCount()]];
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-upsert-ids',['Операция', 'Артикул', 'Результат', 'Новый upsertedId'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-upsert-ids: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-upsert-ids: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Повторите такой прайс новым объектом BulkWriteCommand в той же коллекции. Новых документов быть не должно: upserted=0, прежние цены дают modified=0. Сравните существующие _id до и после.

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

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

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

CART-A/_id=11 и CART-B/_id=12 имеют state=expired; CART-C/_id=13 — active. Сначала удаляем CART-A только при expired, затем остальные expired, затем проверяем отсутствие _id=99.

Путь данных и результата
  1. Операция 0: CART-A соответствует двум условиям → удалён 1 документ
  2. Операция 1: остался только CART-B/expired → удалён 1
  3. Операция 2: _id=99 не существует → удалено 0
  4. getDeletedCount даёт 2, а не число поручений 3
  5. Ограниченное чтение показывает CART-C/active

Короткое действие

$deleted = $result->getDeleteResults();
echo $result->getDeletedCount(); // 2 документа, хотя операций удаления было 3.

Видимый результат

ОперацияКритерий / сохранённая строкаДокументов
0CART-A только expired1
1остальные expired1
2несуществующий _id=990
остатокCART-C / active1

Разбор результата

getDeleteResults содержит successful-операции удаления, включая успешное удаление нуля документов. Значение deletedCount относится к конкретному поручению, а getDeletedCount — ко всему пакету. Здесь ordered=true важен: второе поручение видит состояние после первого. Контрольное чтение ограничено собственной маленькой коллекцией и показывает остаток, а не только арифметику счётчиков.

Ошибки и границы применения

В unordered-пакете с пересекающимися фильтрами нельзя обещать распределение 1/1 по операциям: порядок может отличаться. Нулевое удаление не является исключением. На рабочем каталоге фильтр удаления должен выражать разрешённый набор, а не быть пустым из удобства. В примере все данные созданы самой программой.

Полная программа, helper и архив
Разобрать удаления по операциям и показать оставшиеся данныеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
mr121Seed($manager,$namespace,[['_id'=>11,'sku'=>'CART-A','state'=>'expired'],['_id'=>12,'sku'=>'CART-B','state'=>'expired'],['_id'=>13,'sku'=>'CART-C','state'=>'active']]);
$bulk=new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
$bulk->deleteOne($namespace,['_id'=>11,'state'=>'expired']);
$bulk->deleteMany($namespace,['state'=>'expired']);
$bulk->deleteOne($namespace,['_id'=>99]);
$result=mr121Run($manager,$bulk); $deleted=mr121Plain($result->getDeleteResults()); $rows=[];
foreach (['CART-A только expired','остальные expired','несуществующий _id=99'] as $index=>$reason) {
    $rows[]=[$index,$reason,mr121Number($deleted[$index]['deletedCount'])];
}
$saved=mr121Find($manager,$namespace);
$rows[]=['остаток',$saved[0]['sku'].' / '.$saved[0]['state'],count($saved)];
$checks=['deleted_two'=>$result->getDeletedCount()===2,'three_results'=>array_keys($deleted)===[0,1,2],
    'first_one'=>mr121Number($deleted[0]['deletedCount'])===1,'remaining_one'=>mr121Number($deleted[1]['deletedCount'])===1,
    'missing_zero'=>mr121Number($deleted[2]['deletedCount'])===0,
    'active_survives'=>$saved===[['_id'=>13,'sku'=>'CART-C','state'=>'active']]];
$details=['Результаты удаления'=>$deleted,'Фактический остаток'=>$saved];
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-delete-audit',['Операция', 'Критерий / сохранённая строка', 'Документов'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-delete-audit: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-delete-audit: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Добавьте ещё одну expired-корзину. Первая операция останется равной 1, вторая станет 2, общий deleted станет 3, активная корзина сохранится. Затем поменяйте state у CART-A на active и проследите условие первой операции.

Не путать отсутствие подробного ответа с отсутствием выполненных операций

Иногда нужен только итог загрузки, а иногда — квитанция по каждому поручению. Нужно заранее выбрать подробность ответа и не принять NULL за «сервер ничего не сделал».

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

Два последовательных пакета делают одинаковую цепочку с разными _id: вставить временный товар по цене 100, изменить цену на 150, удалить товар. У первого verboseResults=false, у второго true.

Путь данных и результата
  1. Оба пакета подтверждены сервером
  2. В обоих inserted=1, modified=1, deleted=1
  3. При false три get*Results возвращают NULL
  4. При true подробные документы имеют ключи 0, 1 и 2
  5. Конечное чтение пусто: временные товары удалены

Короткое действие

$bulk = new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
// Выбор сделан ДО исполнения. По умолчанию false: сводка есть, get*Results() вернут NULL.

Видимый результат

verboseResultsВставленоИзмененоУдаленоПодробные результаты
false111три NULL: подробности не запрошены
true111индексы: insert 0, update 1, delete 2

Разбор результата

verboseResults является опцией конструктора пакета и по умолчанию выключен. После исполнения включить подробности задним числом нельзя. isAcknowledged и сводные счётчики доступны и при false. С true getInsertResults/getUpdateResults/getDeleteResults возвращают BSON Document, а не уже готовый PHP array. Наш helper вызывает toPHP с нейтральной картой. Если тип операции не встречался, запрошенный подробный документ может быть пустым; это отличается от NULL при незапрошенных деталях.

Ошибки и границы применения

Детальный ответ требует передачи и хранения большего количества данных; его выбирают по нуждам отчёта, а не автоматически для каждого массового процесса. Это не тест скорости или лимита размера. NULL у get*Results не является неизвестным исходом сетевой ошибки: здесь есть успешный подтверждённый сводный ответ. Отдельно при w=0 методы счётчиков/деталей недоступны и бросают LogicException.

Полная программа, helper и архив
Не путать отсутствие подробного ответа с отсутствием выполненных операцийСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
$rows=[]; $details=[]; $mapsCorrect=true; $countsCorrect=true;
foreach ([false,true] as $verbose) {
    $bulk=new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>$verbose]);
    $id=$verbose?702:701;
    $bulk->insertOne($namespace,['_id'=>$id,'sku'=>'TEMP','price'=>100]);
    $bulk->updateOne($namespace,['_id'=>$id],['$set'=>['price'=>150]]);
    $bulk->deleteOne($namespace,['_id'=>$id]);
    $result=mr121Run($manager,$bulk);
    $insert=mr121Plain($result->getInsertResults()); $update=mr121Plain($result->getUpdateResults()); $delete=mr121Plain($result->getDeleteResults());
    $mapsCorrect=$mapsCorrect && ($verbose
        ? array_keys($insert)===[0] && array_keys($update)===[1] && array_keys($delete)===[2]
        : $insert===null && $update===null && $delete===null);
    $countsCorrect=$countsCorrect && $result->isAcknowledged() && $result->getInsertedCount()===1
        && $result->getMatchedCount()===1 && $result->getModifiedCount()===1 && $result->getDeletedCount()===1;
    $rows[]=[$verbose?'true':'false',$result->getInsertedCount(),$result->getModifiedCount(),$result->getDeletedCount(),
        $verbose?'индексы: insert 0, update 1, delete 2':'три NULL: подробности не запрошены'];
    $details[$verbose?'verbose=true':'verbose=false']=['insert'=>$insert,'update'=>$update,'delete'=>$delete];
}
$saved=mr121Find($manager,$namespace);
$checks=['maps_follow_option'=>$mapsCorrect,'same_actual_counts'=>$countsCorrect,'no_documents_remain'=>$saved===[]];
$details['Конечные документы']=$saved;
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-verbose-switch',['verboseResults', 'Вставлено', 'Изменено', 'Удалено', 'Подробные результаты'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-verbose-switch: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-verbose-switch: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Уберите delete из обоих пакетов. Измените проверку остатка на два документа и сравните пустой запрошенный документ удаления с NULL незапрошенного. Суммарный deleted должен быть 0.

Построить отчёт после остановки на первой ошибке

Пакет упал на дубликате. Оператору нужно знать, что уже записано, какая строка отвергнута и до какой сервер вообще не дошёл. Ошибка пакета не должна превращать успешную первую запись в сообщение «всё отменено».

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

Уже существует _id=900/EXISTS. В ordered-пакете операции: 0 вставляет FIRST/901, 1 пытается занять существующий 900, 2 должна вставить LATER/902. В отдельной маленькой проверке ошибка возникает уже на операции 0.

Путь данных и результата
  1. Операция 0 создаёт FIRST/901
  2. Операция 1 получает duplicate key 11000
  3. Ordered останавливает дальнейшие операции
  4. PartialResult подтверждает одну вставку; WriteErrors содержит ключ 1
  5. LATER/902 отсутствует в фактических данных
  6. Отдельная первая ошибка даёт PartialResult=NULL

Короткое действие

catch (MongoDB\Driver\Exception\BulkWriteCommandException $error) {
    $partial = $error->getPartialResult(); // объект или NULL
    $errors = $error->getWriteErrors();    // ключ 1: ошибка операции 1
} // Фрагмент обработчика после try с executeBulkWriteCommand.

Видимый результат

ОперацияАртикул входа_idРешение по подтверждённому ответу
0FIRST901вставлен
1CONFLICT900отказ 11000
2LATER902не выполнялся после ошибки
отдельный пакетAGAIN900partialResult = NULL

Разбор результата

BulkWriteCommandException предоставляет разные уровни информации. getWriteErrors — ошибки отдельных поручений; getErrorReply — необязательный ответ об ошибке всей команды; getWriteConcernErrors — список проблем подтверждения; getPartialResult — подтверждённые успехи, если они известны. В данном сценарии верхний ответ NULL, список concern пуст, частичный результат содержит индекс 0. Другие коды и состояния не маскируются: helper повторно выбрасывает неожиданное исключение. Отдельная операция с ошибкой до любого успеха показывает, почему partial нужно проверять на NULL прежде, чем вызывать getInsertedCount.

Ошибки и границы применения

NULL partial означает отсутствие подтверждённого успешного результата, а не универсальное доказательство «в базе точно ничего не произошло». В нашем известном дубликате исход дополнительно проверен чтением. При неизвестном сетевом исходе такую уверенность делать нельзя. Ordered сохраняет последовательность и останавливается, но не откатывает ранние операции.

Полная программа, helper и архив
Построить отчёт после остановки на первой ошибкеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
mr121Seed($manager,$namespace,[['_id'=>900,'sku'=>'EXISTS','price'=>100]]);
$bulk=new MongoDB\Driver\BulkWriteCommand(['ordered'=>true,'verboseResults'=>true]);
$source=[['_id'=>901,'sku'=>'FIRST','price'=>200],['_id'=>900,'sku'=>'CONFLICT','price'=>300],['_id'=>902,'sku'=>'LATER','price'=>400]];
foreach ($source as $document) { $bulk->insertOne($namespace,$document); }
try { mr121Run($manager,$bulk); throw new RuntimeException('Expected duplicate write error'); }
catch (MongoDB\Driver\Exception\BulkWriteCommandException $error) {
    $errors=mr121Duplicate($error,[1]); $partial=$error->getPartialResult();
    if ($partial===null || !$partial->isAcknowledged()) { throw $error; }
    $success=mr121Plain($partial->getInsertResults());
    $topReply=$error->getErrorReply(); $concern=$error->getWriteConcernErrors();
}
$rows=[];
foreach ($source as $index=>$document) {
    $status=isset($success[$index])?'вставлен':(isset($errors[$index])?'отказ 11000':'не выполнялся после ошибки');
    $rows[]=[$index,$document['sku'],$document['_id'],$status];
}
$firstFailure=new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
$firstFailure->insertOne($namespace,['_id'=>900,'sku'=>'AGAIN']); $noPartial=false;
try { mr121Run($manager,$firstFailure); throw new RuntimeException('Expected first-operation duplicate'); }
catch (MongoDB\Driver\Exception\BulkWriteCommandException $error) {
    mr121Duplicate($error,[0]); $noPartial=$error->getPartialResult()===null;
}
$rows[]=['отдельный пакет','AGAIN',900,$noPartial?'partialResult = NULL':'unexpected'];
$saved=mr121Find($manager,$namespace);
$checks=['only_first_inserted'=>$partial->getInsertedCount()===1 && array_keys($success)===[0],
    'one_indexed_error'=>array_keys($errors)===[1],'no_top_level_error'=>$topReply===null,
    'no_write_concern_errors'=>$concern===[],'null_when_no_success'=>$noPartial,
    'actual_partial_state'=>$saved===[['_id'=>900,'sku'=>'EXISTS','price'=>100],['_id'=>901,'sku'=>'FIRST','price'=>200]]];
$details=['Успешные индексы'=>$success,'Коды ошибок'=>[1=>$errors[1]->getCode()],
    'getErrorReply'=>$topReply,'getWriteConcernErrors'=>$concern,'Фактические документы'=>$saved];
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-ordered-stop',['Операция', 'Артикул входа', '_id', 'Решение по подтверждённому ответу'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-ordered-stop: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-ordered-stop: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Поместите дубликат в начало основного пакета. Ожидайте отсутствие FIRST/LATER, ошибку индекса 0 и NULL partial. Затем удалите конфликт из входа: все три операции нужно создавать заново в новом пакете.

Разделить успешные строки и несколько ошибок unordered-пакета

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

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

Существуют LOCKED-A/_id=10 и LOCKED-B/_id=20. Четыре входные строки: новый NEW-A/30, конфликт BAD-A/10, новый NEW-B/40, конфликт BAD-B/20. Все действия — независимые вставки.

Путь данных и результата
  1. Unordered разрешает продолжить после индивидуального отказа
  2. Успешные индексы 0 и 2 получают _id 30 и 40
  3. Ошибочные индексы 1 и 3 получают код 11000
  4. Отчёт соединяет две карты по исходному индексу
  5. Старые LOCKED-A/B остаются неизменными

Короткое действие

foreach ($error->getWriteErrors() as $operationIndex => $writeError) {
    echo $operationIndex, ': ', $writeError->getCode(), "\n";
} // 1: 11000 и 3: 11000. Индексы исходного пакета сохранены.

Видимый результат

Индекс операцииАртикул входаПодтверждённый исход_id успеха / индекс ошибки
0NEW-Aподтверждён insert30
1BAD-Aотказ 110001
2NEW-Bподтверждён insert40
3BAD-Bотказ 110003

Разбор результата

ordered=false означает продолжение обработки после индивидуальной ошибки, а не её игнорирование. Весь вызов всё равно бросает исключение. В нём частичный результат описывает успешные вставки, а getWriteErrors сохраняет исходные индексы даже при возможном ином порядке исполнения. Мы сортируем ключи только для читаемого отчёта; это не утверждение о времени выполнения. В колонке последнего значения намеренно подписано: для успеха показан _id, для отказа — индекс WriteError. В подробностях HTML доступны вход и фактическое состояние.

Ошибки и границы применения

Индивидуальные ошибки отличаются от ошибок команды и подтверждения. Здесь wc-список пуст, что говорит лишь об этом подтверждённом запуске с w=1, а не о проверке репликации. Ошибка 11000 может означать конфликт другого уникального индекса; без сверки нельзя объявлять любую такую запись уже правильной. Не опирайтесь на порядок операций, которые должны зависеть друг от друга.

Полная программа, helper и архив
Разделить успешные строки и несколько ошибок unordered-пакетаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
mr121Seed($manager,$namespace,[['_id'=>10,'sku'=>'LOCKED-A','price'=>100],['_id'=>20,'sku'=>'LOCKED-B','price'=>200]]);
$source=[['_id'=>30,'sku'=>'NEW-A','price'=>300],['_id'=>10,'sku'=>'BAD-A','price'=>999],['_id'=>40,'sku'=>'NEW-B','price'=>400],['_id'=>20,'sku'=>'BAD-B','price'=>999]];
$bulk=new MongoDB\Driver\BulkWriteCommand(['ordered'=>false,'verboseResults'=>true]);
foreach ($source as $document) { $bulk->insertOne($namespace,$document); }
try { mr121Run($manager,$bulk); throw new RuntimeException('Expected duplicates'); }
catch (MongoDB\Driver\Exception\BulkWriteCommandException $error) {
    $errors=mr121Duplicate($error,[1,3]); $partial=$error->getPartialResult();
    if ($partial===null || !$partial->isAcknowledged()) { throw $error; }
    $success=mr121Plain($partial->getInsertResults()); ksort($success);
    $topReply=$error->getErrorReply(); $concern=$error->getWriteConcernErrors();
}
$rows=[];
foreach ($source as $index=>$document) {
    $rows[]=[$index,$document['sku'],isset($success[$index])?'подтверждён insert':'отказ 11000',
        isset($success[$index])?$success[$index]['insertedId']:$errors[$index]->getIndex()];
}
$saved=mr121Find($manager,$namespace);
$checks=['two_successes'=>$partial->getInsertedCount()===2 && array_keys($success)===[0,2],
    'two_exact_errors'=>array_keys($errors)===[1,3],'top_level_reply_absent'=>$topReply===null,
    'concern_errors_empty'=>$concern===[],
    'failed_existing_unchanged'=>$saved===[['_id'=>10,'sku'=>'LOCKED-A','price'=>100],['_id'=>20,'sku'=>'LOCKED-B','price'=>200],['_id'=>30,'sku'=>'NEW-A','price'=>300],['_id'=>40,'sku'=>'NEW-B','price'=>400]]];
$details=['Вход по индексам'=>$source,'Успешные вставки'=>$success,'Ошибка индекс→код'=>[1=>$errors[1]->getCode(),3=>$errors[3]->getCode()],
    'Верхний ответ'=>$topReply,'Ошибки подтверждения'=>$concern,'Фактическое состояние'=>$saved];
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-unordered-ledger',['Индекс операции', 'Артикул входа', 'Подтверждённый исход', '_id успеха / индекс ошибки'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-unordered-ledger: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-unordered-ledger: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Добавьте пятый независимый товар с новым _id. Он должен попасть в successes с исходным индексом 4. Затем сделайте ему конфликт и убедитесь, что в writeErrors появился ключ 4, а первые успешные записи не исчезли.

Сверить импорт по стабильному артикулу и выполнить ограниченное восстановление

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

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

В базе CUP/_id=801/price=500. Прайс содержит CUP с другим входным _id=803 и ценой 600, а также LAMP/802/900. Уникальный sku отклоняет новую CUP, но LAMP вставляется. Учебная политика после сверки: этот фиксированный прайс определяет только цену; существующий _id сохраняется.

Путь данных и результата
  1. Первый unordered insert: CUP отказ, LAMP успех
  2. Чтение подтверждает CUP=500 и LAMP=900
  3. Конфликт CUP разрешён явным правилом источника цены
  4. Новый пакет по sku задаёт абсолютные цены 600/900
  5. CUP изменён; подтверждённый LAMP совпадает и не меняется
  6. Повтор этой же формы снова даёт modified=0 и upserted=0

Короткое действие

$bulk->updateOne($namespace, ['sku'=>'CUP'],
    ['$set'=>['price'=>600]], ['upsert'=>true]);
// Только после сверки по уникальному sku и принятого правила для цены; _id сохраняется.

Видимый результат

ЭтапАртикулMatchedModifiedЦена после
восстановлениеCUP11600
восстановлениеLAMP10900
тот же прайс повторноCUP10600
тот же прайс повторноLAMP10900

Разбор результата

Это восстановление через сверку состояния, а не автоматический повтор только ошибочных индексов. Новый пакет намеренно обрабатывает обе строки прайса: для LAMP демонстрируется безопасная повторяемость именно абсолютного $set того же значения. Входной _id=803 не копируется поверх существующего CUP/801. Полная программа проверяет ожидаемое состояние перед решением; при отличии останавливается. getMatchedCount=2/getModifiedCount=1 объясняют первый этап, а 2/0 — повтор. getUpsertedCount=0 подтверждает, что оба этапа использовали прежние товары.

Ошибки и границы применения

Уникальный sku предотвращает две карточки с одинаковым ключом, но не делает любой update идемпотентным. $inc повторно увеличил бы значение; доставка письма или оплата требуют отдельного контракта и ключа идемпотентности. Между сверкой и записью в рабочей системе возможна гонка: используйте версию/ожидаемое значение в фильтре или соответствующую транзакцию. Здесь изолированный последовательный набор и нет сетевого сбоя. При неизвестном исходе нельзя считать каждую неподтверждённую строку неисполненной.

Полная программа, helper и архив
Сверить импорт по стабильному артикулу и выполнить ограниченное восстановлениеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mr121-helper.php';
$runtime=mr121Runtime(); [$manager,$database]=mr121Database();
$runtime['server']=mr121Command($manager,'admin',['buildInfo'=>1])[0]['version'];
if (version_compare($runtime['server'],'8.0.0','<')) { throw new RuntimeException('MongoDB 8.0+ is required'); }
$collection='mongodb121_results_'.bin2hex(random_bytes(8)); $namespace=$database.'.'.$collection; $owned=false;
if (mr121Exists($manager,$database,$collection)) { throw new RuntimeException('Unexpected collection collision'); }
try {
    mr121Command($manager,$database,['create'=>$collection],true); $owned=true;
mr121Seed($manager,$namespace,[['_id'=>801,'sku'=>'CUP','price'=>500]]);
mr121Command($manager,$database,['createIndexes'=>$collection,'indexes'=>[['key'=>['sku'=>1],'name'=>'sku_unique','unique'=>true]]],true);
$input=[['_id'=>803,'sku'=>'CUP','price'=>600],['_id'=>802,'sku'=>'LAMP','price'=>900]];
$first=new MongoDB\Driver\BulkWriteCommand(['ordered'=>false,'verboseResults'=>true]);
foreach ($input as $document) { $first->insertOne($namespace,$document); }
try { mr121Run($manager,$first); throw new RuntimeException('Expected SKU duplicate'); }
catch (MongoDB\Driver\Exception\BulkWriteCommandException $error) {
    mr121Duplicate($error,[0]); $partial=$error->getPartialResult();
    if ($partial===null || $partial->getInsertedCount()!==1) { throw $error; }
}
$before=mr121Find($manager,$namespace);
if ($before!==[['_id'=>801,'sku'=>'CUP','price'=>500],['_id'=>802,'sku'=>'LAMP','price'=>900]]) {
    throw new RuntimeException('Unexpected reconciliation state; stop and inspect');
}
// Explicit fixture policy: this checked supplier price list is authoritative for price only.
// Do not replay inserts or copy incoming _id=803 over the existing CUP identity.
$rows=[]; $passes=[];
foreach (['восстановление','тот же прайс повторно'] as $pass) {
    $bulk=new MongoDB\Driver\BulkWriteCommand(['verboseResults'=>true]);
    foreach ($input as $document) {
        $bulk->updateOne($namespace,['sku'=>$document['sku']],['$set'=>['price'=>$document['price']]],['upsert'=>true]);
    }
    $result=mr121Run($manager,$bulk); $updates=mr121Plain($result->getUpdateResults());
    foreach ($input as $index=>$document) {
        $rows[]=[$pass,$document['sku'],mr121Number($updates[$index]['matchedCount']),mr121Number($updates[$index]['modifiedCount']),$document['price']];
    }
    $passes[]=['matched'=>$result->getMatchedCount(),'modified'=>$result->getModifiedCount(),'upserted'=>$result->getUpsertedCount(),'updates'=>$updates];
}
$after=mr121Find($manager,$namespace);
$checks=['first_partial_insert'=>$partial->getInsertedCount()===1,
    'recovery_one_change'=>$passes[0]['matched']===2 && $passes[0]['modified']===1 && $passes[0]['upserted']===0,
    'replay_no_new_change'=>$passes[1]['matched']===2 && $passes[1]['modified']===0 && $passes[1]['upserted']===0,
    'same_business_entities'=>$after===[['_id'=>801,'sku'=>'CUP','price'=>600],['_id'=>802,'sku'=>'LAMP','price'=>900]]];
$details=['Проверенный вход'=>$input,'Перед решением конфликта'=>$before,'Ответы двух новых пакетов'=>$passes,'После восстановления'=>$after,
    'Граница'=>'Изолированный последовательный пример. Не тест гонки/сетевого retry и не обещание идемпотентности произвольного update.'];
} finally {
    if ($owned) { mr121Command($manager,$database,['drop'=>$collection],true); }
}
$checks['owned_collection_removed']=!mr121Exists($manager,$database,$collection);
$details['Собственная удалённая коллекция']=$collection;
mr121Report('mongodb121-results-controlled-retry',['Этап', 'Артикул', 'Matched', 'Modified', 'Цена после'],$rows,$checks,$runtime,$details);
echo 'mongodb121-results-controlled-retry: saved result.html and result.json
';
Ожидаемый результат
mongodb121-results-controlled-retry: saved result.html and result.json

Запустите php example.php на отдельном локальном MongoDB 8.0+. Все данные, helper и README входят в ZIP; результат читается из настоящего ответа и контрольного запроса. Собственная коллекция удаляется в finally.

Открыть реальный результат · Таблица JSON

Упражнение и направление решения. Добавьте к CUP поле version и обновляйте цену с фильтром по ожидаемой версии, одновременно увеличивая version. Разберите matched=0 как конфликт состояния; не превращайте такой условный фильтр в upsert без отдельного анализа уникальности. Для повторов сохраняйте идентификатор и версию самого прайса.

Практический вопрос: How to handle duplicate error in Mongo BulkInsert retry. Повтор всей вставки после частичного успеха встречает дубликаты. Наш сценарий сначала сверяет товар по стабильному уникальному sku, явно разрешает конфликт цены и применяет абсолютную форму записи; исключения не подавляются автоматически. Обсуждение используется для выбора задачи; контракт текущего PHP API проверен по официальным источникам.

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

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

МетодПолезное действие
MongoDB\Driver\BulkWriteCommandResult::getInsertedCountПосчитать подтверждённые insert без смешивания с upsert.
MongoDB\Driver\BulkWriteCommandResult::getInsertResultsСвязать успешный insert с его исходным индексом и _id.
MongoDB\Driver\BulkWriteCommandResult::getMatchedCountПонять, сколько документов подошло под фильтры обновлений.
MongoDB\Driver\BulkWriteCommandResult::getModifiedCountОтделить фактическое изменение от успешного совпадения фильтра.
MongoDB\Driver\BulkWriteCommandResult::getUpdateResultsПолучить результат каждого update, включая no-op, нулевое совпадение и upsert.
MongoDB\Driver\BulkWriteCommandResult::getUpsertedCountПосчитать новые документы, созданные веткой upsert.
MongoDB\Driver\BulkWriteCommandResult::getDeletedCountПосчитать удалённые документы, а не число delete-поручений.
MongoDB\Driver\BulkWriteCommandResult::getDeleteResultsРазобрать, сколько удалило каждое отдельное поручение.
MongoDB\Driver\BulkWriteCommandResult::isAcknowledgedПроверить, доступны ли подтверждённые сведения о записи.
MongoDB\Driver\Exception\BulkWriteCommandException::getErrorReplyОтделить ошибку команды/связи от ошибок отдельных поручений.
MongoDB\Driver\Exception\BulkWriteCommandException::getPartialResultСохранить подтверждённые успехи, когда весь пакет завершился исключением.
MongoDB\Driver\Exception\BulkWriteCommandException::getWriteConcernErrorsПроверить отдельный слой проблем подтверждения записи.
MongoDB\Driver\Exception\BulkWriteCommandException::getWriteErrorsВернуть индивидуальные ошибки и связать их с исходными поручениями.

Спроектировать понятный журнал импорта

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

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

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

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

MongoDB\Driver\BulkWriteCommandResult::getInsertedCountПосчитать подтверждённые insert без смешивания с upsert.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getInsertedCount(): int

Посчитать подтверждённые insert без смешивания с upsert.

final public MongoDB\Driver\BulkWriteCommandResult::getInsertedCount(): int

Параметры

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

Возвращаемое значение

int: общее число вставленных всеми insert-операциями документов. Документы, созданные веткой upsert, сюда не входят.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

Результат не равен count($bulk): пакет включает разные действия, а upsert учитывается отдельно. Число не сообщает, какие строки вставлены; используйте getInsertResults и собственную карту индексов.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Сводка двух строк прайса

echo $result->getInsertedCount(); // 2

Подтверждены CUP и LAMP; отдельное изменение цены не увеличивает inserted.

Исходные данные и полная программа · Реальная таблица результата

Не потерять успех до ошибки

if ($partial !== null) { echo $partial->getInsertedCount(); } // 1

Вставка FIRST сохранена, хотя следующее поручение отказало.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::getInsertResultsСвязать успешный insert с его исходным индексом и _id.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getInsertResults(): ?MongoDB\BSON\Document

Связать успешный insert с его исходным индексом и _id.

final public MongoDB\Driver\BulkWriteCommandResult::getInsertResults(): ?MongoDB\BSON\Document

Параметры

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

Возвращаемое значение

?MongoDB\BSON\Document: подробности successful insert; ключ каждого поля — индекс операции во всём пакете, вложенное поле insertedId — идентификатор. NULL, если verboseResults не запрашивали.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

verboseResults=true задают в конструкторе до исполнения. insertedId совпадает с возвращённым insertOne идентификатором, но успех подтверждает ответ сервера. Не применяйте array_values: исчезнет адрес операции. Document — BSON-контейнер; для PHP-массива задайте toPHP с явной картой.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Вернуть идентификаторы строкам источника

$insert = mr121Plain($result->getInsertResults());
$id = $insert[2]['insertedId']; // 502

Ключ 2 относится к LAMP после промежуточного update.

Исходные данные и полная программа · Реальная таблица результата

Выделить подтверждённые успехи при отказах соседних строк

$success = mr121Plain($partial->getInsertResults());
ksort($success); // ключи 0 и 2

Два реальных новых товара отделены от ошибок индексов 1 и 3.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::getMatchedCountПонять, сколько документов подошло под фильтры обновлений.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getMatchedCount(): int

Понять, сколько документов подошло под фильтры обновлений.

final public MongoDB\Driver\BulkWriteCommandResult::getMatchedCount(): int

Параметры

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

Возвращаемое значение

int: суммарное число документов, выбранных для обновления. Совпадение не гарантирует изменение; при записи прежнего значения matched может быть больше modified.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

Для ответа «какая строка не совпала» нужна карта getUpdateResults. Ноль совпадений не попадает в getWriteErrors сам по себе. Сводка разных операций не является количеством уникальных товаров за весь пакет: один документ может участвовать в нескольких обновлениях.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Объяснить две найденные карточки из трёх

echo $result->getMatchedCount(); // 2

CUP и LAMP найдены; MISSING — нет.

Исходные данные и полная программа · Реальная таблица результата

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

$matched = $result->getMatchedCount(); // 2 на каждом согласованном проходе

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

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::getModifiedCountОтделить фактическое изменение от успешного совпадения фильтра.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getModifiedCount(): int

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

final public MongoDB\Driver\BulkWriteCommandResult::getModifiedCount(): int

Параметры

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

Возвращаемое значение

int: число существующих документов, изменённых обновлениями пакета. Если новое значение совпадает с прежним, modified может быть меньше matched.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

Ноль не означает неуспех команды. Используйте его вместе с matched, upserted и подробностями операции. Этот счётчик не подтверждает внешние действия приложения и не заменяет бизнес-проверку входа.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Найти единственную реальную смену цены

echo $result->getModifiedCount(); // 1

У LAMP цена выросла, CUP уже имел нужную цену.

Исходные данные и полная программа · Реальная таблица результата

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

$modified = $result->getModifiedCount(); // сначала 1, затем 0

Второй новый пакет того же прайса уже ничего не меняет.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::getUpdateResultsПолучить результат каждого update, включая no-op, нулевое совпадение и upsert.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getUpdateResults(): ?MongoDB\BSON\Document

Получить результат каждого update, включая no-op, нулевое совпадение и upsert.

final public MongoDB\Driver\BulkWriteCommandResult::getUpdateResults(): ?MongoDB\BSON\Document

Параметры

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

Возвращаемое значение

?MongoDB\BSON\Document: карта исходных индексов успешных update-операций. Внутри matchedCount и modifiedCount; у создавшего документ upsert присутствует upsertedId. NULL при незапрошенном verboseResults.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

Подробные счётчики представлены BSON Int64. Не суммируйте произвольные вложенные поля как замену агрегатным методам: проверяйте наличие upsertedId и используйте документированный общий счётчик для сводки. getUpdateResults есть даже у операции с matchedCount=0, если verbose включён.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Показать причину по каждому артикулу

$updates = mr121Plain($result->getUpdateResults());
$matched = mr121Number($updates[2]['matchedCount']); // 0

MISSING не найден; это успешный ответ операции, а не ошибка записи.

Исходные данные и полная программа · Реальная таблица результата

Получить новый _id без выдуманного getUpsertedIds

$updates = mr121Plain($result->getUpdateResults());
$newId = $updates[1]['upsertedId']; // 602

Только LAMP создан; CUP сохраняет прежнюю идентичность.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::getUpsertedCountПосчитать новые документы, созданные веткой upsert.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getUpsertedCount(): int

Посчитать новые документы, созданные веткой upsert.

final public MongoDB\Driver\BulkWriteCommandResult::getUpsertedCount(): int

Параметры

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

Возвращаемое значение

int: суммарное число документов, созданных upsert-операциями пакета, когда подходящего существующего документа не было.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

Не путайте разрешение upsert=true с фактическим созданием. Для идентификатора нужен upsertedId из getUpdateResults. В getInsertedCount upsert-вставки не входят; полезная сводка должна показывать оба вида создания отдельно.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Разделить новую LAMP и изменённую CUP

echo $result->getUpsertedCount(); // 1

Обновление существующей CUP не увеличивает счётчик создания.

Исходные данные и полная программа · Реальная таблица результата

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

$created = $result->getUpsertedCount(); // 0 на обоих проходах

Уникальные sku ведут к двум уже существующим товарам.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::getDeletedCountПосчитать удалённые документы, а не число delete-поручений.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getDeletedCount(): int

Посчитать удалённые документы, а не число delete-поручений.

final public MongoDB\Driver\BulkWriteCommandResult::getDeletedCount(): int

Параметры

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

Возвращаемое значение

int: общее число документов, удалённых всеми delete-операциями данного исполнения.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

deleteMany может удалить несколько документов, deleteOne — не более одного. Ноль допустим и не равен ошибке. Факт удаления относится к фильтру исполненного запроса, поэтому храните его назначение рядом с индексом операции.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Отчёт очистки просроченных корзин

echo $result->getDeletedCount(); // 2

Три поручения дали удаления 1, 1 и 0; активная корзина осталась.

Исходные данные и полная программа · Реальная таблица результата

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

echo $result->getDeletedCount(); // 1 при false и true

В обоих пакетах временная карточка реально удалена.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::getDeleteResultsРазобрать, сколько удалило каждое отдельное поручение.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::getDeleteResults(): ?MongoDB\BSON\Document

Разобрать, сколько удалило каждое отдельное поручение.

final public MongoDB\Driver\BulkWriteCommandResult::getDeleteResults(): ?MongoDB\BSON\Document

Параметры

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

Возвращаемое значение

?MongoDB\BSON\Document: карта успешных delete-операций по исходным индексам, каждая содержит deletedCount. NULL, если verboseResults не был включён.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах; LogicException, если запись не подтверждена (w=0). Перед чтением проверяют isAcknowledged().

Не приписывайте NULL нулевому удалению: это разные состояния. BSON Int64 счётчика преобразуют осознанно. Для пересекающихся фильтров unordered-пакета нельзя обещать то же распределение по операциям, что в ordered.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Различить адресное и групповое удаление

$deletes = mr121Plain($result->getDeleteResults());
$deleted = mr121Number($deletes[2]['deletedCount']); // 0

Отсутствующий _id=99 даёт подробный успешный результат с нулём.

Исходные данные и полная программа · Реальная таблица результата

Проверить общий индекс после insert и update

$deletes = mr121Plain($result->getDeleteResults());
// При true единственный ключ удаления — 2; при false весь результат NULL.

Индекс не перенумеровывается отдельно внутри группы delete.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\BulkWriteCommandResult::isAcknowledgedПроверить, доступны ли подтверждённые сведения о записи.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\BulkWriteCommandResult::isAcknowledged(): bool

Проверить, доступны ли подтверждённые сведения о записи.

final public MongoDB\Driver\BulkWriteCommandResult::isAcknowledged(): bool

Параметры

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

Возвращаемое значение

bool: true означает, что запись подтверждена согласно выбранному write concern; false означает неподтверждённый режим.

Ошибки и ограничения

InvalidArgumentException при неверных аргументах вызова. У метода нет параметров.

Другие методы Result бросают LogicException для w=0. В этой главе w=1 и false не симулируется. Подтверждение не означает атомарность всех операций, гарантированную репликацию или отсутствие индивидуальных ошибок в целом пакете.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Не выдавать квитанцию только по локально известному _id

if (!$result->isAcknowledged()) {
    throw new RuntimeException('Нет подтверждённой квитанции');
}

Таблица источника строится после подтверждения сервера с w=1.

Исходные данные и полная программа · Реальная таблица результата

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

if ($partial === null || !$partial->isAcknowledged()) { throw $error; }

Отказ всего пакета совместим с подтверждённым успехом раннего поручения.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\Exception\BulkWriteCommandException::getErrorReplyОтделить ошибку команды/связи от ошибок отдельных поручений.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\Exception\BulkWriteCommandException::getErrorReply(): ?MongoDB\BSON\Document

Отделить ошибку команды/связи от ошибок отдельных поручений.

final public MongoDB\Driver\Exception\BulkWriteCommandException::getErrorReply(): ?MongoDB\BSON\Document

Параметры

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

Возвращаемое значение

?MongoDB\BSON\Document: доступный ответ верхнего уровня об ошибке взаимодействия с сервером или исполнения команды; может быть NULL при индивидуальных write errors.

Ошибки и ограничения

Отдельные специфические исключения при чтении этого свойства официальной страницей не перечислены. Это инспекция уже полученного исключения; она не выполняет серверную запись.

Ненулевой ответ можно изучать как BSON Document. NULL не доказывает успех всех операций и сам по себе не классифицирует любой сетевой отказ. В примерах нет failpoint или искусственного серверного сбоя; они реально проверяют отсутствие верхней ошибки при известном индивидуальном конфликте.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Проверить классификацию одного дубликата

$topReply = $error->getErrorReply(); // NULL в этом запуске

Ошибка индекса 1 приходит через WriteError, верхнего ответа об ошибке команды нет.

Исходные данные и полная программа · Реальная таблица результата

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

$topReply = $error->getErrorReply(); // NULL при двух индивидуальных 11000

Карта writeErrors и подтверждённые successes рассматриваются отдельно.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\Exception\BulkWriteCommandException::getPartialResultСохранить подтверждённые успехи, когда весь пакет завершился исключением.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\Exception\BulkWriteCommandException::getPartialResult(): ?MongoDB\Driver\BulkWriteCommandResult

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

final public MongoDB\Driver\Exception\BulkWriteCommandException::getPartialResult(): ?MongoDB\Driver\BulkWriteCommandResult

Параметры

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

Возвращаемое значение

?MongoDB\Driver\BulkWriteCommandResult: результат известных успешных подтверждённых операций. NULL, если нельзя установить хотя бы один такой успех.

Ошибки и ограничения

Отдельные специфические исключения при чтении этого свойства официальной страницей не перечислены. Это инспекция уже полученного исключения; она не выполняет серверную запись.

Нельзя бездумно вызывать метод результата через возможный NULL или заменять неизвестный исход на нулевую запись. Для повторного исполнения создают новый BulkWriteCommand; сам partial ничего не повторяет и не отменяет.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

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

$partial = $error->getPartialResult();
if ($partial !== null) { $inserted = $partial->getInsertedCount(); }

Основной пакет даёт одну вставку; отдельный first-error — NULL.

Исходные данные и полная программа · Реальная таблица результата

Учесть два успеха рядом с двумя отказами

$partial = $error->getPartialResult();
$successes = mr121Plain($partial->getInsertResults()); // после проверки NULL

NEW-A и NEW-B сохранены; исключение не откатило их.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\Exception\BulkWriteCommandException::getWriteConcernErrorsПроверить отдельный слой проблем подтверждения записи.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\Exception\BulkWriteCommandException::getWriteConcernErrors(): array

Проверить отдельный слой проблем подтверждения записи.

final public MongoDB\Driver\Exception\BulkWriteCommandException::getWriteConcernErrors(): array

Параметры

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

Возвращаемое значение

array с объектами MongoDB\Driver\WriteConcernError. Список может содержать несколько элементов, если для пакета потребовалось несколько серверных команд; пустой массив означает, что такие ошибки не представлены.

Ошибки и ограничения

Отдельные специфические исключения при чтении этого свойства официальной страницей не перечислены. Это инспекция уже полученного исключения; она не выполняет серверную запись.

Элементы списка — не WriteError отдельных индексов. Проблема подтверждения не является автоматическим откатом выполненной записи. На одиночном стенде не создаётся искусственный дефицит реплик; пустые ответы здесь не считаются тестом поведения при реальной write concern ошибке.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

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

$concern = $error->getWriteConcernErrors();
if ($concern !== []) { throw $error; }

Для показанного ordered-конфликта список действительно пуст.

Исходные данные и полная программа · Реальная таблица результата

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

$concern = $error->getWriteConcernErrors(); // [] в нашем w=1 запуске

Две индивидуальные ошибки не превращаются автоматически в concern errors.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

MongoDB\Driver\Exception\BulkWriteCommandException::getWriteErrorsВернуть индивидуальные ошибки и связать их с исходными поручениями.

ext-mongodb 2.1.0; MongoDB 8.0+ · mongodb

final public MongoDB\Driver\Exception\BulkWriteCommandException::getWriteErrors(): array

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

final public MongoDB\Driver\Exception\BulkWriteCommandException::getWriteErrors(): array

Параметры

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

Возвращаемое значение

array. Ключи — исходные индексы операций BulkWriteCommand. У ordered-пакета не более одного элемента: обработка останавливается на первой индивидуальной ошибке.

Ошибки и ограничения

Отдельные специфические исключения при чтении этого свойства официальной страницей не перечислены. Это инспекция уже полученного исключения; она не выполняет серверную запись.

Ноль совпадений update не является WriteError. Проверяйте конкретный код, индекс и данные; не подавляйте все исключения как «дубликаты». Отсутствие элемента при неизвестном сетевом исходе ещё не является подтверждением успеха соответствующей строки.

Версии

Метод доступен с ext-mongodb 2.1.0. Команда bulkWrite требует MongoDB 8.0+. В этой главе программы рассчитаны на PHP 8.4+ и ext-mongodb 2.5+; это BulkWriteCommandResult/BulkWriteCommandException, не прежний WriteResult/BulkWriteException.

Найти одну строку отказа и неисполненный хвост

$errors = $error->getWriteErrors(); // ключ 1
$code = $errors[1]->getCode(); // 11000

FIRST успешен, CONFLICT отказал, LATER не исполнялся.

Исходные данные и полная программа · Реальная таблица результата

Отдать оператору оба конфликта

foreach ($error->getWriteErrors() as $index => $writeError) {
    $failed[$index] = $writeError->getCode();
} // 1=>11000, 3=>11000

Исходные индексы сохранены независимо от возможной перестановки исполнения.

Исходные данные и полная программа · Реальная таблица результата

Как различать ноль, NULL и ошибку · Как составить и отправить пакет

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

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

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

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

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