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

Дерево руководства
Глава 186 · После массивов, функций, исключений и первой выборки MongoDB
MongoDB: очередь записи, импорт CSV и квитанция по исходным строкам
От одной лампы в очереди до импорта партиями: считаем инструкции, различаем совпадения и изменения, связываем ошибки с физическими строками CSV и перечитываем каталог.
Как отличить подготовленный импорт от сохранённого каталога
Вы загружаете остатки товаров из файла и хотите выдать оператору понятную квитанцию: что подготовлено, что действительно изменилось, какую строку исправить и что теперь видно в каталоге. Начнём с одной лампы LAMP, а затем разберём смешанный импорт с пустой строкой, неверным количеством, повтором артикула и тремя партиями по два действия.
- PHP подготовил insert для LAMP с qty=4
- Очередь содержит одну инструкцию; в коллекции пока нет LAMP
- executeBulkWrite отправил инструкцию
- WriteResult подтвердил один новый документ
- Новый запрос прочитал LAMP=4 из MongoDB
Представьте список поручений кладовщику: записать поручение и выполнить его — два отдельных шага. BulkWrite похож на такой список, но хранит только инструкции в памяти процесса PHP; это не долговечная фоновая очередь заданий. При завершении процесса неотправленный объект не сохранится автоматически. MongoDB хранит документы, например одну карточку с полями _id и qty. Артикул (SKU) обозначает вид товара, qty — число штук. Одна карточка с qty=4 не превращается в четыре операции.
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert(['_id' => 'LAMP', 'qty' => 4]);
echo count($bulk); // 1Здесь $bulk — переменная с объектом, new создаёт объект, -> вызывает его метод. Квадратные скобки задают поля документа: => связывает имя поля со значением, строка LAMP заключена в кавычки, число 4 записано без них. Точка с запятой завершает инструкцию. count читает местный счётчик. В этих трёх строках ещё нет отправки. В первом самостоятельном примере ниже добавлены соединение, проверка коллекции до записи и отдельное чтение после неё.
Для этого маршрута пригодятся ключи и значения массивов, аргументы и результат функции, объект и вызов метода, try/catch и finally и документ, коллекция и фильтр MongoDB. Namespace MongoDB — адрес вида «база.коллекция», а имя PHP-класса MongoDB\Driver\BulkWrite использует пространство имён языка. В строке адреса базы разделителем служит точка, в имени PHP-класса — обратная косая черта.
Как запустить пример и прочитать его файлы
Каждый ZIP содержит самостоятельные example.php, q137-helper.php и README.txt. Для CLI нужен PHP с ext-mongodb и отдельный учебный MongoDB standalone на 127.0.0.1:27075; библиотека Composer этим восьми программам не требуется. Сверенный стенд: PHP 8.5.11, ext-mongodb 2.5.5, MongoDB 8.0.34. Это версии стенда, а не заявление о последних доступных версиях. Команды запуска и две переменные окружения указаны в README.
Функция q137Lab создаёт собственную случайно названную коллекцию в php_manual_lab, передаёт её адрес программе и удаляет только эту коллекцию в finally. Она проверяет, что до начала коллекции не было и после очистки её снова нет. q137Seed добавляет исходные карточки; q137Read выполняет настоящий запрос, устанавливает карту типов в массивы и сортирует результат по _id. В учебной выдаче предел 30 документов, а самый большой набор здесь существенно меньше. Это ограниченное чтение учебного каталога, не универсальная выгрузка любого размера.
Программа выводит таблицу в терминал, сохраняет result.html и result.json, а metrics.json фиксирует проверки запуска. В коротких фрагментах ниже $manager и $ns уже подготовлены обвязкой; для самостоятельного запуска скачивайте полный пример. После изменения данных в упражнении пересчитайте ожидаемые $checks и таблицу: намеренно изменённый результат не должен выдаваться за первоначальную проверку. Проверки HTTP-формы описаны отдельно от запусков CLI.
Поставить товар в очередь и увидеть, когда он появился в базе
На склад поступили четыре лампы. В каталоге будет один документ с артикулом LAMP и количеством 4. Подготовим запись, откроем каталог до отправки, затем отправим и прочитаем ещё раз.
- Создать пустой BulkWrite
- Добавить insert и сосчитать операции
- Отдельным чтением выяснить, есть ли LAMP
- Вызвать executeBulkWrite и прочитать остаток
Короткий фрагмент из программы
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert(['_id'=>'LAMP', 'qty'=>4]);
echo count($bulk); // 1 операция подготовлена
$result = $manager->executeBulkWrite($ns, $bulk);
echo $result->getInsertedCount(); // 1 документ созданЧто увидит оператор
| Что проверили | Результат |
|---|---|
| Операций до insert | 0 |
| Операций после insert | 1 |
| Товаров до отправки | 0 |
| Создано при отправке | 1 |
| Остаток LAMP в базе | 4 |
new создаёт объект в памяти PHP. insert добавляет описание одной операции. count вызывает метод счётчика этого объекта. Изменение в MongoDB выполняет executeBulkWrite, где $ns — имя базы и коллекции через точку. Возвращённый WriteResult содержит число созданных документов.
Границы решения
Номер операции, число документов и количество единиц товара имеют разные единицы измерения. В этой записи а) одна инструкция, б) один новый документ, в) четыре лампы. Для следующей отправки создают новый BulkWrite.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$bulk=new BulkWrite();
$empty=count($bulk);
$bulk->insert(['_id'=>'LAMP','qty'=>4]);
$queued=count($bulk);
$before=q137Read($manager,$ns,['_id'=>'LAMP']);
$result=$manager->executeBulkWrite($ns,$bulk);
$after=q137Read($manager,$ns,['_id'=>'LAMP']);
$rows=[['Операций до insert',$empty],['Операций после insert',$queued],['Товаров до отправки',count($before)],
['Создано при отправке',$result->getInsertedCount()],['Остаток LAMP в базе',$after[0]['qty']]];
$checks=['starts_empty'=>$empty===0,'one_queued'=>$queued===1,'not_yet_stored'=>$before===[],'result_type'=>$result instanceof WriteResult,
'acknowledged'=>$result->isAcknowledged(),'one_created'=>$result->getInsertedCount()===1,'read_after'=>count($after)===1 && $after[0]['qty']===4];
return [$rows,$checks];
});
q137Report('mongodb137-queue-first-send', 'Поставить товар в очередь и увидеть, когда он появился в базе', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
Операций до insert — 0
Операций после insert — 1
Товаров до отправки — 0
Создано при отправке — 1
Остаток LAMP в базе — 4
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Замените qty на 40. Какие две единицы в таблице останутся равными и почему?
Как проверить своё решение
Очередь по-прежнему содержит одну инструкцию, insertedCount равен 1, а qty после чтения станет 40. Число единиц товара хранится внутри одного документа.
Увидеть три совпадения у одной операции обновления
Редактор нажал «Показать все лампы». Три товара удовлетворяют фильтру, но один из них уже показывается. Как подписать в отчёте числа 1, 3 и 2?
- Подготовить A и C скрытыми, B видимой
- Одной инструкцией захватить все лампы
- Получить 3 совпадения и 2 изменения
- Открыть список опубликованных артикулов
Короткий фрагмент из программы
$bulk->update(['group'=>'lamp'], ['$set'=>['visible'=>true]], ['multi'=>true]);
echo count($bulk); // 1Что увидит оператор
| Что проверили | Результат |
|---|---|
| Операций в очереди | 1 |
| Подошло товаров | 3 |
| Видимость изменилась | 2 |
| Теперь показаны | A, B, C |
multi=true разрешает обработать несколько документов одной инструкцией. Количество добавленных инструкций известно до обращения к MongoDB. Сколько товаров подошло и сколько изменилось, сервер сообщает после исполнения. Кабель D вне фильтра остаётся скрытым.
Границы решения
Нельзя использовать count($bulk) как процент обработанных товаров. При широком фильтре одна инструкция может изменить тысячи документов. В учебной коллекции список явно ограничен четырьмя товарами.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$seed=q137Seed($manager,$ns,[['_id'=>'A','group'=>'lamp','visible'=>false],['_id'=>'B','group'=>'lamp','visible'=>true],
['_id'=>'C','group'=>'lamp','visible'=>false],['_id'=>'D','group'=>'cable','visible'=>false]]);
$bulk=new BulkWrite();
$bulk->update(['group'=>'lamp'],['$set'=>['visible'=>true]],['multi'=>true]);
$queued=$bulk->count();
$result=$manager->executeBulkWrite($ns,$bulk);
$shown=q137Read($manager,$ns,['visible'=>true]);
$outside=q137Read($manager,$ns,['_id'=>'D'])[0];
$ids=implode(', ',array_column($shown,'_id'));
$rows=[['Операций в очереди',$queued],['Подошло товаров',$result->getMatchedCount()],['Видимость изменилась',$result->getModifiedCount()],['Теперь показаны',$ids]];
$checks=['four_seeded'=>$seed===4,'one_action'=>$queued===1,'matched_three'=>$result->getMatchedCount()===3,
'changed_two'=>$result->getModifiedCount()===2,'shown_set'=>$ids==='A, B, C','untargeted'=>$outside['visible']===false];
return [$rows,$checks];
});
q137Report('mongodb137-queue-fanout', 'Увидеть три совпадения у одной операции обновления', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
Операций в очереди — 1
Подошло товаров — 3
Видимость изменилась — 2
Теперь показаны — A, B, C
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Сделайте все три лампы видимыми до команды. Как должны измениться matchedCount, modifiedCount и число операций?
Как проверить своё решение
Останется одна операция и три совпадения. Изменённых документов станет ноль: все три значения visible уже равны true. Повторное чтение по-прежнему вернёт A, B и C.
Разобрать три ответа на кнопку «Установить остаток»
Три строки импорта указывают одно и то же новое количество 8. У A уже 8, у B осталось 3, а X вообще нет в каталоге. Менеджеру нужны три разных подписи.
- Подготовить A и B и оставить X отсутствующим
- Выполнить каждое изменение отдельно
- Прочитать два счётчика каждой операции
- Сверить подпись с карточкой товара
Короткий фрагмент из программы
if ($result->getMatchedCount() === 0) { return 'нет товара'; }
return $result->getModifiedCount() === 0 ? 'уже актуально' : 'обновлено';Что увидит оператор
| Что проверили | Результат |
|---|---|
| A | уже актуально · в базе 8 |
| B | обновлено · в базе 8 |
| X | нет товара · в базе нет |
В этом разборе для каждой строки используется своя отправка, поэтому конкретные matched и modified можно отнести к соответствующему артикулу. У A matched=1 и modified=0. У X оба равны нулю. upsert=false запрещает создавать незнакомый артикул.
Границы решения
При отправке трёх обновлений в одном классическом BulkWrite будет единый суммарный WriteResult. Он не сообщает, какой именно строке достался ноль. Если экрану нужна индивидуальная квитанция, заранее определите, какие результаты можно связать однозначно и что следует отдельно перечитать.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$seed=q137Seed($manager,$ns,[['_id'=>'A','qty'=>8],['_id'=>'B','qty'=>3]]);
$requests=['A'=>8,'B'=>8,'X'=>8];
$rows=[];$counts=[];
foreach ($requests as $sku=>$qty) {
$bulk=new BulkWrite();
$bulk->update(['_id'=>$sku],['$set'=>['qty'=>$qty]],['upsert'=>false]);
$result=$manager->executeBulkWrite($ns,$bulk);
$matched=$result->getMatchedCount();$changed=$result->getModifiedCount();
$stored=q137Read($manager,$ns,['_id'=>$sku]);
$status=$matched===0 ? 'нет товара' : ($changed===0 ? 'уже актуально' : 'обновлено');
$rows[]=[$sku,$status.' · в базе '.($stored===[] ? 'нет' : $stored[0]['qty'])];
$counts[$sku]=[$matched,$changed];
}
$checks=['two_seeded'=>$seed===2,'three_requests'=>count($rows)===3,'already_current'=>$counts['A']===[1,0],
'really_changed'=>$counts['B']===[1,1],'no_match'=>$counts['X']===[0,0],'two_remain'=>count(q137Read($manager,$ns))===2];
return [$rows,$checks];
});
q137Report('mongodb137-queue-edit-outcomes', 'Разобрать три ответа на кнопку «Установить остаток»', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
A — уже актуально · в базе 8
B — обновлено · в базе 8
X — нет товара · в базе нет
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Замените начальное количество B на 8. У каких двух артикулов теперь совпадут и статус, и количество в базе?
Как проверить своё решение
A и B дадут matched=1, modified=0 и «уже актуально · в базе 8». X по-прежнему отсутствует и даёт 0/0. Сама нулевая modified не различает эти два случая.
В интерфейсе показано действие, а matched=1 и modified=0. Различаем совпадение фильтра и фактическое изменение с отдельным чтением результата. Реальная постановка на Stack Overflow. В исходной ситуации обработчик React отправлял значение количества при изменении состояния интерфейса. Здесь та же диагностическая потребность разобрана собственным PHP-примером: A уже имеет нужный остаток, B меняется, X отсутствует; отдельное чтение сверяет все три вывода. Этот пример объясняет ответ MongoDB и не подменяет собой исправление состояния React.
Сложить разнородные операции в одну квитанцию каталога
Импорт добавляет новый артикул, меняет остаток, снимает старый товар и ищет для обновления ещё не заведённый артикул. Менеджеру нужна короткая сводка вместо четырёх однотипных сообщений «готово».
- Поставить insert, update, delete и ещё один update
- Отправить один BulkWrite в одну коллекцию
- Разобрать счётчики по видам операций
- Заново открыть каталог KEEP и NEW
Короткий фрагмент из программы
$bulk->insert(['_id'=>'NEW', 'qty'=>6]);
$bulk->update(['_id'=>'KEEP'], ['$set'=>['qty'=>4]]);
$bulk->delete(['_id'=>'OLD'], ['limit'=>1]);Что увидит оператор
| Что проверили | Результат |
|---|---|
| Отправлено инструкций | 4 |
| Вставлено | 1 |
| Подошло под обновление | 1 |
| Изменено обновлением | 1 |
| Удалено | 1 |
| Каталог после импорта | KEEP=4, NEW=6 |
Четыре инструкции дали три фактических изменения. matched и modified описывают пересекающиеся сведения об обновлении KEEP, поэтому складывать все счётчики в общее число изменений нельзя. Количество сетевых команд тоже может отличаться от числа PHP-объектов и числа инструкций.
Границы решения
limit=1 у удаления ограничивает число удалённых документов. И в примере, и в рабочем импорте сначала выделите точный ключ товара. Несколько записей в пакете не становятся одной атомарной транзакцией.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$seed=q137Seed($manager,$ns,[['_id'=>'KEEP','qty'=>3],['_id'=>'OLD','qty'=>1]]);
$bulk=new BulkWrite(['ordered'=>true]);
$bulk->insert(['_id'=>'NEW','qty'=>6]);
$bulk->update(['_id'=>'KEEP'],['$set'=>['qty'=>4]]);
$bulk->delete(['_id'=>'OLD'],['limit'=>1]);
$bulk->update(['_id'=>'ABSENT'],['$set'=>['qty'=>7]],['upsert'=>false]);
$size=count($bulk);
$result=$manager->executeBulkWrite($ns,$bulk);
$catalog=q137Read($manager,$ns);
$items=implode(', ',array_map(fn($d)=>$d['_id'].'='.$d['qty'],$catalog));
$rows=[['Отправлено инструкций',$size],['Вставлено',$result->getInsertedCount()],['Подошло под обновление',$result->getMatchedCount()],
['Изменено обновлением',$result->getModifiedCount()],['Удалено',$result->getDeletedCount()],['Каталог после импорта',$items]];
$checks=['seed'=>$seed===2,'queued_four'=>$size===4,'one_insert'=>$result->getInsertedCount()===1,'one_match'=>$result->getMatchedCount()===1,
'one_change'=>$result->getModifiedCount()===1,'one_delete'=>$result->getDeletedCount()===1,'actual_items'=>$items==='KEEP=4, NEW=6'];
return [$rows,$checks];
});
q137Report('mongodb137-queue-mixed-receipt', 'Сложить разнородные операции в одну квитанцию каталога', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
Отправлено инструкций — 4
Вставлено — 1
Подошло под обновление — 1
Изменено обновлением — 1
Удалено — 1
Каталог после импорта — KEEP=4, NEW=6
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Оставьте OLD за пределами фильтра удаления. Проверьте deletedCount и конечный перечень артикулов.
Как проверить своё решение
Удаление по отсутствующему ключу даст deletedCount=0. В конечной коллекции будут KEEP=4, NEW=6 и OLD=1; число поставленных инструкций останется 4.
Как сохранить номер строки до фильтрации и разбиения
В CLI-сценариях строка CSV начинается заголовком action;sku;qty. Действие insert создаёт карточку, set устанавливает количество существующего товара, delete удаляет товар. Номер физической строки сохраняется до отбора; отсчёт начинается с единицы и включает заголовок. Внутренняя позиция массива и индекс операции начинаются с нуля.
| Условие входа | Что делает разбор |
|---|---|
| Пустая строка внутри файла | Сохраняет её физический номер в blanks и не создаёт операцию. |
| Неверное действие, артикул или число полей | Добавляет номер в invalid; в BulkWrite эта строка не попадёт. |
| insert/set с количеством | Допускает целое число от 0 до 9999 без знака и ведущих нулей, кроме самого 0. |
| delete с количеством | Требует пустое третье поле; количество для удаления не используется. |
| Артикул | От 1 до 15 символов; начинается заглавной латинской буквой, далее заглавные буквы, цифры или дефис. |
| Граница формата | Одна запись занимает одну физическую строку. Вложенные переносы, кавычки и разделитель внутри поля не поддерживаются. Завершающий перевод строки не создаёт новую товарную запись. |
- CSV: строка 2 NEW; строка 3 пустая; строка 4 BAD=-7; строка 5 EXIST; строка 6 TAIL
- q137Parse возвращает valid со строками 2, 5, 6; отдельно blanks=[3], invalid=[4]
- q137Dispatch принимает valid как аргумент $entries
- В foreach одна строка $row передаётся в q137Add($bulk, $row), затем та же $row записывается в $map
- MongoDB возвращает ошибку индекса 1; $map[1] содержит line=5, sku=EXIST
- q137Dispatch возвращает квитанцию; присваивание $sent=... сохраняет её для отчёта
q137Parse — обычная функция разбора, она не обращается к базе. preg_match проверяет согласованный формат артикула и количества; явное приведение (int) выполняется только после проверки. q137Add переводит допустимое действие в insert/update/delete. Для set используется $set: имя оператора стоит в одинарных кавычках, чтобы PHP не пытался подставить переменную $set. Фильтр [_id => артикул] ограничивает правку нужной карточкой. multi=false, upsert=false и limit=1 не дают этому учебному действию незаметно превратиться в массовое изменение или создание.
q137Dispatch создаёт собственные $bulk и $map для каждого вызова. Сначала q137Add успешно добавляет операцию; только затем в карту добавляется исходная строка. После executeBulkWrite функция проверяет подтверждение и возвращает суммарные inserted, matched, modified, deleted, queued и список issues. В catch она принимает только подтверждённый частичный результат с индивидуальными ошибками и без WriteConcernError; иной отказ передаёт выше. Это позволяет не подменять неизвестный исход нулём. Внутренний список ошибок обходится foreach, а исходный индекс читается методом getIndex().
array_chunk($parsed['valid'], 2) делит отобранные строки на массивы длиной не более двух. Это учебная граница приложения, выбранная для наглядного сопоставления; она не является максимальным размером пакета MongoDB. Парсер здесь работает с целой короткой строкой в памяти. Для большого производственного файла потребуются потоковое чтение, полноценный CSV-парсер и сохранение координат записи; чтение файлов и регулярные выражения разбираются отдельно.
Вернуть конфликт записи к строке файла после двух пропусков
Заголовок занимает первую строку CSV, третья строка пустая, четвёртая содержит отрицательный остаток. В пятой строке уже существующий EXIST. MongoDB сообщает индекс 1. Нужно подсветить пятую строку, а не первую или вторую.
- Нумеровать физические строки до любых пропусков
- Для присланных операций сохранить соответствие индекс → исходная строка
- Поймать частичный результат и прочитать индекс ошибки
- По карте найти строку 5 и перечитать EXIST
Короткий фрагмент из программы
// Добавлять запись в карту только вместе с успешным добавлением в BulkWrite.
$bulk->insert($document);
$sourceLines[] = $physicalLine;
// Позже в обработчике ошибок:
$line = $sourceLines[$writeError->getIndex()];Что увидит оператор
| Что проверили | Результат |
|---|---|
| Пустая строка | 3 |
| Ошибка до отправки | 4 |
| Индекс в MongoDB | 1 |
| Строка исходного CSV | 5 |
| Артикул и код | EXIST · 11000 |
| Вставлено остальных | 2 |
После фильтрации до драйвера дошли строки 2, 5 и 6. Внутри одного BulkWrite они стали операциями с индексами 0, 1 и 2. Код 11000 и отдельная карта дают ответ без разбора текста сообщения. В учебном формате одна запись занимает одну физическую строку, кавычки и переносы внутри поля не предусмотрены.
Границы решения
Если в промышленном CSV поля могут занимать несколько строк, нужен полноценный парсер CSV и сохранение координат источника при чтении. Номер записи и физическая строка файла тогда могут различаться. Пустые строки и ошибки входа не следует передавать в карту отправленных операций.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$seed=q137Seed($manager,$ns,[['_id'=>'EXIST','qty'=>1]]);
$input="action;sku;qty\ninsert;NEW;2\n\ninsert;BAD;-7\ninsert;EXIST;9\ninsert;TAIL;4";
$parsed=q137Parse($input);
$sent=q137Dispatch($manager,$ns,$parsed['valid'],false);
$issue=$sent['issues'][0];
$beforeConflict=q137Read($manager,$ns,['_id'=>'EXIST'])[0]['qty'];
$rows=[['Пустая строка',$parsed['blanks'][0]],['Ошибка до отправки',$parsed['invalid'][0]],['Индекс в MongoDB',$issue['index']],
['Строка исходного CSV',$issue['line']],['Артикул и код',$issue['sku'].' · '.$issue['code']],['Вставлено остальных',$sent['inserted']]];
$checks=['one_seed'=>$seed===1,'blanks'=>$parsed['blanks']===[3],'rejected_locally'=>$parsed['invalid']===[4],
'sent_three'=>$sent['queued']===3,'mapped_error'=>$issue===['index'=>1,'line'=>5,'sku'=>'EXIST','code'=>11000],
'unchanged'=>$beforeConflict===1,'others_written'=>$sent['inserted']===2 && count(q137Read($manager,$ns))===3];
return [$rows,$checks];
});
q137Report('mongodb137-queue-source-lines', 'Вернуть конфликт записи к строке файла после двух пропусков', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
Пустая строка — 3
Ошибка до отправки — 4
Индекс в MongoDB — 1
Строка исходного CSV — 5
Артикул и код — EXIST · 11000
Вставлено остальных — 2
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Вставьте ещё одну пустую строку перед EXIST. Что изменится — индекс MongoDB, номер строки CSV или оба числа?
Как проверить своё решение
Пустая строка не добавляется в BulkWrite: индекс ошибки останется 1. EXIST переместится с физической строки 5 на строку 6. Номер берётся из карты источника, а не вычисляется по индексу.
После большой пакетной вставки оператор видит индекс и код, но не знает, какой документ отклонён. Карта индексов связывает исходный артикул и номер CSV с WriteError без разбора текста исключения. Реальная постановка на Stack Overflow. Собственная PHP-карта добавляется синхронно с BulkWrite: после исключения getIndex связывается с исходным артикулом и физической строкой. Пустые и неверные строки не перенумеровывают источник; сообщение сервера не разбирается как строковый формат.
Распознать одинаковый индекс ошибки в двух разных партиях
В большом списке оператор дважды видит «индекс 0, код 11000». На первой странице речь идёт о DUPA, на второй — о DUPB. Присвоим партии отдельный номер и сохраним исходные строки внутри каждой.
- Из семи физических строк отобрать четыре действия
- Отправить две небольшие партии по два действия
- Сопоставить индекс 0 с локальной картой каждой партии
- Убедиться, что OKA и OKB появились
Короткий фрагмент из программы
foreach (array_chunk($validRows, 2) as $batchNumber => $entries) {
// Для каждого массива $entries создаётся собственный BulkWrite и собственная карта.
$receipt = q137Dispatch($manager, $ns, $entries, false);
}Что увидит оператор
| Что проверили | Результат |
|---|---|
| Партия 1, индекс 0 | строка 2 · DUPA |
| Партия 2, индекс 0 | строка 6 · DUPB |
| Создано товаров | 2 |
| Их артикулы | OKA, OKB |
WriteError::getIndex() относится к конкретному переданному объекту BulkWrite. После создания нового объекта отсчёт начинается с нуля. Формула «номер партии × размер + индекс» найдёт в лучшем случае позицию в отфильтрованном списке, но не исходную строку, пропущенную оператором.
Границы решения
В этом примере приложение само разбивает записи по две до отправки. Внутренняя группировка драйвера по видам операций, размерам и ограничениям сервера — другой механизм. Используйте соответствие с объектом API, которому принадлежит результат.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$seed=q137Seed($manager,$ns,[['_id'=>'DUPA','qty'=>5],['_id'=>'DUPB','qty'=>6]]);
$input="action;sku;qty\ninsert;DUPA;9\n\ninsert;OKA;1\ninsert;BAD;-1\ninsert;DUPB;9\ninsert;OKB;2";
$parsed=q137Parse($input);
$rows=[];$issues=[];$inserted=0;
foreach (array_chunk($parsed['valid'],2) as $group=>$entries) {
$receipt=q137Dispatch($manager,$ns,$entries,false);
$problem=$receipt['issues'][0];$issues[]=$problem;
$rows[]=['Партия '.($group+1).', индекс '.$problem['index'],'строка '.$problem['line'].' · '.$problem['sku']];
$inserted+=$receipt['inserted'];
}
$accepted=q137Read($manager,$ns,['_id'=>['$in'=>['OKA','OKB']]]);
$rows[]=['Создано товаров',$inserted];
$rows[]=['Их артикулы',implode(', ',array_column($accepted,'_id'))];
$checks=['two_seeded'=>$seed===2,'blank_before'=>$parsed['blanks']===[3],'bad_before'=>$parsed['invalid']===[5],
'same_indices'=>array_column($issues,'index')===[0,0],'different_source'=>array_column($issues,'line')===[2,6],
'both_duplicate'=>array_column($issues,'code')===[11000,11000],'both_survived'=>$inserted===2 && count($accepted)===2];
return [$rows,$checks];
});
q137Report('mongodb137-queue-batch-offsets', 'Распознать одинаковый индекс ошибки в двух разных партиях', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
Партия 1, индекс 0 — строка 2 · DUPA
Партия 2, индекс 0 — строка 6 · DUPB
Создано товаров — 2
Их артикулы — OKA, OKB
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Измените размер партии с 2 на 3. Выведите все полученные ошибки в цикле вместо выбора первой. Какие две исходные строки всё равно должны быть отмечены?
Как проверить своё решение
В первой партии из трёх действий конфликты получат индексы 0 и 2. Вторая партия с OKB пройдёт без ошибок. Оба сообщения должны по-прежнему указывать физические строки 2 и 6. Нужно перебирать issues: в партии может быть ноль, одна или несколько ошибок.
Нужен массовый импорт, где дубликат одной строки допускает обработку остальных. Разбираем результат каждого отправленного пакета, исключение и проверку сохранившихся записей. Реальная постановка на Stack Overflow. Собственные два пакета PHP по две операции показывают два индекса 0, относящихся к разным физическим строкам. Частичный результат и повторное чтение подтверждают сохранение OKA/OKB. Повтор неудачных действий не объявляется автоматически безопасным.
После остановки отправить только ещё не обработанный хвост
Последовательный импорт остановился в середине на попытке создать EXIST с количеством 99. В каталоге у EXIST количество 2. Поддержке нужно продолжить независимую загрузку TAIL, оставив конфликт для отдельного решения.
- Сохранить карту отправленных строк
- При подтверждённом отказе увидеть один успешно созданный товар
- Проверить конфликт и отсутствие TAIL
- Собрать оставшийся хвост в новом объекте и отправить его
Короткий фрагмент из программы
// Только для известной ошибки элемента в ordered=true.
$tail = array_slice($submitted, $writeError->getIndex() + 1);Что увидит оператор
| Что проверили | Результат |
|---|---|
| Успели вставить до отказа | 1 |
| Строка конфликта | 4 |
| Остаток конфликтного EXIST | 2 |
| Строка для отдельной отправки | 5 |
| TAIL до продолжения | 0 |
| TAIL после продолжения | 3 |
В этом пакете ordered=true, причина остановки — известная ошибка конкретного элемента, строки независимы друг от друга. Префикс уже выполнен. Хвост после индекса не был обработан; для него создаётся новая очередь. Существующий товар с другим количеством требует решения оператора.
Границы решения
Так нельзя восстанавливать пакет при разрыве связи или ошибке подтверждения записи. При ordered=false следующие действия тоже могли уже исполниться. При неоднозначном результате сначала сверяют известные ключи и состояние. Операции вроде $inc опасно повторять вслепую.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$seed=q137Seed($manager,$ns,[['_id'=>'EXIST','qty'=>2]]);
$parsed=q137Parse("action;sku;qty\ninsert;NEW;1\n\ninsert;EXIST;99\ninsert;TAIL;3");
$batch=$parsed['valid'];
$first=q137Dispatch($manager,$ns,$batch,true);
$error=$first['issues'][0];
$tail=array_slice($batch,$error['index']+1);
$existing=q137Read($manager,$ns,['_id'=>'EXIST'])[0];
$before=q137Read($manager,$ns,['_id'=>'TAIL']);
$resumed=q137Dispatch($manager,$ns,$tail,true);
$after=q137Read($manager,$ns,['_id'=>'TAIL'])[0];
$rows=[['Успели вставить до отказа',$first['inserted']],['Строка конфликта',$error['line']],['Остаток конфликтного EXIST',$existing['qty']],
['Строка для отдельной отправки',$tail[0]['line']],['TAIL до продолжения',count($before)],['TAIL после продолжения',$after['qty']]];
$checks=['seed'=>$seed===1,'prefix_done'=>$first['inserted']===1,'duplicate_known'=>$error['code']===11000 && $error['index']===1,
'conflict_not_overwritten'=>$existing['qty']===2,'tail_known'=>count($tail)===1 && $tail[0]['line']===5,
'tail_not_yet_applied'=>$before===[],'resumed_only_tail'=>$resumed['queued']===1 && $resumed['inserted']===1 && $after['qty']===3,
'prefix_still_intact'=>q137Read($manager,$ns,['_id'=>'NEW'])[0]['qty']===1];
return [$rows,$checks];
});
q137Report('mongodb137-queue-resume-tail', 'После остановки отправить только ещё не обработанный хвост', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
Успели вставить до отказа — 1
Строка конфликта — 4
Остаток конфликтного EXIST — 2
Строка для отдельной отправки — 5
TAIL до продолжения — 0
TAIL после продолжения — 3
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Сделайте TAIL зависимым от успешного создания EXIST. Почему теперь правомерно отложить хвост целиком, даже если технически можно отправить его отдельно?
Как проверить своё решение
У зависимой операции не выполнено прикладное условие запуска. Оператор сначала разрешает конфликт EXIST, затем решает судьбу TAIL. Возможность отправить очередь технически не заменяет правило бизнеса.
Показать оператору, что из файла действительно осталось в каталоге
Оператор прислал смешанный файл. Ему нужны три ответа: какие строки исправить в самом файле, какое действие отклонила база и какой остаток сейчас виден покупателю. Важны и те действия, которые успешно исполнились без изменения значений.
- При чтении исключить пустую строку 3 и неверное количество из строки 4
- Разложить оставшиеся 6 действий на три партии по два
- Найти повтор LED по индексу 0 второй партии и исходной строке 6
- Сложить счётчики разных партий и перечитать каталог
Короткий фрагмент из программы
$receipt = q137Dispatch($manager, $ns, $rowsForThisBatch, false);
foreach ($receipt['issues'] as $issue) {
echo 'Строка ', $issue['line'], ', артикул ', $issue['sku'], ', код ', $issue['code'], PHP_EOL;
}Что увидит оператор
| Что проверили | Результат |
|---|---|
| Пустые строки | 3 |
| Ошибки входа | 4 |
| Отправлено операций | 6 |
| Ошибка MongoDB | партия 2, индекс 0, строка 6, код 11000 |
| Вставлено / обновлено / удалено | 1 / 1 / 1 |
| Совпало обновлений | 2 |
| Каталог после отправки | KEEP=5, LED=2 |
KEEP сначала получил прежнее количество 3, затем новое 5. Оба фильтра совпали, но изменился товар только один раз. Строка для GHOST не создала товар, для OLD прошло удаление. На экране остаются KEEP=5 и LED=2; попытка поставить повторный LED с количеством 9 отклонена.
Границы решения
В партиях этого примера отдельные действия независимы или разведены по последовательным вызовам. unordered-пакет нельзя использовать как способ гарантировать порядок зависимых изменений одного товара. Итоговое чтение показывает текущее состояние после последовательного учебного запуска.
Самостоятельная программа и ZIP
<?php
declare(strict_types=1);
use MongoDB\Driver\{Manager,BulkWrite,WriteResult,WriteError};
use MongoDB\Driver\Exception\BulkWriteException;
require __DIR__.'/q137-helper.php';
[$rows,$checks,$runtime,$counts]=q137Lab(static function(Manager $manager,string $db,string $ns): array {
$seed=q137Seed($manager,$ns,[['_id'=>'KEEP','qty'=>3],['_id'=>'OLD','qty'=>1]]);
$input="action;sku;qty\ninsert;LED;2\n\nset;BAD;-9\nset;KEEP;3\ninsert;LED;9\nset;KEEP;5\ndelete;OLD;\nset;GHOST;7";
$parsed=q137Parse($input);
$queued=$inserted=$matched=$changed=$deleted=0;
$issueRows=[];$groups=0;
foreach (array_chunk($parsed['valid'],2) as $group=>$entries) {
$receipt=q137Dispatch($manager,$ns,$entries,false);
$groups++;
foreach ($receipt['issues'] as $issue) {
$issueRows[]='партия '.($group+1).', индекс '.$issue['index'].', строка '.$issue['line'].', код '.$issue['code'];
}
$queued+=$receipt['queued'];$inserted+=$receipt['inserted'];$matched+=$receipt['matched'];
$changed+=$receipt['modified'];$deleted+=$receipt['deleted'];
}
$actual=q137Read($manager,$ns);
$catalog=implode(', ',array_map(fn($d)=>$d['_id'].'='.$d['qty'],$actual));
$rows=[['Пустые строки',implode(', ',$parsed['blanks'])],['Ошибки входа',implode(', ',$parsed['invalid'])],['Отправлено операций',$queued],
['Ошибка MongoDB',implode(' · ',$issueRows)],['Вставлено / обновлено / удалено',$inserted.' / '.$changed.' / '.$deleted],
['Совпало обновлений',$matched],['Каталог после отправки',$catalog]];
$checks=['initial_catalog'=>$seed===2,'input_rejected'=>$parsed['blanks']===[3] && $parsed['invalid']===[4],
'three_batches'=>$groups===3 && $queued===6,'exact_issue'=>$issueRows===['партия 2, индекс 0, строка 6, код 11000'],
'real_counters'=>$inserted===1 && $changed===1 && $deleted===1 && $matched===2,'fresh_catalog'=>$catalog==='KEEP=5, LED=2',
'missing_stays_missing'=>q137Read($manager,$ns,['_id'=>'GHOST'])===[],'old_removed'=>q137Read($manager,$ns,['_id'=>'OLD'])===[]];
return [$rows,$checks];
});
q137Report('mongodb137-queue-dispatch-receipt', 'Показать оператору, что из файла действительно осталось в каталоге', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
foreach ($rows as [$label,$value]) {echo $label, ' — ', $value, PHP_EOL;}
Ожидаемый результат
Пустые строки — 3
Ошибки входа — 4
Отправлено операций — 6
Ошибка MongoDB — партия 2, индекс 0, строка 6, код 11000
Вставлено / обновлено / удалено — 1 / 1 / 1
Совпало обновлений — 2
Каталог после отправки — KEEP=5, LED=2
Программа работает с собственной учебной коллекцией MongoDB, получает настоящий ответ драйвера и повторно читает состояние. Полный архив содержит helper, инструкцию запуска и ожидаемую таблицу.
Открыть таблицу фактического запуска · Скачать JSON результата.
Попробуйте изменить
Поменяйте количество второго LED на исходные 2. Должен ли конфликт уникального _id исчезнуть лишь от совпадения количеств? Объясните, как оператор отличит безопасный повтор поставки от отдельной операции.
Как проверить своё решение
Второй insert с тем же _id всё равно конфликтует. Совпадающее количество не делает новую вставку обновлением и не устанавливает, что это повтор той же поставки. Для такого решения нужен отдельный идентификатор операции и явно заданная политика повторов.
Изменить CSV в браузере и получить строковую квитанцию
В ZIP именно этого итогового примера дополнительно находятся web/import.php, web/public/index.php, web/sample.csv, web/db.php и web-README.md. Запустите php -S 127.0.0.1:8091 -t web/public и откройте адрес из инструкции. «Предпросмотр» разбирает текст без записи; «Записать» создаёт собственную пустую учебную коллекцию, выполняет импорт, перечитывает остатки и очищает коллекцию.
У формы намеренно более узкий договор: заголовок sku;qty и только вставки, количество от 1 до 9999. Это отдельная практическая ступень к смешанному CLI-импорту с action;sku;qty, а не тот же входной формат. В исходном web/sample.csv строки 2 и 5 составляют первую партию, строки 6 и 7 — вторую. Повтор LED даёт индекс 0 второй партии, но экран показывает физическую строку 6. Столбец «В каталоге, шт.» приходит из последующего запроса, а не копируется из входа. Исправьте второй LED на PIN: должны сохраниться четыре допустимые строки. Детали запуска находятся в web-README.md.
Четыре карточки: очередь, счётчик, результат и ошибка
Выберите объект по тому, какой ответ нужен вашему экрану. Карточки сохраняют структуру API, типы входов и результатов, версии, ошибки и два разных применения. Полные программы находятся выше.
| Класс или метод | Зачем открыть |
|---|---|
| BulkWrite | Подготовить очередь изменений одной коллекции и явно выбрать порядок исполнения. |
| BulkWrite::count | Посчитать подготовленные инструкции до отправки и отличить их от изменённых товаров. |
| WriteResult | Прочитать счётчики и частичный итог исполнения, сохраняя разницу между нулём и неизвестным результатом. |
| WriteError | Вернуть ошибку конкретной операции к артикулу и физической строке исходного файла. |
Применить квитанцию в своём импорте
Сначала подпишите единицы в своей квитанции: операции, совпадения документов, реальные изменения и штуки товара. Затем сохраните соответствие операции исходной строке и явно выберите правило частичного отказа. Отдельное чтение показывает наблюдаемое состояние после записи; при параллельных изменениях другим процессом оно уже может отражать более позднее состояние, а не снимок именно вашего действия.
Если одно поручение затрагивает товар и связанную запись другой коллекции, переходите к отправке изменений по нескольким коллекциям. Для выбора уровня подтверждения — к read/write concern, для безопасного изменения остатка — к условной атомарной записи. Эти четыре карточки закрывают перечисленные узлы справочника; они не заменяют всё дерево MongoDB.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
MongoDB\Driver\BulkWriteПодготовить очередь изменений одной коллекции и явно выбрать порядок исполнения.
ext-mongodb 1.0.0+; изменения 1.2.0, 2.0.0 и 2.3.0 отмечены в карточке · mongodb
final class MongoDB\Driver\BulkWrite implements CountableМенеджер хочет заранее проверить импорт и отправить его отдельным действием. BulkWrite хранит изменяемую очередь инструкций insert, update и delete в памяти PHP. Сам объект ещё не является записью в MongoDB. Класс final запрещает наследование; Countable позволяет передать очередь в count(). Он доступен с ext-mongodb 1.0.0.
- insert добавляет LAMP с qty=4 в объект $bulk
- count($bulk) показывает 1 подготовленную инструкцию
- executeBulkWrite получает адрес одной коллекции и отправляет очередь
- WriteResult сообщает результат, Query отдельно читает LAMP=4
final class MongoDB\Driver\BulkWrite implements Countable {
public function __construct(?array $options = null);
public function count(): int;
public function insert(array|object $document): mixed;
public function update(array|object $filter, array|object $newObj, ?array $updateOptions = null): void;
public function delete(array|object $filter, ?array $deleteOptions = null): void;
}Это справочная схема встроенного API, её не нужно объявлять в своей программе. Для нового пакета используйте new BulkWrite(). Вставка возвращает _id подготовленного документа; если поля нет, драйвер создаёт ObjectId. Возвращённый идентификатор ещё не подтверждает сохранение. update и delete возвращают void — полезного возвращаемого значения у них нет. Ошибки неверных аргументов относятся к этапу подготовки; ошибки сервера обнаруживаются при отправке.
| Вход | Что означает и куда перейти |
|---|---|
| $options конструктора | ordered=true по умолчанию сохраняет последовательность и останавливает оставшиеся действия после ошибки записи. ordered=false разрешает произвольный порядок и обработку других элементов после индивидуального отказа. Это не обещание завершить пакет при любом сетевом отказе. |
| Остальные настройки конструктора | bypassDocumentValidation=false сохраняет серверную проверку документов; true разрешает её обход на MongoDB 3.2+. comment добавляет отметку для диагностики на 4.4+. let задаёт именованные постоянные значения или замкнутые выражения без обращения к полям документа для использования как $$имя; требуется 5.0+. На старом сервере неподдерживаемые comment/let приводят к ошибке исполнения. |
| $document для insert | PHP-массив или объект с полями нового документа. _id в уроке равен артикулу. Попытка вставить второй документ с таким же уникальным ключом приводит к ошибке 11000 при исполнении. |
| $filter и $newObj для update | Фильтр выбирает документы, а второй аргумент описывает операторы обновления, замену документа или pipeline. Здесь $set устанавливает qty. multi=true разрешает несколько совпадений, upsert=true разрешает создание при отсутствии совпадения; в импорте оба режима включаются только явно. |
| $filter и $deleteOptions для delete | Фильтр задаёт условие удаления. limit=1 ограничивает действие одним документом; limit=0 допускает все совпадения. В примере адресуем один _id. Пустой фильтр охватывает всю коллекцию. |
| Чтение количества операций | Метод без параметров возвращает int. Одна инструкция с multi=true всё равно увеличивает очередь только на один элемент. |
bypassDocumentValidation появился в расширении 1.1.0, comment и let — в 1.14.0. При выборе опции проверяют отдельно версию расширения и сервера. В синтаксисе выше array|object означает допустимые виды входа; ?array допускает также null, то есть отсутствие дополнительных опций. Опции конструктора.
Отправка, порядок и частичный отказ
При вызове Manager::executeBulkWrite адрес namespace имеет вид «база.коллекция». Он задаётся один раз для всего BulkWrite. Очередь должна содержать хотя бы одну операцию и может исполняться только один раз: пустой или уже исполненный объект вызывает InvalidArgumentException. Для следующей партии создают новый объект. WriteConcern в современном API передают внутри массива options; непосредственный объект третьим аргументом удалён в ext-mongodb 2.0.0.
Смешанные инструкции драйвер объединяет в команды по типу. Например, последовательность insert A, insert B, update A, insert C, delete B при ordered=true содержит пять действий и четыре группы типов. Между update и последующим insert нельзя поменять порядок ради укрупнения группы. Дополнительные ограничения размеров также могут разделить передачу. Число PHP-объектов, инструкций и сетевых обращений поэтому измеряет разные вещи. Группировка операций при исполнении.
При BulkWriteException доступен WriteResult с уже известными итогами. Проверяют отдельно WriteError конкретных элементов и WriteConcernError подтверждения. Выполненный префикс не откатывается автоматически. При ordered=false нельзя строить зависимость «сначала вставить, затем изменить» по порядку строк. Для атомарной бизнес-операции на нескольких документах изучите транзакции MongoDB; для нескольких коллекций — BulkWriteCommand.
Первое применение: добавить один товар
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert(['_id'=>'LAMP', 'qty'=>4]);
echo count($bulk); // 1 операция подготовлена
$result = $manager->executeBulkWrite($ns, $bulk);
echo $result->getInsertedCount(); // 1 документ созданДо отправки повторное чтение не находит LAMP. После отправки insertedCount=1, а отдельный запрос возвращает qty=4. Никакого преобразования четырёх штук в четыре операции не происходит.
Исходные данные, шаги и самостоятельная программа.
Второе применение: продолжить известный неисполненный хвост
$tail = array_slice($batch, $error['index'] + 1);
$resumed = q137Dispatch($manager, $ns, $tail, true);Для независимого TAIL после подтверждённого индивидуального отказа EXIST создаётся новый объект. NEW повторно не отправляется; количество EXIST остаётся 2. Условия, при которых такой хвост известен, разобраны в программе.
MongoDB\Driver\BulkWrite::countПосчитать подготовленные инструкции до отправки и отличить их от изменённых товаров.
ext-mongodb 1.0.0+; изменения 1.2.0, 2.0.0 и 2.3.0 отмечены в карточке · mongodb
public function MongoDB\Driver\BulkWrite::count(): intДо отправки импорта можно показать оператору «подготовлено 6 действий». count отвечает именно на этот вопрос. Метод принадлежит BulkWrite и доступен с ext-mongodb 1.0.0; начиная с 1.2.0 он считает добавленные операции. Более старые версии возвращали оценку числа сетевых обменов, поэтому старый материал с таким толкованием не подходит для современного расширения.
- Пустая очередь → 0
- Один вызов insert с qty=4 → 1
- Вызов count ничего не отправляет
- После отправки insertedCount=1, в документе qty=4
public function MongoDB\Driver\BulkWrite::count(): int
// При реализации Countable равнозначно:
$number = count($bulk);Параметров нет. int — целое число добавленных write-операций. Ошибка разбора аргументов документирована как MongoDB\Driver\Exception\InvalidArgumentException. Ноль означает пустую очередь, которую следует обработать до executeBulkWrite. Сам count не обращается к серверу, не проверяет уникальность _id и не узнаёт количество совпавших документов.
Первое применение: проверить кнопку отправки
$empty = count($bulk); // 0 до добавления
$bulk->insert(['_id'=>'LAMP', 'qty'=>4]);
$queued = $bulk->count(); // 1 после добавленияВ первом сценарии эти значения получены последовательно на одном объекте. Удобно разрешить отправку только непустого пакета; окончательный ответ всё равно берут после обращения к базе.
Исходные данные, шаги и самостоятельная программа.
Второе применение: одна инструкция выбирает три товара
$bulk->update(['group'=>'lamp'], ['$set'=>['visible'=>true]], ['multi'=>true]);
echo count($bulk); // 1Обновление группы ламп даёт count=1, matchedCount=3 и modifiedCount=2. Поэтому число в очереди нельзя делить на число товаров и выдавать полученное отношение за процент завершения.
Исходные данные, шаги и самостоятельная программа.
Для сопоставления планов и результата используйте WriteResult. Размер пользовательской партии ограничивают при накоплении очереди: в разборе партий по два действия исходная строка хранится рядом с каждым добавленным действием. Ограничение количества само по себе не ограничивает общий объём документов в байтах; для больших файлов нужен потоковый разбор и продуманный предел памяти.
MongoDB\Driver\WriteResultПрочитать счётчики и частичный итог исполнения, сохраняя разницу между нулём и неизвестным результатом.
ext-mongodb 1.0.0+; изменения 1.2.0, 2.0.0 и 2.3.0 отмечены в карточке · mongodb
final class MongoDB\Driver\WriteResultПосле импорта оператору нужен проверяемый итог: сколько создано, найдено, изменено и удалено. WriteResult — final-класс ответа от executeBulkWrite, а при BulkWriteException тот же вид результата читают через getWriteResult(). Он описывает исполнение очереди, а не коллекцию документов; создавать экземпляр через new для собственной квитанции не требуется. Класс существует с ext-mongodb 1.0.0.
- BulkWrite отправил 4 инструкции
- WriteResult: insert=1, matched=1, modified=1, delete=1
- Новый Query читает текущие документы
- На экране KEEP=4 и NEW=6; ABSENT не создан
final class MongoDB\Driver\WriteResult {
public readonly ?int $insertedCount;
public readonly ?int $matchedCount;
public readonly ?int $modifiedCount;
public readonly ?int $deletedCount;
public readonly ?int $upsertedCount;
public readonly MongoDB\Driver\Server $server;
public readonly array $upsertedIds;
public readonly array $writeErrors;
public readonly ?MongoDB\Driver\WriteConcernError $writeConcernError;
public readonly ?MongoDB\Driver\WriteConcern $writeConcern;
public readonly array $errorReplies;
}Публичные readonly-свойства добавлены в ext-mongodb 2.3.0: их можно читать, но нельзя использовать как редактируемые поля своего отчёта. В учебных программах вызываются методы. Знак ? у типа свойства означает, что оно может содержать null; нулевой счётчик и отсутствие сведений — разные ответы.
| Метод без параметров и тип результата | Смысл соответствующего свойства |
|---|---|
| WriteResult::getInsertedCount(): int | insertedCount считает документы, созданные insert; вставки, появившиеся через upsert, учитываются отдельно. Свойство равно null при неподтверждённой записи. |
| WriteResult::getMatchedCount(): int | matchedCount — число совпадений update и замены документа. Это не обязательно число изменений; свойство равно null без подтверждения. |
| WriteResult::getModifiedCount(): int | modifiedCount — документы, фактически изменённые update/заменой. Свойство может быть null без подтверждения либо если сервер не предоставил эти сведения. |
| WriteResult::getDeletedCount(): int | deletedCount — число удалённых документов, либо null без подтверждения. Ноль допустим для фильтра, который ничего не нашёл. |
| WriteResult::getUpsertedCount(): int | upsertedCount — число новых документов, созданных обновлениями с upsert=true; свойство равно null без подтверждения. |
| WriteResult::getUpsertedIds(): array | upsertedIds связывает индекс исходной update-операции с _id нового документа. Это не полный список идентификаторов обычных insert. |
| WriteResult::getServer(): MongoDB\Driver\Server | server указывает сервер, исполнивший пакет. Его можно использовать для диагностических сведений о соединении. |
| WriteResult::getWriteErrors(): array | writeErrors содержит объекты WriteError по индивидуальным отказам; при отсутствии таких ошибок массив пуст. Нужный индекс операции берут из самого WriteError, а не из позиции элемента списка ошибок. |
| WriteResult::getWriteConcernError(): ?MongoDB\Driver\WriteConcernError | writeConcernError содержит ошибку требуемого подтверждения или null, если она не возникла. Это другой слой, чем отказ конкретной строки. |
| WriteResult::isAcknowledged(): bool | true сообщает о подтверждённом режиме результата; false означает, что подтверждённые счётчики недоступны. Это не флаг атомарности всех действий. |
| Дополнительные свойства writeConcern и errorReplies | writeConcern — применённое требование к подтверждению либо null, если оно недоступно. errorReplies — массив серверных документов с ответами об ошибках. Отдельные одноимённые get-методы в этом классе не объявлены. |
Все перечисленные методы имеют final public и не принимают аргументов. Ошибки разбора аргументов документированы как InvalidArgumentException. В ext-mongodb 2.x методы получения счётчиков выбрасывают LogicException для неподтверждённой записи; прежний код, ожидающий null от такого вызова, требует изменения. Сначала проверьте isAcknowledged(). Публичные nullable-свойства и старое поведение методов нельзя смешивать. Контракт modified и изменение с 2.0.0.
Сводные matched и modified пересекаются: изменённый документ обычно уже посчитан среди совпавших. Складывать их для числа выполненных действий нельзя. В пакете нет отдельного результата каждого успешного update. Для индивидуальной квитанции выберите отдельные запросы, ограниченные адресные чтения либо подробные результаты BulkWriteCommandResult при поддерживаемом сервере. WriteResult не является транзакцией и не отменяет уже исполненное из-за следующей ошибки.
Первое применение: отличить совпадение от изменения
if ($result->getMatchedCount() === 0) { return 'нет товара'; }
return $result->getModifiedCount() === 0 ? 'уже актуально' : 'обновлено';A даёт 1/0 и остаётся с количеством 8. B даёт 1/1 и после запроса имеет 8. X даёт 0/0 и не появляется. В этом примере отправки отдельные, поэтому счётчики однозначно относятся к товару.
Исходные данные, шаги и самостоятельная программа.
Второе применение: сверить смешанный импорт
$inserted = $result->getInsertedCount(); // 1
$modified = $result->getModifiedCount(); // 1
$deleted = $result->getDeletedCount(); // 1
$catalog = q137Read($manager, $ns);Четыре инструкции дали новую карточку NEW, изменение KEEP и удаление OLD. Отдельный запрос подтверждает KEEP=4, NEW=6. Нулевое совпадение ABSENT допустимо и не породило WriteError.
MongoDB\Driver\WriteErrorВернуть ошибку конкретной операции к артикулу и физической строке исходного файла.
ext-mongodb 1.0.0+; изменения 1.2.0, 2.0.0 и 2.3.0 отмечены в карточке · mongodb
final class MongoDB\Driver\WriteErrorПосле частичного импорта оператору надо исправить конкретную строку. WriteError хранит сведения об одном отказавшем действии. Этот final-класс существует с ext-mongodb 1.0.0; драйвер выдаёт его внутри WriteResult::getWriteErrors(). Это объект данных об ошибке, а не исключение, которое следует перехватывать в catch. Перехватывают BulkWriteException, затем читают его результат.
- Строки 2, 5 и 6 прошли входную проверку
- Они добавлены как операции 0, 1 и 2
- WriteError сообщает index=1 и code=11000
- Карта map[1] возвращает line=5 и sku=EXIST
- Оператор видит пятую строку; база отдельно подтверждает старый остаток 1
final class MongoDB\Driver\WriteError {
public readonly string $message;
public readonly int $code;
public readonly int $index;
public readonly ?object $info;
final public function getCode(): int;
final public function getIndex(): int;
final public function getInfo(): ?object;
final public function getMessage(): string;
}| Что читать | Как использовать |
|---|---|
| WriteError::getCode / code | Числовой код конкретного отказа: в примерах 11000 означает конфликт уникального ключа. Не превращайте любой код в сообщение «повтор товара». |
| WriteError::getIndex / index | Индекс операции внутри переданного BulkWrite, начиная с нуля. У новой партии своя нумерация. Это не номер физической строки CSV и не номер ошибки в списке. |
| WriteError::getMessage / message | Строка диагностического сообщения. Для решения по строке используйте структурированные код и индекс. При выводе сообщения в HTML экранируйте его. |
| WriteError::getInfo / info | Дополнительный документ сведений либо null, если сервер его не предоставил. Не разыменовывайте info как обязательный объект и не считайте null отсутствием самой ошибки. |
Методы не принимают параметров; ошибки их разбора документированы как InvalidArgumentException. readonly-свойства доступны с ext-mongodb 2.3.0. Чтение объекта не повторяет операцию, не исправляет CSV и не откатывает успешные записи. Для нового действия создают новую очередь после решения приложения. Контракт дополнительной информации.
Первое применение: найти строку после пропусков
foreach ($result->getWriteErrors() as $item) {
$index = $item->getIndex();
$source = $map[$index];
// $source['line'] = 5, $source['sku'] = 'EXIST'
}Пустая строка 3 и неверная строка 4 не попадали в очередь. Индекс 1 поэтому указывает на строку 5. В полной функции проверяется наличие индекса в карте; неизвестное соответствие не заменяется догадкой.
Исходные данные, шаги и самостоятельная программа.
Второе применение: одинаковые индексы разных партий
foreach (array_chunk($validRows, 2) as $batchNumber => $entries) {
// Для каждого массива $entries создаётся собственный BulkWrite и собственная карта.
$receipt = q137Dispatch($manager, $ns, $entries, false);
}Первый индекс 0 означает DUPA в строке 2, второй индекс 0 — DUPB в строке 6. Карта и результат принадлежат одной партии; счётчик партии сохраняется рядом, а не используется для восстановления пропущенных строк.
Исходные данные, шаги и самостоятельная программа.
Для обработки частичного успеха откройте условия продолжения ordered-очереди. Для проблем подтверждения и неизвестного исхода нужен отдельный разбор WriteConcernError и отказов команды. Пустой список WriteError сам по себе не доказывает, что после сбоя связи все строки сохранены.
Источники и обсуждения
- PHP Manual — BulkWrite
- PHP Manual — BulkWrite::count
- PHP Manual — WriteResult
- PHP Manual — WriteError
- PHP Manual — WriteError::getIndex
- PHP Manual — Manager::executeBulkWrite
- PHP Manual — BulkWrite::__construct
- PHP Manual — WriteResult::getModifiedCount
- PHP Manual — BulkWrite::insert
- PHP Manual — BulkWrite::update
- PHP Manual — BulkWrite::delete
- PHP Manual — WriteResult::getUpsertedIds
- PHP Manual — WriteError::getInfo
- PHP Manual — WriteResult::isAcknowledged
- PHP Manual — WriteResult::getWriteConcernError
- PHP Manual — WriteResult::getWriteErrors
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


