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

Дерево руководства
Глава 156 · После Query, BSON-типов и основ итерации
MongoDB: жизненный цикл курсора, партии, страницы и потоковый экспорт
Как читать ровно нужные данные, различать ID сервера и локальный буфер, безопасно использовать CursorInterface и мигрировать старый CursorId.
От нужной страницы к последовательному чтению
Кабинету нужны три заказа, бухгалтеру — файл оплаченных заказов, диагностике — источник ответа. Для этих задач не требуется загружать весь каталог в PHP и затем сортировать огромный массив. Сначала ограничим работу на сервере, затем выберем способ чтения результата.
- На сервере 7 заказов → фильтр status=paid оставляет 5
- sort по _id и limit=3 → нужны заказы 1, 2, 4
- Первый ответ batchSize=2 → документы попадают в буфер драйвера
- PHP читает текущую запись; драйвер при необходимости продолжает через getMore
- Страница получает 3 строки, а не всю коллекцию
Буфер можно представить как небольшой лоток документов, который пополняется. Но это не буквальная модель памяти: размер партии ограничивается также байтами, документы имеют разный размер, сервер может выполнять сортировку отдельно, а приложение способно само накопить всё в массиве.
$query = new MongoDB\Driver\Query(['status'=>'paid'], ['projection'=>['total_kopecks'=>1], 'sort'=>['_id'=>1], 'limit'=>3, 'batchSize'=>2]);
$cursor = $manager->executeQuery($namespace, $query);
foreach ($cursor as $order) { echo $order->_id, PHP_EOL; }Query описывает условия и настройки. $manager — заранее созданное подключение, $namespace — строка «база.коллекция». executeQuery выполняет запрос и возвращает Cursor. Цикл foreach читает его результаты. В первом полном архиве ниже вся подготовка включена; фрагмент только выделяет текущую идею.
Перед этой главой полезны соединение, документ и обычный Query, BSON-типы значений и основной проход курсора. Новые карточки дополняют прежние Cursor::toArray и Cursor::setTypeMap: здесь добавлены жизненный цикл, CursorInterface и история CursorId.
Фильтр, лимит, партия, ID и буфер — разные вещи
| Задача / понятие | Где задаётся | Смысл |
|---|---|---|
| Какие документы | Фильтр Query / $match | Уменьшить набор на сервере |
| Какие поля | projection / $project | Не передавать ненужные данные |
| Порядок и число строк страницы | sort и limit | Нужный результат до передачи в PHP |
| Размер партии | batchSize | Режим передачи, не общий лимит и не память всей программы |
| ID курсора | getId(): Int64 | Непрозрачное серверное продолжение, не количество |
| Текущая запись | valid/current | Доступен ли сейчас документ итератору |
| Возможность дополнительных результатов | isDead | Учитывает буфер, конец, лимит и ошибки |
| Повторный локальный доступ | toArray после ограниченного Query | Материализовать маленькую выборку один раз |
ID 0 не означает «не найдено ни одной записи»: ответ мог уже целиком прийти в локальный буфер. И наоборот, ненулевой ID не гарантирует, что следующий запрос getMore даст документ без ошибки. На обычном конечном курсоре выбирайте foreach; не стройте цикл только на isDead.
Две среды и запуск самостоятельных архивов
Текущие восемь архивов используют PHP 8.5.11, расширение mongodb 2.5.5 и MongoDB 8.0.34; требования примера — PHP 8.4+ x64 и mongodb 2.5+. Два архива с окончанием -legacy используют отдельный PHP 8.4.27 и mongodb 1.20.1. Все десять создают свою учебную коллекцию, читают сервер и убирают её; исторические примеры не имитируются на новом расширении.
В каждом архиве: example.php, cl122-helper.php, README.txt. README содержит установку DLL, запуск mongod с отдельным dbpath и bind_ip=127.0.0.1, порт 27075, переменные PHPMAN_MONGODB_URI/PHPMAN_MONGODB_DATABASE. Composer не нужен. Для legacy используйте полный путь к отдельному php.exe и отдельный php.ini.
Запустите php example.php и откройте result.html. JSON содержит ту же таблицу; metrics.json — версии, проверки и хеши. В экспорте остаётся orders.csv. На сайте показан сохранённый результат выполнения; браузер посетителя не подключается к базе.
Коллекции имеют случайные имена mongodb122_cursor_…; владение записывается после успешного create. finally удаляет только эти точные имена, затем listCollections проверяет отсутствие. Нет dropDatabase или изменения сервера. Полное аварийное завершение процесса может не выполнить finally.
Показать три заказа, получая данные партиями по два
На странице кабинета нужны три первых оплаченных заказа. Черновики, отменённые заказы и служебные заметки посетителю не нужны. Получим ID 1, 2 и 4 с суммами 1200, 2300 и 3400 копеек.
Откуда берутся данные
В фикстуре семь заказов; пять имеют status=paid. Каждая запись хранит _id, статус, сумму, вложенного покупателя, позиции и служебную заметку. Фильтр выбирает оплаченные, projection оставляет только ID и сумму.
- Семь заказов на сервере → status=paid оставляет пять
- sort и limit → по _id выбраны первые три: 1, 2, 4
- Передача партиями → batchSize=2: первая небольшая партия, затем продолжение
- PHP foreach → три строки страницы и сумма 6900 копеек
Короткая запись действия
$query = new MongoDB\Driver\Query(['status'=>'paid'], ['projection'=>['total_kopecks'=>1], 'sort'=>['_id'=>1], 'limit'=>3, 'batchSize'=>2]);Этот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Заказ / показатель | Сумма в копейках / результат |
|---|---|
| 1 | 1200 |
| 2 | 2300 |
| 4 | 3400 |
| Итого заказов / копеек | 3 / 6900 |
Как это работает
Query — описание запроса; executeQuery выполняет его и возвращает Cursor. Это объект для последовательного чтения, а не уже собранный PHP-массив. Символ -> вызывает метод объекта. new Query(...) создаёт описание, но не курсор вручную.
Первый массив содержит условие, второй — настройки. limit=3 ограничивает общий результат; batchSize=2 задаёт размер партии передачи. Пять подходящих заказов не означают пять строк страницы, а партия из двух не ограничивает страницу двумя строками.
foreach получает следующий документ и при необходимости позволяет драйверу запросить следующую партию. Сортировка выполняется в базе до отдачи нужных строк. PHP не загружает всю коллекцию ради выбора трёх заказов.
Когда способ подходит и где его границы
Партия ограничена также байтовыми пределами протокола и может быть меньше заданного числа документов. Размер партии не является точным числом сетевых запросов и не гарантирует ускорение. Индекс для большого каталога выбирают по фильтру и порядку; здесь семь учебных записей.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
$cursor=cl122Query($manager,$ns['orders'],['status'=>'paid'],[
'projection'=>['_id'=>1,'total_kopecks'=>1],'sort'=>['_id'=>1],'limit'=>3,'batchSize'=>2]);
cl122ArrayMap($cursor);
$initialId=$cursor->getId();
$rows=[];$ids=[];$sum=0;$projected=true;
foreach($cursor as $order){
$rows[]=[$order['_id'],$order['total_kopecks']];$ids[]=$order['_id'];$sum+=$order['total_kopecks'];
$projected=$projected && array_keys($order)===['_id','total_kopecks'];
}
$rows[]=['Итого заказов / копеек',count($ids).' / '.$sum];
$checks=['three_not_two'=>$ids===[1,2,4],'sum_is_6900'=>$sum===6900,
'projection_only'=>$projected,'id_is_int64'=>$initialId instanceof MongoDB\BSON\Int64,
'finished'=>!$cursor->valid() && $cursor->isDead() && (string)$cursor->getId()==='0'];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-page-batches', ['Заказ / показатель', 'Сумма в копейках / результат'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-page-batches: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-page-batches: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Поменяйте limit на 2, сохранив batchSize=2: ожидаются ID 1, 2 и сумма 3500. Затем верните limit=3 и установите batchSize=1: состав страницы должен остаться 1, 2, 4.
Увидеть доступные строки при нулевом серверном ID
В журнале курсора появился ID 0, но на странице всё ещё должны быть заказы. Проверим, где заканчивается серверное продолжение и где остаются уже полученные документы.
Откуда берутся данные
Запрос выбирает первые три оплаченных заказа. singleBatch=true закрывает серверное продолжение после первой партии; batchSize=10 вмещает эти три маленьких документа.
- Сервер → отдаёт 1, 2, 4 одним ответом
- ID сервера → 0: getMore для продолжения не требуется
- Локальный буфер драйвера → заказы ещё можно прочитать
- После полного прохода → valid=false, isDead=true
Короткая запись действия
$cursor->rewind();
echo (string) $cursor->getId(); // 0, но буфер ещё содержит заказы
var_dump($cursor->valid(), $cursor->isDead()); // true, falseЭтот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Момент | ID / значения | valid: текущая запись | isDead: больше нет |
|---|---|---|---|
| После первого rewind | 0 | да | нет |
| После полного прохода | 0 | нет | да |
| Фактически прочитанные ID | 1,2,4 | 3 документа | без нового запроса |
Как это работает
rewind в начале устанавливает итератор на первую запись. valid отвечает, существует ли текущая запись, а current возвращает её. next сдвигает позицию. Эти методы уже разобраны в главе агрегирования; здесь мы связываем их с серверным состоянием.
getId возвращает номер серверного курсора. Ноль показывает отсутствие серверного продолжения, но не уничтожает документы, которые драйвер уже получил в буфер. Поэтому первый valid=true совместим с ID 0.
isDead проверяет возможность дополнительных результатов с учётом буфера, предела и ошибок. На начале этого примера он false, после исчерпания — true. Он не является простым сравнением getId с нулём и не заменяет valid в обычном цикле.
Когда способ подходит и где его границы
false означает возможность продолжения, а не обещание непустого следующего ответа. Ошибка сети может возникнуть при дальнейшем чтении. Для обычного конечного запроса используйте foreach либо valid/next; tailable-курсоры имеют отдельные правила ожидания и здесь не запускаются.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
$cursor=cl122Query($manager,$ns['orders'],['status'=>'paid'],[
'projection'=>['_id'=>1],'sort'=>['_id'=>1],'limit'=>3,'batchSize'=>10,'singleBatch'=>true]);
cl122ArrayMap($cursor);$cursor->rewind();
$beforeId=(string)$cursor->getId();$beforeValid=$cursor->valid();$beforeDead=$cursor->isDead();
$ids=[];while($cursor->valid()){$ids[]=$cursor->current()['_id'];$cursor->next();}
$rows=[['После первого rewind',$beforeId,$beforeValid?'да':'нет',$beforeDead?'да':'нет'],
['После полного прохода',(string)$cursor->getId(),$cursor->valid()?'да':'нет',$cursor->isDead()?'да':'нет'],
['Фактически прочитанные ID',implode(',',$ids),'3 документа','без нового запроса']];
$checks=['zero_id_with_buffer'=>$beforeId==='0' && $beforeValid && !$beforeDead,
'buffer_was_read'=>$ids===[1,2,4],'exhausted_after_read'=>!$cursor->valid() && $cursor->isDead()];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-buffered-state', ['Момент', 'ID / значения', 'valid: текущая запись', 'isDead: больше нет'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-buffered-state: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-buffered-state: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Уберите singleBatch, задайте batchSize=2 и limit=3. Состав результата не меняется, но сначала серверу обычно нужно сохранить продолжение. Не записывайте случайный ненулевой ID в ожидаемые результаты.
Записать оплаченные заказы в CSV без массива всех результатов
Бухгалтеру нужен CSV оплаченных заказов, а не многотысячный массив в памяти сайта. Создадим orders.csv с пятью строками и итогом 17000 копеек.
Откуда берутся данные
Из семи заказов сервер выбирает status=paid, сортирует по _id и передаёт только _id и total_kopecks. В лаборатории задан потолок limit=20; совпавших записей пять.
- Фильтр MongoDB → оплаченные: 1, 2, 4, 5, 7
- Буфер партии → batchSize=2
- Одна текущая строка PHP → ID и сумма → fputcsv
- Готовый orders.csv → 5 заказов, 17000 копеек
Короткая запись действия
foreach ($cursor as $order) {
fputcsv($file, [$order['_id'], $order['total_kopecks']], ',', '"', '', "\n");
}Этот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| order_id / итог | total_kopecks / итог |
|---|---|
| 1 | 1200 |
| 2 | 2300 |
| 4 | 3400 |
| 5 | 4500 |
| 7 | 5600 |
| Итого | 5 заказов / 17000 коп. |
Как это работает
fopen открывает выходной файл, fputcsv корректно формирует строку CSV, fclose закрывает файл. Пустой escape передаётся явно, перевод строки задан "\n". Поля фиксированы приложением и не включают private_note.
В основном цикле нет $allRows[]=$order: обработанная строка сразу уходит в файл, а в PHP остаются счётчик и сумма. Драйвер при этом всё равно держит текущую партию; это не обещание нулевой памяти.
После записи учебный скрипт повторно читает маленький CSV для проверки заголовка, порядка и вывода HTML. Эта проверочная таблица из пяти строк не нужна большому production-экспорту: там результат проверяют потоково или по счётчикам, не собирая файл целиком обратно.
Когда способ подходит и где его границы
batchSize не отменяет память самого большого документа, буфера, кода обработки или накопленных вами массивов. Для большой задачи задайте осмысленную область выгрузки, контроль времени и безопасное возобновление. Наш CSV содержит только числа, поэтому вопрос формул из недоверенного текстового поля здесь не возникает.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
$cursor=cl122Query($manager,$ns['orders'],['status'=>'paid'],[
'projection'=>['_id'=>1,'total_kopecks'=>1],'sort'=>['_id'=>1],'limit'=>20,'batchSize'=>2]);
cl122ArrayMap($cursor);
$csvPath=__DIR__.'/orders.csv';
$file=fopen($csvPath,'wb');if($file===false){throw new RuntimeException('Cannot create output CSV');}
$count=0;$sum=0;
try{
if(fputcsv($file,['order_id','total_kopecks'],',','"','',"\n")===false){throw new RuntimeException('Cannot write header');}
foreach($cursor as $order){
if(fputcsv($file,[$order['_id'],$order['total_kopecks']],',','"','',"\n")===false){throw new RuntimeException('Cannot write order');}
++$count;$sum+=$order['total_kopecks'];
}
}finally{fclose($file);}
$file=fopen($csvPath,'rb');if($file===false){throw new RuntimeException('Cannot verify CSV');}
$rows=[];$expectedIds=[1,2,4,5,7];$position=0;$csvValid=true;
try{
$header=fgetcsv($file,null,',','"','');
while(($line=fgetcsv($file,null,',','"',''))!==false){
$csvValid=$csvValid && count($line)===2 && (int)$line[0]===($expectedIds[$position]??null);
$rows[]=[(int)$line[0],(int)$line[1]];++$position;
}
}finally{fclose($file);}
$rows[]=['Итого', $count.' заказов / '.$sum.' коп.'];
$checks=['export_filtered'=>$count===5 && $position===5,'sum_is_17000'=>$sum===17000,
'csv_header'=>$header===['order_id','total_kopecks'],'csv_rows'=>$csvValid,
'cursor_exhausted'=>$cursor->isDead() && (string)$cursor->getId()==='0'];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-stream-export', ['order_id / итог', 'total_kopecks / итог'], $rows, $checks, $runtime, $details,['orders.csv']);
echo 'mongodb122-cursor-stream-export: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-stream-export: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Добавьте в запрос условие total_kopecks >= 3400. В CSV останутся ID 4, 5, 7, три заказа и 13500 копеек. Сумму вычисляйте тем же проходом, а не загрузкой CSV целиком.
Реальный вопрос разработчика: How does the PHP MongoDB Driver's Cursor buffer a result set?. Разработчик не понимает, приходит ли весь ответ, по одному документу или партиями. В оригинальном примере явно разделены серверный фильтр, партия драйвера и построчная запись CSV; старый ответ обсуждения не используется как описание текущего протокола. Обсуждение используется как постановка задачи; правила современного API проверены по первичным источникам.
Открыть следующую страницу по ключу данных
Пользователь нажал «Следующая», то есть пришёл новый HTTP-запрос. Продолжим по последнему ID заказа, не пытаясь хранить живой серверный курсор между запросами.
Откуда берутся данные
Размер страницы равен 3. Для определения наличия продолжения запрашиваем size+1=4 записи. Последний показанный _id первой страницы равен 4.
- Первая выборка → paid, _id>0, limit=4
- Показать три → 1, 2, 4; четвёртая запись только доказывает продолжение
- Сохранить границу данных → after=4, не getId()
- Новый запрос → paid, _id>4 → 5, 7
Короткая запись действия
$query = new MongoDB\Driver\Query(['status'=>'paid', '_id'=>['$gt'=>4]], ['sort'=>['_id'=>1], 'limit'=>4, 'batchSize'=>2]);Этот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Страница | ID заказов | after: ключ данных | Есть следующая |
|---|---|---|---|
| Первая | 1,2,4 | 4 | да |
| Следующая | 5,7 | 7 | нет |
Как это работает
Функция readPage122 получает менеджер, namespace, последний ключ и размер страницы. Она создаёт новый Query и новый Cursor при каждом вызове. Проверка 1..10 ограничивает размер учебной страницы; отрицательная граница отвергается.
Лишняя четвёртая запись — lookahead, то есть взгляд на один элемент вперёд. После чтения она удаляется из показанной страницы через array_pop. Следующая граница берётся из последнего действительно показанного документа.
По _id существует уникальный индекс; это устойчивый порядок в данном примере. Такой ключ — данные приложения. Случайный ID курсора привязан к серверной операции и не подходит как номер страницы, идентификатор пользователя или долгоживущий checkpoint.
Когда способ подходит и где его границы
Обычные раздельные страницы не являются единым snapshot: между запросами статусы и документы могут измениться. В реальном кабинете добавьте неизменный фильтр владельца, проверку входной границы и подходящий индекс. Здесь нет авторизации, конкурентных изменений или обещания неизменного снимка.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
function readPage122(MongoDB\Driver\Manager $manager,string $namespace,int $after,int $size): array {
if($after<0 || $size<1 || $size>10){throw new InvalidArgumentException('Invalid page boundary');}
$pageCursor=cl122Query($manager,$namespace,['status'=>'paid','_id'=>['$gt'=>$after]],[
'projection'=>['_id'=>1,'total_kopecks'=>1],'sort'=>['_id'=>1],'limit'=>$size+1,'batchSize'=>2]);
cl122ArrayMap($pageCursor);$page=$pageCursor->toArray();$hasMore=count($page)>$size;
if($hasMore){array_pop($page);}
$last=$page===[]?$after:$page[array_key_last($page)]['_id'];
return ['documents'=>$page,'after'=>$last,'hasMore'=>$hasMore];
}
$first=readPage122($manager,$ns['orders'],0,3);
$second=readPage122($manager,$ns['orders'],$first['after'],3);
$rows=[['Первая',implode(',',array_column($first['documents'],'_id')),$first['after'],$first['hasMore']?'да':'нет'],
['Следующая',implode(',',array_column($second['documents'],'_id')),$second['after'],$second['hasMore']?'да':'нет']];
$checks=['first_page'=>array_column($first['documents'],'_id')===[1,2,4] && $first['hasMore'],
'second_page'=>array_column($second['documents'],'_id')===[5,7] && !$second['hasMore'],
'business_checkpoint'=>$first['after']===4 && $second['after']===7];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-resume-page', ['Страница', 'ID заказов', 'after: ключ данных', 'Есть следующая'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-resume-page: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-resume-page: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Установите размер 2. Получатся страницы 1,2; 4,5; 7. После второй страницы after должен быть 5, а не серверный ID и не номер позиции 4.
Реальный вопрос разработчика: use skip and limit to iterate over cursor multiple time. Попытка менять страницу уже начатого курсора смешивает объект результата с описанием нового запроса. Учебная функция каждый раз создаёт новый Query по последнему ключу данных и отдельно проверяет наличие следующей страницы. Обсуждение используется как постановка задачи; правила современного API проверены по первичным источникам.
Проверить источник списка и отчёта
Нужно сопоставить список заказов и агрегированный отчёт с сервером, который их выполнил. Получим понятную диагностику, не выводя весь объект подключения и его внутренности.
Откуда берутся данные
Один запрос возвращает два ID: 1, 2. Второй считает число и сумму всех оплаченных заказов: 5 и 17000. Оба возвращают Cursor, но получены разными путями.
- executeQuery → курсор списка
- executeReadCommand aggregate → курсор отчёта
- getServer у каждого → Server фактического исполнителя
- Сравнить endpoint → host и port совпали с заданным локальным стендом
Короткая запись действия
$server = $cursor->getServer();
echo $server->getHost(), ':', $server->getPort();Этот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Источник курсора | Результат: ID либо число / копейки | Фактический endpoint |
|---|---|---|
| Список | 1,2 | заданный локальный сервер |
| Отчёт | 5 / 17000 | заданный локальный сервер |
Как это работает
getServer возвращает объект MongoDB\Driver\Server, который связан именно с этим курсором. getHost/getPort читают адрес этого сервера. Это полезнее предположения, что первый адрес URI всегда исполнил запрос.
Функция origin122 принимает CursorInterface — общий контракт курсорного результата. Ей не требуется знать, создан курсор обычным запросом или агрегированием. Список и отчёт проходят одну проверку источника.
В команде aggregate pipeline — PHP-массив стадий; строки "$match" и "$sum" в одинарных кавычках не интерполируют PHP-переменные. cursor => (object)[] создаёт пустой BSON-документ настроек, а не BSON-массив.
Когда способ подходит и где его границы
Локальный standalone имеет один сервер. Эти результаты не доказывают failover, чтение с реплики или балансировку. Сам объект Server не обещает, что будущая операция пройдёт; актуальное состояние и диагностика сервера рассматриваются в соседней главе.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
function origin122(MongoDB\Driver\CursorInterface $cursor,array $expected): array {
$server=$cursor->getServer();
return ['server_type'=>get_debug_type($server),
'same_endpoint'=>$server->getHost()===$expected['host'] && $server->getPort()===$expected['port']];
}
$endpoint=parse_url((string)getenv('PHPMAN_MONGODB_URI'));
$queryCursor=cl122Query($manager,$ns['orders'],['status'=>'paid'],[
'projection'=>['_id'=>1],'sort'=>['_id'=>1],'limit'=>2,'batchSize'=>1]);
$aggregateCursor=$manager->executeReadCommand($database,new MongoDB\Driver\Command([
'aggregate'=>$collection,'pipeline'=>[['$match'=>['status'=>'paid']],['$group'=>['_id'=>null,'orders'=>['$sum'=>1],'kopecks'=>['$sum'=>'$total_kopecks']]]],
'cursor'=>(object)[],'maxTimeMS'=>2000]));
$queryOrigin=origin122($queryCursor,$endpoint);$reportOrigin=origin122($aggregateCursor,$endpoint);
cl122ArrayMap($queryCursor);cl122ArrayMap($aggregateCursor);
$orders=$queryCursor->toArray();$report=$aggregateCursor->toArray()[0];
$rows=[['Список',implode(',',array_column($orders,'_id')),$queryOrigin['same_endpoint']?'заданный локальный сервер':'другой сервер'],
['Отчёт',$report['orders'].' / '.$report['kopecks'],$reportOrigin['same_endpoint']?'заданный локальный сервер':'другой сервер']];
$checks=['actual_server_objects'=>$queryOrigin['server_type']==='MongoDB\Driver\Server' && $reportOrigin['server_type']==='MongoDB\Driver\Server',
'configured_endpoint'=>$queryOrigin['same_endpoint'] && $reportOrigin['same_endpoint'],
'query_and_report'=>array_column($orders,'_id')===[1,2] && $report['orders']===5 && $report['kopecks']===17000];
$details['endpoint']=['host'=>$queryCursor->getServer()->getHost(),'port'=>$queryCursor->getServer()->getPort()];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-server-origin', ['Источник курсора', 'Результат: ID либо число / копейки', 'Фактический endpoint'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-server-origin: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-server-origin: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Добавьте условие total_kopecks >= 3400 в обе выборки: список первых двух ID станет 4,5, отчёт — 3 заказа и 13500 копеек; сервер останется тем же.
Передать курсор в функцию с согласованной формой данных
Одна функция вывода ожидает $row["customer"]["name"], а другая получает stdClass и обращается через ->. Сделаем эту границу явной до чтения данных.
Откуда берутся данные
Обе выборки возвращают одну и ту же проекцию customer первого оплаченного заказа. Без карты типов корень и вложенный customer становятся stdClass; с явной картой — PHP-массивами.
- BSON-документ заказ 1 → customer.name=Анна
- Курсор без карты → stdClass → customer->name
- Курсор с setTypeMap → array → ["customer"]["name"]
- Одинаковое значение → Анна, разные формы PHP
Короткая запись действия
function prepareRows(MongoDB\Driver\CursorInterface $cursor): array {
$cursor->setTypeMap(['root'=>'array','document'=>'array','array'=>'array']);
return $cursor->toArray(); // только для заранее ограниченной выборки
}Этот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Настройка | Корень документа | Вложенный customer | Прочитанное имя |
|---|---|---|---|
| Без карты | stdClass | stdClass | Анна |
| Явная карта | array | array | Анна |
Как это работает
CursorInterface в параметре функции — требование к переданному объекту, а не инструкция создать интерфейс через new. Реальный Cursor удовлетворяет этому контракту; конкретные методы выполняет драйвер.
setTypeMap задаёт root для верхнего документа, document для вложенных документов, array для BSON-массивов. Сам метод возвращает void; преобразованные данные мы получаем при чтении курсора.
Здесь два новых курсора нужны, чтобы честно показать два варианта декодирования одного запроса. Мы не читаем первый курсор повторно и не меняем карту посередине частично прочитанного результата.
Когда способ подходит и где его границы
Карта типов не является схемой проверки полей и не превращает BSON ObjectId, Decimal128 или UTCDateTime в произвольные скаляры. Пользовательские классы должны удовлетворять Unserializable; подробности fieldPaths и Persistable сохранены в карточке и связанной главе.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
function firstCustomer122(MongoDB\Driver\CursorInterface $cursor,bool $asArrays): array {
if($asArrays){$cursor->setTypeMap(['root'=>'array','document'=>'array','array'=>'array']);}
$items=$cursor->toArray();$first=$items[0];
return $asArrays?[get_debug_type($first),get_debug_type($first['customer']),$first['customer']['name']]:
[get_debug_type($first),get_debug_type($first->customer),$first->customer->name];
}
$options=['projection'=>['_id'=>0,'customer'=>1],'sort'=>['_id'=>1],'limit'=>1,'batchSize'=>1];
$first=cl122Query($manager,$ns['orders'],['status'=>'paid'],$options);
$second=cl122Query($manager,$ns['orders'],['status'=>'paid'],$options);
$objectResult=firstCustomer122($first,false);$arrayResult=firstCustomer122($second,true);
$rows=[['Без карты',...$objectResult],['Явная карта',...$arrayResult]];
$checks=['interface_accepted'=>$first instanceof MongoDB\Driver\CursorInterface && $second instanceof MongoDB\Driver\CursorInterface,
'default_objects'=>$objectResult===['stdClass','stdClass','Анна'],
'explicit_arrays'=>$arrayResult===['array','array','Анна']];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-interface-shape', ['Настройка', 'Корень документа', 'Вложенный customer', 'Прочитанное имя'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-interface-shape: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-interface-shape: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Измените только root на array, оставив document по умолчанию. Корень станет массивом, customer останется stdClass: путь к имени будет $row["customer"]->name.
Дважды отобразить небольшую страницу без повторного прохода курсора
Шаблон страницы дважды использует одни и те же два заказа: сначала выводит список, потом сумму. Сохраним маленькую выборку в массив один раз и покажем, почему повторный rewind курсора не заменяет это решение.
Откуда берутся данные
Запрос заранее ограничен limit=2 и нужными полями. toArray читает этот курсор полностью: получается массив заказов 1 и 2, сумма 3500 копеек.
- Запрос с limit=2 → только два подходящих документа
- toArray один раз → маленький PHP-массив
- Два прохода массива → список 1,2 и сумма 3500
- rewind старого курсора → LogicException: нельзя проиграть чтение заново
Короткая запись действия
$page = $cursor->toArray(); // запрос уже содержит limit=2
foreach ($page as $order) { echo $order['_id'], PHP_EOL; }
$sum = array_sum(array_column($page, 'total_kopecks'));Этот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Действие | Результат |
|---|---|
| Страница для списка | 1,2 |
| Сумма той же страницы | 3500 |
| Массив повторно перебран | да |
| Повторный rewind курсора | LogicException |
Как это работает
toArray — явная материализация: собрать все результаты именно этого курсора в память PHP. Слово «все» безопасно здесь потому, что запрос уже ограничен двумя небольшими документами, а не потому, что метод сам устанавливает предел.
После toArray два foreach идут по обычному массиву и не отправляют запросы. cursor->rewind после завершённой итерации намеренно проверяется отдельно и выдаёт LogicException. В рабочем шаблоне ошибочный вызов просто не нужен.
Новая функция materializePage122 принимает CursorInterface, но её название не ограничивает вход автоматически. Ограничение обеспечено конкретным Query вызывающего кода; передать туда курсор всей базы было бы иной, опасной по памяти задачей.
Когда способ подходит и где его границы
Не применяйте этот приём к сотням тысяч документов только ради второго прохода или сортировки. Для экспорта используйте поток, для суммы всего набора — агрегацию в базе, для нового запроса — новый Cursor. Отладчик тоже может израсходовать курсор, если вычисляет toArray в watch.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
$cursor=cl122Query($manager,$ns['orders'],['status'=>'paid'],[
'projection'=>['_id'=>1,'total_kopecks'=>1],'sort'=>['_id'=>1],'limit'=>2,'batchSize'=>1]);
cl122ArrayMap($cursor);
function materializePage122(MongoDB\Driver\CursorInterface $source): array{return $source->toArray();}
$page=materializePage122($cursor);$ids=[];$sum=0;
foreach($page as $order){$ids[]=$order['_id'];}
foreach($page as $order){$sum+=$order['total_kopecks'];}
$rewindFailed=false;
try{$cursor->rewind();}catch(MongoDB\Driver\Exception\LogicException $error){$rewindFailed=true;}
$rows=[['Страница для списка',implode(',',$ids)],['Сумма той же страницы',(string)$sum],
['Массив повторно перебран','да'],['Повторный rewind курсора',$rewindFailed?'LogicException':'неожиданный успех']];
$checks=['small_array'=>count($page)===2 && $ids===[1,2],'page_sum'=>$sum===3500,
'replay_not_supported'=>$rewindFailed,'cursor_finished'=>$cursor->isDead() && !$cursor->valid()];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-bounded-array', ['Действие', 'Результат'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-bounded-array: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-bounded-array: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Оставьте limit=2 и вычислите среднюю сумму показанных заказов: 1750 копеек. Если нужен итог всех оплаченных, изменять лимит на безграничный не нужно: воспользуйтесь агрегированным отчётом из предыдущего сценария.
Реальный вопрос разработчика: Trying to iterate over a mongodb cursor twice - failing. Шаблон хочет дважды пройти результат одного запроса и получает ошибку. Решение ограничивает набор на сервере и один раз материализует маленькую страницу; большим выгрузкам предлагается поток, а не массив всей базы. Обсуждение используется как постановка задачи; правила современного API проверены по первичным источникам.
Перейти от CursorId к Int64 и не создавать курсор вручную
Старый проект проверяет CursorId и пытается создать Cursor вручную. На текущем расширении нужно получить курсор через Manager и принять новый тип ID.
Откуда берутся данные
В расширении 2.5.5 getId() возвращает BSON\Int64. Запрос четырёх маленьких заказов с batchSize=2 оставляет серверное продолжение до чтения; его случайное значение фиксируется только в diagnostics.
- executeQuery → создан реальный Cursor
- getId() на 2.x → BSON\Int64 → десятичная строка журнала
- Полное чтение → заказы 1,2,4,5
- После чтения → getId() представляет 0; CursorId как класс отсутствует
Короткая запись действия
$id = $cursor->getId(); // ext-mongodb 2.x: MongoDB\BSON\Int64
$logValue = (string) $id; // не переводим в float и не используем как номер страницыЭтот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Проверка миграции | Результат |
|---|---|
| Фактический тип ID | MongoDB\BSON\Int64 |
| ID в журнале | десятичная строка, значение зависит от запуска |
| CursorId в расширении 2.x | удалён |
| Ручной new Cursor | запрещён приватным конструктором |
| Прочитанные заказы | 1,2,4,5 |
| ID после чтения | 0 |
Как это работает
Cursor::__construct приватный: экземпляры создаёт драйвер в результате команды или запроса. В примере намеренная попытка new Cursor ловит стандартный PHP Error о приватном конструкторе; это демонстрация ограничения, не рабочий способ создания.
CursorId был объявлен устаревшим в 1.20 и удалён в 2.0. На 1.20 переходный getId(true) давал Int64; на 2.x этот параметр удалён и используется getId() без аргументов. Старый пример php.net с true нужно читать вместе с таблицей версий.
Строковое представление сохраняет значение без потери точности float. Оно годится для диагностической корреляции с сервером, но не является количеством заказов. Не обещается положительность номера: программа допускает знак и проверяет только десятичную запись.
Когда способ подходит и где его границы
Значение ID непредсказуемо и меняется между запусками. В ожидаемой таблице показывается тип/свойство, а фактический номер хранится в metrics.json. ID сам по себе не переносит запрос, права, сессию и позицию приложения на новый HTTP-запрос.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
$cursor=cl122Query($manager,$ns['orders'],['status'=>'paid'],[
'projection'=>['_id'=>1],'sort'=>['_id'=>1],'limit'=>4,'batchSize'=>2]);
cl122ArrayMap($cursor);
$id=$cursor->getId();$text=(string)$id;$legacyExists=class_exists('MongoDB\Driver\CursorId',false);
$constructor=new ReflectionMethod(MongoDB\Driver\Cursor::class,'__construct');
$manualBlocked=false;
try{new MongoDB\Driver\Cursor;}catch(Error $error){$manualBlocked=str_contains($error->getMessage(),'private');}
$ids=[];foreach($cursor as $order){$ids[]=$order['_id'];}
$rows=[['Фактический тип ID',get_debug_type($id)],['ID в журнале','десятичная строка, значение зависит от запуска'],
['CursorId в расширении 2.x',$legacyExists?'есть':'удалён'],
['Ручной new Cursor',$manualBlocked?'запрещён приватным конструктором':'неожиданный успех'],
['Прочитанные заказы',implode(',',$ids)],['ID после чтения',(string)$cursor->getId()]];
$checks=['modern_id'=>$id instanceof MongoDB\BSON\Int64 && preg_match('/\A-?[0-9]+\z/D',$text)===1,
'server_cursor_was_open'=>$text!=='0','legacy_removed'=>!$legacyExists,
'private_constructor'=>$constructor->isPrivate() && $manualBlocked,
'actual_rows'=>$ids===[1,2,4,5],'id_after_consumption'=>(string)$cursor->getId()==='0'];
$details['initial_cursor_id_string']=$text;
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-id-migration', ['Проверка миграции', 'Результат'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-id-migration: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-id-migration: saved result.html and result.json
Чтение MongoDB 8.0.34 через mongodb 2.5.5; result.html/result.json показывают итог. Helper, README и проверка очистки включены.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. Найдите в старом коде проверки instanceof CursorId, getId(true) и new Cursor. Замените первые на современный контракт Int64, второй на getId() только после перехода на 2.x, создание — на выполненный Query/Command.
Исторический CursorId: список заказов
Проверить поведение старого расширения перед миграцией журнала списка заказов.
Откуда берутся данные
Изолированный PHP 8.4.27 с mongodb 1.20.1 обращается к тому же учебному MongoDB 8.0.34. Это исторический архив, не программа для текущего расширения 2.x.
- Старый фактический курсор → executeQuery возвращает четыре заказа
- getId() без true → CursorId и один ожидаемый E_DEPRECATED
- getId(true) → Int64 с тем же строковым значением
- Чтение и проверка → ID 1,2,4,5; ручной new CursorId запрещён
Короткая запись действия
$legacyId = $cursor->getId(); // только ext-mongodb 1.20, ожидается E_DEPRECATED
$text = (string) $legacyId;
$newType = $cursor->getId(true); // переходный вызов 1.20, не API 2.xЭтот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Проверка исторического API | Результат |
|---|---|
| Старый тип | MongoDB\Driver\CursorId |
| Строковое представление | десятичная строка |
| Переходный getId(true) | MongoDB\BSON\Int64 |
| ID обоих типов совпадает | да |
| Ожидаемых E_DEPRECATED | 1 |
| Ручной new CursorId | запрещён |
| Реально прочитанные заказы | 1,2,4,5 |
Как это работает
В старой модели CursorId тоже был объектом-значением, который возвращал драйвер. Приватный конструктор запрещал new CursorId, поэтому в архиве эта попытка специально перехватывает PHP Error. Рабочие значения получаются только от выполненного курсора.
__toString возвращает десятичную строку ID. На том же ещё не прочитанном курсоре вызов getId(true) создаёт переходный BSON\Int64; строки должны совпасть. Строка журнала сохраняется, хотя класс меняется.
Обработчик временно принимает только E_DEPRECATED с точным текстом данного getId(). Он фиксирует одно событие и восстанавливает прежний обработчик в finally. Остальные предупреждения не скрываются, ошибки выполнения запроса не выдаются за успех.
Когда способ подходит и где его границы
Эти два архива существуют для воспроизводимой проверки исторических карточек. Не заменяйте ради них DLL рабочего PHP и не переносите getId(true) в 2.x. Депрекация относится к старому пути getId; приведение уже полученного CursorId к строке не должно добавлять ещё одно предупреждение.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
$cursor=cl122Query($manager,$ns['orders'],['status'=>'paid'],['projection'=>['_id'=>1],'sort'=>['_id'=>1],'limit'=>4,'batchSize'=>2]);
cl122ArrayMap($cursor);$events=[];
$legacyId=cl122LegacyId($cursor,$events);
$legacyText=(string)$legacyId;
$transitionId=$cursor->getId(true);
$blocked=false;
try{new MongoDB\Driver\CursorId;}catch(Error $error){$blocked=str_contains($error->getMessage(),'private');}
$ids=[];foreach($cursor as $order){$ids[]=$order['_id'];}
$rows=[['Старый тип',get_debug_type($legacyId)],['Строковое представление','десятичная строка'],
['Переходный getId(true)',get_debug_type($transitionId)],
['ID обоих типов совпадает',$legacyText===(string)$transitionId?'да':'нет'],
['Ожидаемых E_DEPRECATED',count($events)],['Ручной new CursorId',$blocked?'запрещён':'неожиданный успех'],
['Реально прочитанные заказы',implode(',',$ids)]];
$checks=['old_type'=>$legacyId instanceof MongoDB\Driver\CursorId,'string_preserved'=>preg_match('/\A-?[0-9]+\z/D',$legacyText)===1 && $legacyText!=='0',
'transition_same_id'=>$transitionId instanceof MongoDB\BSON\Int64 && $legacyText===(string)$transitionId,
'one_exact_deprecation'=>count($events)===1,'manual_constructor_blocked'=>$blocked,
'real_rows'=>$ids===[1,2,4,5]];
$details=['expected_deprecations'=>$events,'initial_cursor_id_string'=>$legacyText];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-id-query-legacy', ['Проверка исторического API', 'Результат'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-id-query-legacy: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-id-query-legacy: saved result.html and result.json
Историческая среда PHP 8.4.27 / mongodb 1.20.1 и учебный MongoDB 8.0.34; точный ожидаемый E_DEPRECATED учитывается отдельно.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. В отдельной старой среде используйте только getId(true): тип должен стать Int64 без данного E_DEPRECATED. Для современной среды откройте id-migration: там тот же переход завершается getId() без параметров.
Исторический CursorId: агрегационный отчёт
Проверить поведение старого расширения перед миграцией диагностики агрегационного отчёта.
Откуда берутся данные
Изолированный PHP 8.4.27 с mongodb 1.20.1 обращается к тому же учебному MongoDB 8.0.34. Это исторический архив, не программа для текущего расширения 2.x.
- Старый фактический курсор → executeReadCommand с aggregate возвращает четыре заказа
- getId() без true → CursorId и один ожидаемый E_DEPRECATED
- getId(true) → Int64 с тем же строковым значением
- Чтение и проверка → ID 1,2,4,5; ручной new CursorId запрещён
Короткая запись действия
$legacyId = $cursor->getId(); // только ext-mongodb 1.20, ожидается E_DEPRECATED
$text = (string) $legacyId;
$newType = $cursor->getId(true); // переходный вызов 1.20, не API 2.xЭтот фрагмент выделяет текущий вызов. Подготовленные переменные, фикстура и очистка включены в полную программу ниже.
Что получится
| Проверка исторического API | Результат |
|---|---|
| Старый тип | MongoDB\Driver\CursorId |
| Строковое представление | десятичная строка |
| Переходный getId(true) | MongoDB\BSON\Int64 |
| ID обоих типов совпадает | да |
| Ожидаемых E_DEPRECATED | 1 |
| Ручной new CursorId | запрещён |
| Реально прочитанные заказы | 1,2,4,5 |
Как это работает
В старой модели CursorId тоже был объектом-значением, который возвращал драйвер. Приватный конструктор запрещал new CursorId, поэтому в архиве эта попытка специально перехватывает PHP Error. Рабочие значения получаются только от выполненного курсора.
__toString возвращает десятичную строку ID. На том же ещё не прочитанном курсоре вызов getId(true) создаёт переходный BSON\Int64; строки должны совпасть. Строка журнала сохраняется, хотя класс меняется.
Обработчик временно принимает только E_DEPRECATED с точным текстом данного getId(). Он фиксирует одно событие и восстанавливает прежний обработчик в finally. Остальные предупреждения не скрываются, ошибки выполнения запроса не выдаются за успех.
Когда способ подходит и где его границы
Эти два архива существуют для воспроизводимой проверки исторических карточек. Не заменяйте ради них DLL рабочего PHP и не переносите getId(true) в 2.x. Депрекация относится к старому пути getId; приведение уже полученного CursorId к строке не должно добавлять ещё одно предупреждение.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/cl122-helper.php';
[$manager,$database,$runtime]=cl122Connect();
$owned=[];$details=[];
try{
$ns=cl122Create($manager,$database,['orders'],$owned);
$collection=substr($ns['orders'],strlen($database)+1);
cl122Seed($manager,$ns['orders'],cl122Fixture());
$cursor=$manager->executeReadCommand($database,new MongoDB\Driver\Command(['aggregate'=>$collection,
'pipeline'=>[['$match'=>['status'=>'paid']],['$sort'=>['_id'=>1]],['$limit'=>4],['$project'=>['_id'=>1]]],
'cursor'=>['batchSize'=>2],'maxTimeMS'=>2000]));
cl122ArrayMap($cursor);$events=[];
$legacyId=cl122LegacyId($cursor,$events);
$legacyText=(string)$legacyId;
$transitionId=$cursor->getId(true);
$blocked=false;
try{new MongoDB\Driver\CursorId;}catch(Error $error){$blocked=str_contains($error->getMessage(),'private');}
$ids=[];foreach($cursor as $order){$ids[]=$order['_id'];}
$rows=[['Старый тип',get_debug_type($legacyId)],['Строковое представление','десятичная строка'],
['Переходный getId(true)',get_debug_type($transitionId)],
['ID обоих типов совпадает',$legacyText===(string)$transitionId?'да':'нет'],
['Ожидаемых E_DEPRECATED',count($events)],['Ручной new CursorId',$blocked?'запрещён':'неожиданный успех'],
['Реально прочитанные заказы',implode(',',$ids)]];
$checks=['old_type'=>$legacyId instanceof MongoDB\Driver\CursorId,'string_preserved'=>preg_match('/\A-?[0-9]+\z/D',$legacyText)===1 && $legacyText!=='0',
'transition_same_id'=>$transitionId instanceof MongoDB\BSON\Int64 && $legacyText===(string)$transitionId,
'one_exact_deprecation'=>count($events)===1,'manual_constructor_blocked'=>$blocked,
'real_rows'=>$ids===[1,2,4,5]];
$details=['expected_deprecations'=>$events,'initial_cursor_id_string'=>$legacyText];
}finally{
unset($cursor,$queryCursor,$aggregateCursor,$first,$second,$pageCursor,$legacyId,$transitionId,$resultCursor);
foreach(array_reverse($owned) as $name){cl122Command($manager,$database,['drop'=>$name],true);}
}
$removed=true;
foreach($owned as $name){if(cl122Exists($manager,$database,$name)){$removed=false;}}
$checks['owned_collections_removed']=$removed;
cl122Report('mongodb122-cursor-id-aggregate-legacy', ['Проверка исторического API', 'Результат'], $rows, $checks, $runtime, $details,[]);
echo 'mongodb122-cursor-id-aggregate-legacy: saved result.html and result.json
';
Ожидаемый результат
mongodb122-cursor-id-aggregate-legacy: saved result.html and result.json
Историческая среда PHP 8.4.27 / mongodb 1.20.1 и учебный MongoDB 8.0.34; точный ожидаемый E_DEPRECATED учитывается отдельно.
Открыть фактическую таблицу результата · Скачать JSON
Попробуйте изменить условие. В отдельной старой среде используйте только getId(true): тип должен стать Int64 без данного E_DEPRECATED. Для современной среды откройте id-migration: там тот же переход завершается getId() без параметров.
Миграция CursorId без потери строкового ID
| Среда | Получение ID | Фактический класс | Статус |
|---|---|---|---|
| До 1.20 | getId() | CursorId | Исторический API |
| 1.20 | getId() | CursorId | E_DEPRECATED; два реальных legacy архива |
| 1.20 | getId(true) | BSON\Int64 | Переходный вариант без этой депрекации |
| 2.x | getId() | BSON\Int64 | Актуальный вариант, asInt64 удалён |
Cursor и старый CursorId имеют приватные конструкторы. Получать их нужно из выполненной операции, а не из new. ID из лога не превращается в выборку сам по себе. Два старых архива сравнивают реальные представления одного серверного курсора и проверяют только ожидаемое событие E_DEPRECATED.
Справочник 11 методов и исторических точек входа
Четыре метода Cursor, два исторических метода CursorId и пять методов CursorInterface. Прежние канонические карточки методов итерации, Cursor::toArray и Cursor::setTypeMap сохранены в своих главах.
| Метод | Назначение / статус |
|---|---|
| MongoDB\Driver\Cursor::__construct | Почему курсор получают от выполненного запроса или команды, а не создают через new. |
| MongoDB\Driver\Cursor::getId | Получить непрозрачный серверный ID, не перепутав его с количеством строк или границей страницы. |
| MongoDB\Driver\Cursor::getServer | Узнать сервер, который исполнил конкретный запрос или команду. |
| MongoDB\Driver\Cursor::isDead | Проверить отсутствие дальнейших результатов с учётом буфера и состояния курсора. |
| MongoDB\Driver\CursorId::__construct | Удалён в 2.0; историческая карточка |
| MongoDB\Driver\CursorId::__toString | Удалён в 2.0; историческая карточка |
| MongoDB\Driver\CursorInterface::getId | Общий контракт получения ID для функции, принимающей курсорный результат. |
| MongoDB\Driver\CursorInterface::getServer | Единый способ получить исполнителя запроса и агрегирования. |
| MongoDB\Driver\CursorInterface::isDead | Контракт проверки возможного продолжения курсорного результата. |
| MongoDB\Driver\CursorInterface::setTypeMap | Задать PHP-форму декодируемых BSON-результатов до передачи в шаблон или обработчик. |
| MongoDB\Driver\CursorInterface::toArray | Материализовать заранее ограниченный результат, если нужен повторный локальный доступ. |
Выбрать режим чтения под результат для человека
Для страницы сначала сформулируйте нужные документы и поля, затем ограничьте запрос. Для экспорта обрабатывайте строки по мере чтения. Для повторного отображения маленькой страницы сохраните ограниченный массив один раз. Для следующего запроса храните границу данных, а не номер живого курсора.
Далее: какой сервер исполнил запрос и как читать его состояние; индексы под фильтр и порядок; отчёты, вычисляемые на сервере; согласованность и snapshot.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
MongoDB\Driver\Cursor::__constructПочему курсор получают от выполненного запроса или команды, а не создают через new.
mongodb 1.0.0 · mongodb
final private function MongoDB\Driver\Cursor::__construct()Почему курсор получают от выполненного запроса или команды, а не создают через new.
Версии и применимость
Класс и приватный конструктор существуют с ext-mongodb 1.0.0; конструктор остаётся приватным в 2.5.5. Возможность Cursor быть Iterator появилась в 1.9.0; она не разрешает ручное создание.
Сигнатура и параметры
final private function MongoDB\Driver\Cursor::__construct()| Параметр | Назначение |
|---|---|
| Нет параметров | Это приватный конструктор внутреннего класса; публичного вызова создания нет. |
Что возвращается и что может пойти не так
Конструктор не возвращает пользовательское значение. Cursor выдаётся методами выполнения Query или Command у Manager/Server.
Официальная страница не перечисляет отдельные исключения метода. Попытка new Cursor из пользовательского кода запрещена видимостью и приводит к PHP Error; в миграционном MAIN это намеренно показано.
Границы
Один курсор связан с выполненной операцией. Объект Query — описание, Manager — исполнитель, Cursor — результат чтения. Не пытайтесь восстановить результат из произвольного числа через конструктор.
Страница заказов
Выполненный Query даёт Cursor с лимитом страницы; new относится только к Query.
$cursor = $manager->executeQuery($namespace, new MongoDB\Driver\Query(['status'=>'paid'], ['sort'=>['_id'=>1], 'limit'=>3]));Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Агрегированный отчёт
executeReadCommand возвращает Cursor отчёта; тот же путь использован для реальных итогов 5 / 17000.
$cursor = $manager->executeReadCommand($database, $aggregateCommand);Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\Cursor::getIdПолучить непрозрачный серверный ID, не перепутав его с количеством строк или границей страницы.
mongodb 1.0.0 · mongodb
final public function MongoDB\Driver\Cursor::getId(): MongoDB\BSON\Int64Получить непрозрачный серверный ID, не перепутав его с количеством строк или границей страницы.
Версии и применимость
Метод с 1.0.0. В 1.20 возвращение CursorId объявлено устаревшим, добавлен asInt64=true для перехода. В 2.0 возвращается только Int64, аргумент удалён. Нижний исторический пример официальной страницы необходимо сопоставлять с текущей сигнатурой.
Сигнатура и параметры
final public function MongoDB\Driver\Cursor::getId(): MongoDB\BSON\Int64| Параметр | Назначение |
|---|---|
| Нет параметров в 2.x | Старый asInt64 удалён. В текущих примерах используется getId() без true. |
Что возвращается и что может пойти не так
BSON\Int64 с номером, идентифицирующим курсор на сервере. Строковое представление 0 означает отсутствие серверного продолжения; уже полученные документы могут оставаться локально.
InvalidArgumentException при ошибках разбора аргументов. Этот вызов не заменяет обработку возможных ошибок при дальнейшем чтении.
Границы
ID — не число найденных записей, не порядковый номер, не счётчик и не автономный токен продолжения. Для диагностики храните его строкой вместе с контекстом сервера/операции; не переводите через float.
Разобраться с ID 0
В singleBatch серверное продолжение закрыто, но первый заказ доступен: valid=true, isDead=false.
$serverId = (string) $cursor->getId();
$hasCurrent = $cursor->valid();Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Обновить формат журнала
Текущий Int64 записывается в диагностический JSON строкой, а программа проверяет реальное чтение четырёх заказов.
$id = $cursor->getId();
$diagnostic = ['cursor_id'=>(string)$id];Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\Cursor::getServerУзнать сервер, который исполнил конкретный запрос или команду.
mongodb 1.0.0 · mongodb
final public function MongoDB\Driver\Cursor::getServer(): MongoDB\Driver\ServerУзнать сервер, который исполнил конкретный запрос или команду.
Версии и применимость
Метод существует с ext-mongodb 1.0.0. В текущем 2.5.5 возвращает Server.
Сигнатура и параметры
final public function MongoDB\Driver\Cursor::getServer(): MongoDB\Driver\Server| Параметр | Назначение |
|---|---|
| Нет параметров | Метод использует связь текущего курсора с сервером. |
Что возвращается и что может пойти не так
Объект Server, связанный с этим результатом: сервер, выполнивший Query или Command.
InvalidArgumentException при ошибках разбора аргументов. Чтение этого объекта не является тестом успешности следующей сетевой операции.
Границы
Не выводите var_dump объекта подключения в публичный ответ приложения. В учебном отчёте выбираются только host/port, сравниваются с явным лабораторным URI; авторизационных данных в нём нет.
Источник страницы
У обычного Query читаются host/port именно его курсора, а не первый адрес произвольного URI.
$server = $queryCursor->getServer();
$host = $server->getHost();Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Источник агрегированного итога
Вторая проверка относится к Command aggregate: она связывает итог 5 / 17000 с фактическим endpoint.
$server = $aggregateCursor->getServer();
$port = $server->getPort();Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\Cursor::isDeadПроверить отсутствие дальнейших результатов с учётом буфера и состояния курсора.
mongodb 1.0.0 · mongodb
final public function MongoDB\Driver\Cursor::isDead(): boolПроверить отсутствие дальнейших результатов с учётом буфера и состояния курсора.
Версии и применимость
С ext-mongodb 1.0.0. Особая польза — управление tailable-курсорами; данные MAIN используют обычные конечные запросы.
Сигнатура и параметры
final public function MongoDB\Driver\Cursor::isDead(): bool| Параметр | Назначение |
|---|---|
| Нет параметров | Читается состояние текущего курсора; метод не читает все документы для подсчёта. |
Что возвращается и что может пойти не так
true, когда дополнительных результатов определённо нет; false, когда продолжение возможно. Непрочитанный локальный буфер или ненулевой ID могут позволять дальнейшее чтение.
InvalidArgumentException при ошибках разбора аргументов. Ошибка, произошедшая во время итерации, также может сделать курсор dead; это не значит, что приложение успешно прочитало весь ожидаемый набор.
Границы
Дополнительных результатов нет после полного чтения последней партии с ID 0, ошибки или достижения лимита. Если локальная партия пуста, но ID ненулевой, getMore может дать следующую порцию, пустой ответ либо закрытие. isDead не является заменой valid, подсчётом и обещанием отсутствия сетевых ошибок.
Буфер после закрытия сервера
Начальный ID 0 сочетается с isDead=false: три документа уже на клиенте. После цикла isDead=true.
$canContinue = !$cursor->isDead();Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Завершённый CSV
Проверка после успешного полного foreach дополняет число записей и сумму. Исключение во время записи или чтения не превращается в успешный отчёт.
$finished = $cursor->isDead() && (string)$cursor->getId() === '0';Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorId::__constructИсторический приватный конструктор удалённого CursorId и современный путь получения Int64.
mongodb 1.0.0 · mongodb
final private function MongoDB\Driver\CursorId::__construct()Исторический приватный конструктор удалённого CursorId и современный путь получения Int64.
Версии и применимость
Исторически с 1.0.0; класс deprecated с 1.20.0, удалён в 2.0.0. С 1.7.0 реализовывал Serializable, с 1.12.0 — Stringable при PHP 8.0+. Два архива -legacy выполняются под 1.20.1; актуальная альтернатива — getId(): Int64.
Сигнатура и параметры
final private function MongoDB\Driver\CursorId::__construct()| Параметр | Назначение |
|---|---|
| Нет параметров | Объекты создавались драйвером через Cursor::getId, не пользовательским new. |
Что возвращается и что может пойти не так
Публичного создаваемого значения нет. В расширении 1.x Cursor::getId мог вернуть CursorId; в 2.x этот класс отсутствует.
На старой поддерживающей среде new CursorId нарушает private-видимость и вызывает PHP Error. В 2.x самого класса нет. Не перехватывайте обе ситуации как «пустой результат запроса».
Границы
Историческая карточка сохранена для чтения старого кода, не как рекомендация установить старую библиотеку на рабочий сайт. Создание собственного числа не создаёт серверную выборку ни в старой, ни в новой модели.
Миграция списка заказов
Реальная старая программа получает ID от Query и отдельно доказывает запрет ручного конструктора.
$legacyId = $cursor->getId(); // только 1.20, ожидается E_DEPRECATEDФрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Миграция отчёта
У результата aggregate происхождение такое же: ID даёт драйвер. Нельзя подставить new CursorId для якобы восстановления отчёта.
$transitionId = $cursor->getId(true); // переходный Int64 в 1.20Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorId::__toStringИсторическое строковое представление ID и сохранение строки при переходе на Int64.
mongodb 1.0.0 · mongodb
final public function MongoDB\Driver\CursorId::__toString(): stringИсторическое строковое представление ID и сохранение строки при переходе на Int64.
Версии и применимость
Метод старого CursorId существовал с 1.0.0; класс deprecated 1.20.0, удалён 2.0.0. Для текущего BSON\Int64 по-прежнему можно получить строку через (string), но это уже другой класс и контракт.
Сигнатура и параметры
final public function MongoDB\Driver\CursorId::__toString(): string| Параметр | Назначение |
|---|---|
| Нет параметров | Вызовется также при приведении (string) уже полученного объекта CursorId. |
Что возвращается и что может пойти не так
Строковое представление номера курсора. Это значение для диагностики, не строка JSON с документами и не число результатов.
Страница метода не указывает собственных исключений. Депрекация в архивах относится к предшествующему getId() без true на 1.20, а не к дополнительному предупреждению от приведения полученного объекта к строке.
Границы
Не полагайтесь на совпадение случайного ID между отдельными запусками. Мы сравниваем два представления одного ещё не прочитанного курсора: CursorId и переходный Int64 на 1.20.
Сохранить строковое поле старого журнала
Строка query ID сохраняется в diagnostics, полученный результат заказов отдельно проверяется.
$logValue = (string) $legacyId;Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Проверить миграцию отчётного обработчика
Та же серверная операция должна дать одинаковую строку при старом CursorId и переходном Int64.
$sameId = (string)$legacyId === (string)$transitionId;Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorInterface::getIdОбщий контракт получения ID для функции, принимающей курсорный результат.
mongodb 1.6.0 · mongodb
abstract public function MongoDB\Driver\CursorInterface::getId(): MongoDB\BSON\Int64Общий контракт получения ID для функции, принимающей курсорный результат.
Версии и применимость
Интерфейс с 1.6.0. В 1.20 Int64 добавлен в tentative return type для перехода; с 2.0 CursorId удалён из возвращаемого типа. Актуальная карточка не принимает исторический asInt64.
Сигнатура и параметры
abstract public function MongoDB\Driver\CursorInterface::getId(): MongoDB\BSON\Int64| Параметр | Назначение |
|---|---|
| Нет параметров | Современная сигнатура интерфейса; объект конкретной реализации должен ей соответствовать. |
Что возвращается и что может пойти не так
ID курсора в современном BSON\Int64. У настоящего Cursor он идентифицирует серверное продолжение.
Официальная страница перечисляет InvalidArgumentException при ошибках аргументов. Интерфейс не выполняет запрос сам: поведение обеспечивает переданный объект.
Границы
CursorInterface описывает возможности результата, а не сохранённый запрос. Аннотация интерфейса не делает число ID пригодным для возобновления между HTTP-запросами.
Разделить номер курсора и записи страницы
Реальный Cursor реализует этот интерфейс. Из ID 0 нельзя заключить, что доступной строки нет.
$idText = (string) $cursor->getId();Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Современная диагностика вместо CursorId
Контракт результата принимает Int64, а журнал хранит строку. Набор данных проверяется отдельно.
$diagnosticId = (string) $cursor->getId();Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorInterface::getServerЕдиный способ получить исполнителя запроса и агрегирования.
mongodb 1.6.0 · mongodb
abstract public function MongoDB\Driver\CursorInterface::getServer(): MongoDB\Driver\ServerЕдиный способ получить исполнителя запроса и агрегирования.
Версии и применимость
С ext-mongodb 1.6.0; актуальный возвращаемый тип — Server.
Сигнатура и параметры
abstract public function MongoDB\Driver\CursorInterface::getServer(): MongoDB\Driver\Server| Параметр | Назначение |
|---|---|
| Нет параметров | Интерфейс фиксирует требуемый метод для функций диагностики. |
Что возвращается и что может пойти не так
Server, связанный с результатом выполненного Query или Command.
InvalidArgumentException при ошибках разбора аргументов; факт получения Server не подтверждает будущую доступность сервера.
Границы
Не создавайте new CursorInterface: это интерфейс. Проверка origin122 получает настоящие Cursor от Manager и не подменяет фактический endpoint настроенной строкой.
Общий диагностический адаптер для списка
origin122 принимает CursorInterface и извлекает сервер реального списка ID 1,2.
$queryOrigin = origin122($queryCursor, $endpoint);Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Тот же адаптер для отчёта
Агрегационный курсор обрабатывается без отдельной копии логики выбора host/port.
$reportOrigin = origin122($aggregateCursor, $endpoint);Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorInterface::isDeadКонтракт проверки возможного продолжения курсорного результата.
mongodb 1.6.0 · mongodb
abstract public function MongoDB\Driver\CursorInterface::isDead(): boolКонтракт проверки возможного продолжения курсорного результата.
Версии и применимость
Интерфейсный метод существует с 1.6.0; особенно полезен при работе с tailable-курсорами.
Сигнатура и параметры
abstract public function MongoDB\Driver\CursorInterface::isDead(): bool| Параметр | Назначение |
|---|---|
| Нет параметров | Не требуется передавать номер строки, размер партии или Server. |
Что возвращается и что может пойти не так
true, когда дополнительных результатов нет, false иначе. Конкретный драйвер учитывает итерацию, локальную партию, лимит и ошибки.
InvalidArgumentException при ошибках аргументов. Состояние после ошибки нельзя выдавать за успешное чтение: исключение нужно обработать отдельно.
Границы
В описании интерфейса перечислены конец обычного курсора, ошибка, полностью прочитанная последняя партия и достижение лимита. false не гарантирует наличие следующего документа. Для обычного Cursor текущую запись проверяет valid, а не этот метод.
Завершение маленькой страницы
После успешного чтения трёх заказов проверяются isDead и отсутствие текущей записи; лимит запроса уже соблюдён.
$finished = $cursor->isDead() && !$cursor->valid();Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Проверка полного CSV
К состоянию курсора добавляются реальные число строк и сумма, чтобы dead после ошибки не стал ложным успехом.
$finished = $cursor->isDead() && $count === 5 && $sum === 17000;Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorInterface::setTypeMapЗадать PHP-форму декодируемых BSON-результатов до передачи в шаблон или обработчик.
mongodb 1.6.0 · mongodb
abstract public function MongoDB\Driver\CursorInterface::setTypeMap(array $typemap): voidЗадать PHP-форму декодируемых BSON-результатов до передачи в шаблон или обработчик.
Версии и применимость
Метод интерфейса с 1.6.0; сама возможность Cursor::setTypeMap существует с 1.0.0. Сценарии используют 2.5.5.
Сигнатура и параметры
abstract public function MongoDB\Driver\CursorInterface::setTypeMap(array $typemap): void| Параметр | Назначение |
|---|---|
| typemap: array | Карта root/document/array и необязательная fieldPaths; полный смысл значений приведён ниже, а не ограничен тремя array в примерах. |
Что возвращается и что может пойти не так
void: новое значение не возвращается. Карта влияет на декодирование документов курсора; не меняет документы в базе.
InvalidArgumentException при разборе аргументов. В конкретном Cursor неверный класс карты может вызвать InvalidArgumentException также при декодировании: класс должен существовать, быть создаваемым и реализовать BSON\Unserializable.
| Ключ или значение карты | Полное правило |
|---|---|
| root | Верхний BSON-документ результата; document не заменяет это отдельное правило. |
| document | Вложенные BSON-документы, например customer. |
| array | BSON-массивы, например items; это отдельная категория, а не все значения PHP array. |
| fieldPaths | Карта конкретных путей; addresses.$.city выбирает city у каждого элемента массива addresses. Более точное правило пути позволяет отделить нужное поле от общей карты. |
| Не задано / null | Обычный BSON-документ становится stdClass, BSON-массив — PHP-массивом. Валидный __pclass может восстановить существующий класс Persistable. |
| array | Создаётся PHP-массив; специальная обработка __pclass не применяется. Поле, если оно получено, остаётся обычным элементом. |
| object / stdClass | Создаётся stdClass; специальная обработка __pclass не применяется. |
| bson | Для root/document/array получаются BSON\Document либо BSON\PackedArray; значение bson нельзя применять как значение конкретного fieldPaths. |
| Имя класса | Класс должен существовать, быть конкретным и реализовывать BSON\Unserializable. Валидный __pclass класса Persistable может иметь приоритет. Данные передаются в bsonUnserialize. |
Параметры BSON-декодирования: официальная карта типов. Практические классы, fieldPaths и __pclass: отображение документов в PHP-объекты. Явно выбирайте нужные поля вместе с __pclass, если используете это восстановление.
Границы
В Cursor 2.5.5 уже существующий текущий документ пересоздаётся при смене карты; ранее сохранённые вами PHP-значения не становятся автоматически другой формы. Поэтому задавайте карту до первого чтения. Типы BSON ObjectId/даты/decimal остаются отдельными значениями. Карта не заменяет проверку схемы. У BSON-документа с дублирующимися ключами преобразование в PHP-массив/объект теряет дубликаты; такие документы не создавайте.
Шаблон ожидает вложенные массивы
Функция с CursorInterface получает customer как массив и читает имя через два ключа.
$cursor->setTypeMap(['root'=>'array','document'=>'array','array'=>'array']);Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Общий обработчик агрегированного результата
cl122ArrayMap принимает тот же интерфейс и подготавливает строку отчёта для чтения orders/kopecks.
cl122ArrayMap($aggregateCursor);
$report = $aggregateCursor->toArray()[0];Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorInterface::toArrayМатериализовать заранее ограниченный результат, если нужен повторный локальный доступ.
mongodb 1.6.0 · mongodb
abstract public function MongoDB\Driver\CursorInterface::toArray(): arrayМатериализовать заранее ограниченный результат, если нужен повторный локальный доступ.
Версии и применимость
С ext-mongodb 1.6.0 для интерфейса; Cursor::toArray существует с 1.0.0. Современный Cursor не становится повторяемым после материализации.
Сигнатура и параметры
abstract public function MongoDB\Driver\CursorInterface::toArray(): array| Параметр | Назначение |
|---|---|
| Нет параметров | Лимит и фильтр задают Query/Command до выполнения, а не этот метод. |
Что возвращается и что может пойти не так
PHP-массив всех результатов данного курсора; вид каждого документа определяется картой типов. Метод выполняет итерацию, а не возвращает ленивый список.
Страница интерфейса указывает InvalidArgumentException при ошибках аргументов. Конкретная итерация Cursor также может завершиться ошибкой декодирования/сети либо LogicException при попытке повторного прохода. Не подменяйте эти ошибки пустым массивом.
Границы
Память растёт с общим размером результата, даже при малом batchSize. Если нужны два маленьких отображения — сохраните массив один раз. Если нужно обработать большой набор — потоковая запись/обработка, либо серверная агрегация.
Список и итог одной страницы
Два заказа читаются один раз, потом массив повторно используется в двух проходах без запроса.
$page = materializePage122($cursor);
$sum = array_sum(array_column($page, 'total_kopecks'));Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Страница с проверкой следующей
readPage122 материализует максимум size+1 записей, а не весь оплаченный каталог, затем убирает lookahead.
$page = $pageCursor->toArray();
$hasMore = count($page) > $size;Фрагмент показывает текущий вызов над подготовленными объектами. Полный самостоятельный пример с подготовкой и видимым результатом.
Связанные темы: модель партии, буфера и продолжения; итерация курсора и серверные отчёты; фактический сервер и выбор узла.
MongoDB\Driver\CursorIdПрочитать удалённый объект ID курсора и перенести журнал со старого CursorId на современный Int64.
ext-mongodb 1.0.0; deprecated 1.20.0; removed 2.0.0 · mongodb
final class MongoDB\Driver\CursorId implements Serializable, StringableСтарый журнал запросов сохраняет ID курсора как строку, а после обновления расширения проверка instanceof CursorId перестала работать. Исторический CursorId представлял номер серверного курсора; его возвращал драйвер, а не создавал прикладной код. Для сохранения строкового поля журнала нужно поменять ожидаемый тип объекта, сохранив смысл самой строки.
Исторический API. Класс доступен с ext-mongodb 1.0.0, объявлен устаревшим в 1.20.0 и удалён в 2.0.0. В современных программах Cursor::getId() возвращает MongoDB\BSON\Int64. Эта карточка объясняет старые проекты; новый код должен использовать современный контракт.
- В расширении 1.20 старый getId() возвращает CursorId с ожидаемой депрекацией
- Для того же курсора getId(true) даёт переходный BSON Int64
- Приведение обоих объектов к строке позволяет сравнить один серверный ID
- В 2.x аргумента больше нет: getId() сразу возвращает Int64
- Журнал хранит десятичную строку; документы по-прежнему читаются через курсор
// Справочный контракт исторического класса, не объявление для вашего проекта.
final class MongoDB\Driver\CursorId implements Serializable, Stringable {
final private function __construct();
final public function __toString(): string;
}| Часть контракта | Как прочитать |
|---|---|
| final | От исторического класса нельзя наследоваться. Это внутренний объект значения, создававшийся драйвером. |
| private __construct() | Публичных параметров создания нет. Попытка new CursorId на поддерживавшей его среде нарушала private-видимость и вызывала Error; в 2.x самого класса нет. |
| __toString(): string | Метод без параметров выдаёт строковое представление ID; приведение (string)$id вызывает эту возможность. Число строк запроса и документы в этой строке не закодированы. |
| Serializable | Исторически реализуется с ext-mongodb 1.7.0. Этот интерфейс PHP относится к представлению значения, а не к возможности восстановить серверную выборку из журнала. |
| Stringable | Реализуется с ext-mongodb 1.12.0 при PHP 8.0+. Он обозначает возможность строкового представления через __toString. |
Конструктор и строковое преобразование уже разобраны в отдельных карточках: CursorId::__construct и CursorId::__toString. У страницы __toString нет отдельного списка собственных исключений; в исторических программах ожидаемое E_DEPRECATED относится к получению старого типа через getId(), а не к обещанию дополнительных предупреждений при каждом приведении к строке.
Первое применение: сохранить ID чтения заказов
В историческом примере запроса отдельный PHP 8.4.27 с ext-mongodb 1.20.1 получает настоящий CursorId и переходный Int64 одного курсора. Проверяются строковое соответствие, один ожидаемый E_DEPRECATED, запрет ручного конструктора и чтение заказов 1, 2, 4, 5. Код и результат этого запуска уже существуют; для новой карточки они не выполняются повторно.
// Фрагмент существующей исторической программы, только ext-mongodb 1.20.x.
$legacyId = $cursor->getId(); // CursorId; ожидается E_DEPRECATED
$transitionId = $cursor->getId(true);
$sameLogValue = (string) $legacyId === (string) $transitionId;Второе применение: проверить обработчик отчёта aggregate
В историческом агрегатном отчёте источник курсора — команда aggregate. Проверки сравнивают два представления того же ID и отдельно подтверждают прочитанные данные. Это важно для приложения, в котором обычный поиск и отчёты используют разные точки входа: переход типа должен быть понятен в обоих местах. Фактический номер курсора зависит от запуска, поэтому нельзя закрепить произвольное число как ожидаемый ID.
Современный вариант и границы переноса
// Текущий ext-mongodb 2.x; $cursor получен от выполненной операции.
$id = $cursor->getId(); // MongoDB\BSON\Int64, без аргументов
$logValue = (string) $id;Полный современный запуск находится в миграции CursorId → Int64, контракт вызова — в Cursor::getId(), значение — в BSON Int64. В 1.20 аргумент true служил переходу; в 2.0 аргумент удалён. При чтении официальной страницы сверяйтесь с текущей сигнатурой и Changelog: сохранившийся там пример с getId(true) относится к прежнему переходному вызову.
ID не является сохранённым запросом и не заменяет фильтр, сортировку или данные страницы. Строковое преобразование не возобновляет истёкший курсор. Значение 0 после исчерпания серверной части не доказывает, что документов вообще не было: они могли находиться в локальном буфере. Эта граница разобрана в модели ID и буфера.
Упражнение. Найдите в старом обработчике instanceof CursorId, getId(true) и запись (string)$id. Что следует изменить после перехода на 2.x?
Решение
Проверку конкретного типа замените в соответствии с контрактом Int64, параметр true у getId уберите, а осмысленное сохранение строкового ID можно оставить. Не создавайте собственный CursorId или Int64 ради имитации настоящего курсора: объект должен прийти от выполненной операции. В отдельном старом окружении 1.20 переходный true остаётся исторически правильным.
Источники и обсуждения
- MongoDB\Driver\Cursor::__construct
- MongoDB\Driver\Cursor::getId
- MongoDB\Driver\Cursor::getServer
- MongoDB\Driver\Cursor::isDead
- MongoDB\Driver\CursorId::__construct
- MongoDB\Driver\CursorId::__toString
- MongoDB\Driver\CursorInterface::getId
- MongoDB\Driver\CursorInterface::getServer
- MongoDB\Driver\CursorInterface::isDead
- MongoDB\Driver\CursorInterface::setTypeMap
- MongoDB\Driver\CursorInterface::toArray
- Класс Cursor
- Параметры Query
- История CursorId
- PHP Manual — CursorId
- PHP Manual — Cursor::getId
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


