» Хвостовые курсоры —
особый тип курсора СУБД MongoDB, который разрешает клиенту читать отдельные результаты,
а затем ждать, пока не появятся дополнительные документы.
Эти курсоры часто используются
с » Capped Collections
и » Change Streams.
Хотя стандартные курсоры возможно перебрать конструкцией foreach один раз,
этот подход не сработает с хвостовыми курсорами.
Цикл останавливается по достижении конца начального набора результатов,
когда хвостовой курсор перебирают конструкцией foreach.
Попытка продолжить итерацию курсора со вторым foreach выбросить исключение,
поскольку PHP пытается перемотать курсор. Подобно объектам результатов в других драйверах баз данных,
курсоры в СУБД MongoDB поддерживают только продвижение вперёд, поэтому их нельзя перемотать.
Для непрерывного считывания с хвостового курсора объект курсора заворачивают
в объект IteratorIterator. Это разрешает приложению напрямую управлять
итерацией курсора, избегать непреднамеренного перемотки курсора и решать, когда ждать новых результатов
или прекратить итерацию.
Продемонстрируем хвостовой курсор в действии,
для этого напишем два скрипта: «производитель» (producer) и «потребитель» (consumer).
Скрипт продюсера создаст новую capped-коллекцию командой
» create
и начнёт вставку нового документа в эту коллекцию каждую секунду.
<?php
$manager = new MongoDB\Driver\Manager;
$manager->executeCommand('test', new MongoDB\Driver\Command([
'create' => 'asteroids',
'capped' => true,
'size' => 1048576,
]));
while (true) {
$bulkWrite = new MongoDB\Driver\BulkWrite;
$bulkWrite->insert(['createdAt' => new MongoDB\BSON\UTCDateTime]);
$manager->executeBulkWrite('test.asteroids', $bulkWrite);
sleep(1);
}
?>
Пока скрипт продюсера (producer) запущен,
выполняют второй пользовательский скрипт для чтения вставленных документов
с помощью хвостового (tailable) курсора, обозначенного параметрами
tailable и awaitData
в методе MongoDB\Driver\Query::__construct().
<?php
$manager = new MongoDB\Driver\Manager;
$query = new MongoDB\Driver\Query([], [
'tailable' => true,
'awaitData' => true,
]);
$cursor = $manager->executeQuery('test.asteroids', $query);
$iterator = new IteratorIterator($cursor);
$iterator->rewind();
while (true) {
if ($iterator->valid()) {
$document = $iterator->current();
printf("Пользовательский документ создан: %s\n", $document->createdAt);
}
$iterator->next();
}
?>
Пользовательский скрипт начнёт с быстрой печати всех доступных документов
в заблокированной коллекции, как если бы коллекцию перебирали конструкцией foreach;
но при достижении конца начального набора результатов перебор не завершится.
Поскольку курсор хвостовой, вызов метода IteratorIterator::valid()
будет блокировать и ждать дополнительных результатов.
Метод IteratorIterator::valid() также используется для проверки наличия
на каждом этапе данных, доступных для чтения.
Замечание:
В этом примере используется опция запроса awaitData,
чтобы проинструктировать сервер блокировать в течение короткого периода
(например, одну секунду) в конце набора результатов перед возвратом ответа драйверу.
Это используется для предотвращения агрессивного опроса (polling) сервера
при отсутствии результатов. Параметр maxAwaitTimeMS может
использоваться в сочетании с tailable и awaitData,
чтобы указать время, которое сервер должен блокировать, когда он достигнет конца набора результатов.