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

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

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

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

Глава 163 · После первого каталога и введения в SDAM

MongoDB: время жизни клиента, ссылки и события открытия/закрытия

Реальные товары, два клиента одного адреса, Manager и Cursor, readonly-классы событий, отказ подключения, окно подписки и ограниченный журнал заданий.

Когда данные уже получены, а клиент ещё жив

Страница каталога получила CUP и LAMP. Нужно завершить работу с отдельным клиентом базы и понять журнал: почему иногда есть Opening, но нет Closed; почему после unset клиент продолжает отвечать; почему закрытие одного клиента не означает остановку MongoDB. Будем читать реальные товары и связывать результат с жизненным циклом объектов.

Сначала различим четыре вещи. Manager — PHP-объект, через который приложение выполняет операции. Внутри расширения работает клиент libmongoc. Топология — клиентское представление доступных узлов. Сам mongod — отдельный серверный процесс. Освобождение PHP-переменной, уничтожение клиента, удаление узла из топологии и остановка mongod не являются одной операцией.

Объект можно представить как настроенный инструмент, а переменную — как доступ к нему. Две переменные могут вести к одному инструменту. Аналогия заканчивается на владении: PHP дополнительно хранит внутренние ссылки, например от Cursor к Manager. Их мы покажем реальным опытом. Базовые понятия доступны в классах и объектах, переменных и интерфейсах; первый полезный запрос разобран в каталоге MongoDB.

Переменные, клиент, топология и четыре события

Один запрос каталога и время жизни его клиента
  1. Обычные документы CUP 500 и LAMP 1200 → известны входные данные
  2. Global observer зарегистрирован → новый отдельный Manager ещё не выполнил I/O
  3. Первый find → TopologyOpening и ServerOpening → две строки в PHP
  4. Cursor/дополнительные переменные освобождены → удаляется последняя ссылка Manager
  5. ServerClosed и TopologyClosed → observer снимается; товары остаются для HTML
СобытиеЧто сообщаетЧего не доказывает
TopologyOpeningОткрыта клиентская топологияЧто каждый new Manager создал новое TCP-соединение
ServerOpeningСервер добавлен в топологиюЧто сервер ответил и запрос успешен
ServerClosedСервер удалён из этой топологииЧто mongod остановлен или недоступен другому клиенту
TopologyClosedКлиентская топология закрытаЧто завершились все клиенты процесса/сервера

Событие приходит в callback — обычный метод подписчика, который вызывает драйвер. В записи $event->getHost() стрелка обращается к методу объекта; пустые скобки означают отсутствие аргументов; возвращается строка host. Код наблюдателя сохраняет короткую копию значений. Он не посылает запросов из callback, не пишет посетителю сырой журнал и не удерживает Manager, Server, Cursor, Event или exception.

$host = $event->getHost();
$port = $event->getPort();
$topologyId = (string) $event->getTopologyId();

Этот короткий фрагмент помещается в serverOpening или serverClosed. Для обычного стенда host равен 127.0.0.1, port — 27075, а ID создаёт драйвер. Нельзя получить настоящее событие через new: подписчик получает его от расширения. Четыре класса и все восемь getter полностью разобраны ниже; классы heartbeat изучаются отдельно.

Почему unset и persistence требуют разных объяснений

С ext-mongodb 1.2 клиент libmongoc по умолчанию сохраняется в процессе PHP worker и может переиспользовать соединения и сведения о топологии между запросами. Одинаковые URI/настройки могут разделять этот сохранённый клиент. Поэтому количество PHP-переменных или вызовов new само по себе не равно количеству новых сетевых подключений.

Для изолированного опыта используется третий аргумент Manager:

$client = new MongoDB\Driver\Manager(
    $uri,
    ['directConnection'=>true, 'retryWrites'=>false],
    ['disableClientPersistence'=>true]
);

Теперь клиент не сохраняется и не разделяется с другим Manager. Его освобождение можно наблюдать до RSHUTDOWN — стадии завершения запроса PHP. TopologyOpening может отсутствовать при использовании ранее сохранённого клиента; TopologyClosed требует отключённой persistence и освобождения до этой стадии. Снятый раньше observer также не увидит окончание.

Запустить самостоятельные примеры на своём стенде

Все восемь примеров — отдельные программы CLI. Используется PHP 8.4+ с ext-mongodb 2.5+ и отдельный локальный MongoDB 8.0+. Здесь фактически установлен PHP 8.5.11, расширение 2.5.5 и сервер 8.0.34. Composer не нужен; сайт показывает готовые результаты, браузер читателя сам не обращается к этой учебной базе.

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

ZIP содержит example.php, ml125-helper.php и README.txt с установкой расширения, проверкой CLI php.ini и запуском отдельного mongod. URI допускает только loopback без учётных данных; это не инструкция для публичного сервера. После запуска откройте result.html. Таблица повторяет данные урока, подробности содержат реальные ID и адреса, result.json содержит строки, а metrics.json — проверки и хеши.

Каждый архив сначала создаёт свою коллекцию mongodb125_lifecycle_… со случайным окончанием, затем читает только нужные поля с ограничением строк. Успешный create устанавливает владение; finally удаляет точное имя и проверяет отсутствие. Fixture-клиент работает вне наблюдаемого окна. Никакие чужие коллекции, настройки сервера или процессы не меняются. При принудительном завершении процесса finally может не выполниться.

Страница каталога: данные получены, клиент освобождён

Каталог уже получил две карточки. Нужно понять, где заканчивается работа клиента базы и почему готовый массив товаров продолжает существовать после его освобождения. Итог — товары CUP за 500 копеек и LAMP за 1200, а рядом три понятных этапа времени жизни клиента.

Исходные данные и понятия

Коллекция содержит два документа с полями sku и price. Fixture-клиент создаёт их заранее, до подписки. Отдельный наблюдаемый Manager — объект, через который отправляется запрос; Cursor — объект полученного результата; products — обычный PHP-массив прочитанных значений. Helper запроса освобождает свой локальный Cursor при возврате массива.

Путь данных и освобождения объектов
  1. Свой каталог: CUP 500 и LAMP 1200 → observer регистрируется
  2. Manager создан: запросов ещё нет, журнал пуст
  3. find с projection/sort/limit → две строки products
  4. unset последней переменной Manager → ServerClosed и TopologyClosed; products остаётся

Самая короткая запись действия

$products = ml125Products($client, $namespace);
unset($client); // Данные products остаются, ссылка на Manager удаляется.

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

ЭтапТоваров в PHPTopologyOpeningTopologyClosed
До первого запроса000
Товары прочитаны210
Последняя ссылка удалена211

Как читать результат

Первая строка таблицы подтверждает ленивое подключение именно этого запуска. После запроса топология открыта, но ещё не закрыта. После unset счётчик TopologyClosed становится равен одному. Конкретные host, port и topologyId находятся в подробном журнале result.html; строка ID каждый раз новая. Дополнительно на настоящих событиях сверяются свойства, getter, final и невозможность создать событие самому.

Границы применения

Здесь persistence намеренно отключена третьим аргументом Manager. В обычном приложении освобождение переменной не обязано уничтожить сохраняемый libmongoc-клиент. События не измеряют все TCP-соединения MongoDB, а результат чтения не является доказательством высокой доступности. Если другая переменная или Cursor удерживает Manager, момент закрытия сдвинется — следующие два сценария показывают это отдельно.

Полная программа, helper, инструкция и архив
Страница каталога: данные получены, клиент освобождёнСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
$journal=new Ml125Journal(32,true);$client=null;
    MongoDB\Driver\Monitoring\addSubscriber($journal);
    try {
        $client=ml125Manager($uri);$before=count($journal->events);
        $products=ml125Products($client,$namespace);$afterRead=$journal->count('topologyClosed');
        unset($client);$afterRelease=$journal->count('topologyClosed');
        $contracts=true;
        foreach ($journal->contracts as $contract) {
            $contracts=$contracts && $contract['same'] && $contract['final'] && $contract['readonly']
                && $contract['not_instantiable'] && $contract['new_rejected'];
        }
        $checks=['lazy_before_io'=>$before===0,'products_read'=>$products===[['sku'=>'CUP','price'=>500],['sku'=>'LAMP','price'=>1200]],
            'last_reference_released'=>$afterRead===0 && $afterRelease===1,'one_actual_cycle'=>ml125OneCycle($journal),
            'endpoint_pair'=>ml125EndpointPair($journal,$uri),'four_event_contracts'=>count($journal->contracts)===4 && $contracts];
        $details=['products'=>$products,'actual_lifecycle'=>$journal->events,'actual_class_contracts'=>$journal->contracts];
        return [['До первого запроса',0,$before,0],['Товары прочитаны',count($products),$journal->count('topologyOpening'),$afterRead],
            ['Последняя ссылка удалена',count($products),$journal->count('topologyOpening'),$afterRelease]];
    } finally { unset($client);MongoDB\Driver\Monitoring\removeSubscriber($journal); }
});
$checks['owned_collection_removed']=true;
$columns=['Этап', 'Товаров в PHP', 'TopologyOpening', 'TopologyClosed'];
ml125Report('mongodb125-lifecycle-request-timeline',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-request-timeline: OK
';
Ожидаемый результат
mongodb125-lifecycle-request-timeline: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Получите только CUP: замените фильтр [] на ['sku'=>'CUP'] и limit на 1. Ожидайте одну строку товара, но всё ещё один жизненный цикл. Связь числа товаров и числа клиентов не является взаимно однозначной.

Вопрос из практики: How to close MongoDB connection in PHP. После перехода на новый драйвер пользователь пытается вызвать старый close. В главе разделены объект Manager, persistence и наблюдаемое освобождение; старый ответ не используется как современный контракт. Обсуждение задаёт проблему; актуальные контракты проверены по PHP.net, данные и решения этой главы написаны отдельно.

Два клиента одного адреса: не смешивать их события

На одной странице работают каталог и отчёт. В журнале у обоих адрес 127.0.0.1:27075. Сопоставление только по адресу ошибочно приписало бы закрытие каталога отчёту. Свяжем каждый открытый клиент с его собственным закрытием.

Исходные данные и понятия

Каталог читает CUP, отчёт — LAMP из той же собственной коллекции. Оба Manager создаются с отключённой persistence, поэтому у них отдельные libmongoc-клиенты. topologyId — ObjectId идентификатора топологии; преобразование в строку удобно для ключа журнала, но не превращает его в _id товара.

Путь данных и освобождения объектов
  1. Клиент каталога → CUP → topologyId A
  2. Второй клиент того же endpoint → LAMP → topologyId B
  3. Освободить каталог → Closed только для A
  4. Освободить отчёт → Closed для B; адреса обеих пар совпадают

Самая короткая запись действия

$id = (string) $event->getTopologyId();
// Адрес один, а ID клиента каталога и клиента отчёта различны.

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

НазначениеПрочитанный товарЦена, коп.Своё закрытие найдено
КаталогCUP500true
ОтчётLAMP1200true

Как читать результат

Полная программа не подставляет A и B вместо настоящих значений: реальные ID сохранены в diagnostics, а буквы здесь — только схема. Проверяются различие двух ID, принадлежность первого закрытия каталогу, последовательное появление второго закрытия и одинаковый endpoint всех серверных событий. Таблица показывает полезный результат каждой операции.

Границы применения

Это два намеренно отдельных клиента. При включённой persistence одинаковые настройки могут использовать сохранённый клиент; по числу PHP-переменных нельзя вывести число новых соединений. topologyId годится для связи событий данного клиента, но не заменяет business request ID и не кодирует назначение «каталог». Назначение добавляет программа.

Полная программа, helper, инструкция и архив
Два клиента одного адреса: не смешивать их событияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
$journal=new Ml125Journal;$catalog=null;$report=null;
    MongoDB\Driver\Monitoring\addSubscriber($journal);
    try {
        $catalog=ml125Manager($uri);$cup=ml125Products($catalog,$namespace,['sku'=>'CUP'],1);
        $catalogId=$journal->ids('topologyOpening')[0];
        $report=ml125Manager($uri);$lamp=ml125Products($report,$namespace,['sku'=>'LAMP'],1);
        $reportId=$journal->ids('topologyOpening')[1];
        unset($catalog);$afterCatalog=$journal->ids('topologyClosed');
        unset($report);$afterBoth=$journal->ids('topologyClosed');
        $sameEndpoint=true;
        foreach ($journal->events as $event) {
            if (isset($event['host'])) { $sameEndpoint=$sameEndpoint && $event['host']===parse_url($uri,PHP_URL_HOST)
                && $event['port']===parse_url($uri,PHP_URL_PORT); }
        }
        $checks=['different_client_ids'=>$catalogId!==$reportId,'catalog_only_closed'=>$afterCatalog===[$catalogId],
            'both_clients_closed'=>$afterBoth===[$catalogId,$reportId],'same_endpoint'=>$sameEndpoint,
            'separate_results'=>$cup===[['sku'=>'CUP','price'=>500]] && $lamp===[['sku'=>'LAMP','price'=>1200]],
            'two_cycles'=>$journal->count('serverOpening')===2 && $journal->count('serverClosed')===2];
        $details=['client_labels'=>['catalog'=>$catalogId,'report'=>$reportId],'actual_lifecycle'=>$journal->events];
        return [['Каталог',$cup[0]['sku'],$cup[0]['price'],in_array($catalogId,$afterCatalog,true)],
            ['Отчёт',$lamp[0]['sku'],$lamp[0]['price'],in_array($reportId,$afterBoth,true)]];
    } finally { unset($catalog,$report);MongoDB\Driver\Monitoring\removeSubscriber($journal); }
});
$checks['owned_collection_removed']=true;
$columns=['Назначение', 'Прочитанный товар', 'Цена, коп.', 'Своё закрытие найдено'];
ml125Report('mongodb125-lifecycle-two-clients',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-two-clients: OK
';
Ожидаемый результат
mongodb125-lifecycle-two-clients: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Поменяйте порядок unset: сначала отчёт, потом каталог. Порядок Closed должен поменяться вместе с ID, а прочитанные CUP/LAMP — остаться прежними. Сверяйте ID, а не порядковый номер записи без контекста.

Вторая переменная удерживает тот же Manager

После unset($client) клиент всё ещё работает. Прежде чем искать утечку, проверим, не сохранил ли сервис вторую переменную с тем же объектом. Пример позволяет прочитать LAMP через оставшуюся переменную и увидеть закрытие только после её удаления.

Исходные данные и понятия

Выражение $alias = $client копирует идентификатор объекта: обе переменные обращаются к одному Manager. Это обычное присваивание, здесь нет PHP-оператора ссылки & и нет clone. Строгое сравнение === подтверждает идентичность объекта, а не просто похожие настройки.

Путь данных и освобождения объектов
  1. $client → один Manager → запрос CUP
  2. $alias = $client → две переменные, один Manager
  3. unset($client) → $alias остаётся → запрос LAMP успешен
  4. unset($alias) → больше ссылок нет → одно закрытие

Самая короткая запись действия

$alias = $client; // Две переменные указывают на один объект; это не clone.
unset($client);
$products = ml125Products($alias, $namespace);
unset($alias);

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

ДействиеTopologyClosedПрактический результат
unset($client)0LAMP доступен через $alias
unset($alias)1Данные LAMP остались в массиве

Как читать результат

В первой строке Closed равен нулю, потому что $alias сохраняет доступ к клиенту. Второй запрос не открывает отдельную топологию: в полном журнале только один Opening. После удаления $alias появляется одно Closed, а массив второй карточки по-прежнему содержит LAMP и 1200.

Границы применения

unset удаляет переменную в текущей области видимости; он не сообщает всем сервисам «закрой этот объект». Поле другого объекта, контейнер зависимостей или замыкание также могут удерживать Manager. Пример не предписывает отключать persistence в сервисном контейнере. Вместо бездумного unset нужно понимать, кто владеет клиентом и когда заканчивается работа этого владельца.

Полная программа, helper, инструкция и архив
Вторая переменная удерживает тот же ManagerСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
$journal=new Ml125Journal;$client=null;$alias=null;
    MongoDB\Driver\Monitoring\addSubscriber($journal);
    try {
        $client=ml125Manager($uri);$first=ml125Products($client,$namespace,['sku'=>'CUP'],1);
        $alias=$client;$same=$alias===$client;unset($client);$afterFirstUnset=$journal->count('topologyClosed');
        $second=ml125Products($alias,$namespace,['sku'=>'LAMP'],1);unset($alias);$afterLastUnset=$journal->count('topologyClosed');
        $checks=['same_object'=>$same,'alias_keeps_client'=>$afterFirstUnset===0,'last_alias_closes'=>$afterLastUnset===1,
            'read_through_alias'=>$second===[['sku'=>'LAMP','price'=>1200]],'one_client_not_two'=>ml125OneCycle($journal),
            'matching_endpoint'=>ml125EndpointPair($journal,$uri)];
        $details=['first_query'=>$first,'second_query'=>$second,'actual_lifecycle'=>$journal->events];
        return [['unset($client)',$afterFirstUnset,'LAMP доступен через $alias'],['unset($alias)',$afterLastUnset,'Данные LAMP остались в массиве']];
    } finally { unset($client,$alias);MongoDB\Driver\Monitoring\removeSubscriber($journal); }
});
$checks['owned_collection_removed']=true;
$columns=['Действие', 'TopologyClosed', 'Практический результат'];
ml125Report('mongodb125-lifecycle-manager-alias',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-manager-alias: OK
';
Ожидаемый результат
mongodb125-lifecycle-manager-alias: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Добавьте третью переменную $forReport = $alias. После первых двух unset закрытия не будет; оно появится после unset($forReport). Не используйте clone для имитации такой связи: это другая операция.

Вопрос из практики: How to close MongoDB idle connections in Lumen. Сервис видит много idle-соединений и ищет причину их жизни после действия. Разбираем владельцев объекта и настройку клиента, не объявляя каждое сохранённое соединение утечкой. Обсуждение задаёт проблему; актуальные контракты проверены по PHP.net, данные и решения этой главы написаны отдельно.

Прочитанный Cursor тоже может удерживать клиента

Обработчик выгрузки уже прочитал товары в массив и удалил переменную Manager, но закрытия в журнале нет. Покажем скрытую для начинающего связь: объект Cursor хранит ссылку на Manager даже после полного чтения результата.

Исходные данные и понятия

Запрос ограничен двумя товарами; projection отдаёт только sku и price, sort делает порядок понятным. batchSize=3 позволяет вернуть эти две строки одной порцией. toArray читает их в PHP-массив. Это ограниченный учебный пример; большой экспорт не следует целиком переносить в память.

Путь данных и освобождения объектов
  1. Manager выполняет find → Cursor удерживает Manager
  2. Cursor.toArray → обычный массив двух товаров
  3. unset Manager-переменной → Cursor ещё жив → Closed=0
  4. unset Cursor → Closed=1; обычные данные не исчезают

Самая короткая запись действия

$products = $cursor->toArray();
unset($client); // Cursor ещё хранит внутреннюю ссылку на Manager.
unset($cursor); // products — отдельный массив прочитанных данных.

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

ЭтапТоваров в обычном массивеTopologyClosed
После toArray и unset($client)20
После unset($cursor)21

Как читать результат

Объект результата и серверный cursor — разные уровни. Здесь результат уже полностью прочитан; дальнейших строк на сервере мы не ожидаем. Однако PHP-объект Cursor всё ещё существует и удерживает клиент. Освобождение этого объекта закрывает отдельный клиент, а два массива товара остаются пригодны для HTML.

Границы применения

Не следует трактовать отсутствие Closed как доказательство незавершённого запроса или утечки серверного cursor. В production клиент может намеренно жить дольше одной выдачи. Для длинной выгрузки важны потоковый перебор, ограничение порций и отсутствие случайно сохранённых Cursor; они разобраны в соседней главе. Этот опыт показывает зависимость объектов, а не метод принудительного разрыва соединений.

Полная программа, helper, инструкция и архив
Прочитанный Cursor тоже может удерживать клиентаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
$journal=new Ml125Journal;$client=null;$cursor=null;
    MongoDB\Driver\Monitoring\addSubscriber($journal);
    try {
        $client=ml125Manager($uri);
        $cursor=$client->executeQuery($namespace,new MongoDB\Driver\Query([],['projection'=>['_id'=>0,'sku'=>1,'price'=>1],
            'sort'=>['price'=>1],'limit'=>2,'batchSize'=>3,'maxTimeMS'=>1000]));
        $cursor->setTypeMap(['root'=>'array']);$products=$cursor->toArray();
        unset($client);$afterManager=$journal->count('topologyClosed');
        unset($cursor);$afterCursor=$journal->count('topologyClosed');
        $checks=['exact_two_rows'=>$products===[['sku'=>'CUP','price'=>500],['sku'=>'LAMP','price'=>1200]],
            'cursor_retains_manager'=>$afterManager===0,'cursor_release_closes'=>$afterCursor===1,
            'one_actual_cycle'=>ml125OneCycle($journal),'matching_endpoint'=>ml125EndpointPair($journal,$uri)];
        $details=['products'=>$products,'actual_lifecycle'=>$journal->events];
        return [['После toArray и unset($client)',count($products),$afterManager],['После unset($cursor)',count($products),$afterCursor]];
    } finally { unset($client,$cursor);MongoDB\Driver\Monitoring\removeSubscriber($journal); }
});
$checks['owned_collection_removed']=true;
$columns=['Этап', 'Товаров в обычном массиве', 'TopologyClosed'];
ml125Report('mongodb125-lifecycle-retained-cursor',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-retained-cursor: OK
';
Ожидаемый результат
mongodb125-lifecycle-retained-cursor: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Перенесите executeQuery и toArray в функцию, которая возвращает только массив. Локальный Cursor освободится при выходе. После удаления внешнего Manager закрытие больше не будет задержано переменной Cursor в вызывающем коде.

Четыре класса событий: данные доступны, изменение запрещено

При переходе на новое расширение хочется читать $event->host напрямую и случайно возникает соблазн изменить событие или создать его вручную для журнала. Проверим реальный контракт четырёх классов, полученных при чтении CUP и освобождении клиента.

Исходные данные и понятия

Callback — метод подписчика, который вызывает драйвер в момент события. В нём уже есть готовый $event. public даёт доступ на чтение; readonly запрещает переприсвоение свойства; final запрещает наследование класса. Эти определения не означают, что объект может самостоятельно открыть базу.

Путь данных и освобождения объектов
  1. Запрос CUP → реальные Opening-события
  2. Callback читает property и getter → сверяет значения
  3. Ожидаемая запись в readonly/new класса → Error обработан локально
  4. Освобождение клиента → реальные Closed-события → та же проверка

Самая короткая запись действия

// В настоящем callback serverOpening:
$host = $event->host;           // public readonly, ext-mongodb 2.3+
$same = $host === $event->getHost(); // getter доступен с 1.13
// $event->host = 'other';       // Error: нельзя менять readonly.

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

Настоящий классСвойства = getterfinalЗапись отклоненаnew отклонён
ServerOpeningEventtruetruetruetrue
ServerClosedEventtruetruetruetrue
TopologyOpeningEventtruetruetruetrue
TopologyClosedEventtruetruetruetrue

Как читать результат

Каждая строка результата получена из настоящего объекта своего класса. В helper проверяются все свойства события: host/port/topologyId у серверных классов и topologyId у топологических. Reflection подтверждает final и отсутствие доступного конструктора; попытки new и записи отвергаются. Журнал сохраняет только примитивные выводы, поэтому сами события не остаются в памяти подписчика.

Границы применения

Прямые public readonly properties добавлены в ext-mongodb 2.3.0; getter доступны с 1.13.0. Для совместимости со старыми версиями используйте getter. Попытки нарушения показаны как локально пойманные учебные ошибки, а не как обычная работа production callback. Нельзя вручную вызвать callback и выдать это за подтверждённое драйвером событие.

Полная программа, helper, инструкция и архив
Четыре класса событий: данные доступны, изменение запрещеноСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
$journal=new Ml125Journal(32,true);$client=null;
    MongoDB\Driver\Monitoring\addSubscriber($journal);
    try {
        $client=ml125Manager($uri);$product=ml125Products($client,$namespace,['sku'=>'CUP'],1);unset($client);
        $rows=[];$all=true;
        foreach (['serverOpening','serverClosed','topologyOpening','topologyClosed'] as $name) {
            $contract=$journal->contracts[$name];$row=[substr($contract['class'],strrpos($contract['class'],'\\')+1),
                $contract['same'],$contract['final'],$contract['readonly'],$contract['new_rejected']];
            $rows[]=$row;$all=$all && $contract['same'] && $contract['final'] && $contract['readonly']
                && $contract['new_rejected'] && $contract['not_instantiable'];
        }
        $checks=['real_query_before_events'=>$product===[['sku'=>'CUP','price'=>500]],'four_classes_checked'=>count($rows)===4 && $all,
            'real_open_close'=>ml125OneCycle($journal),'getter_endpoint_pair'=>ml125EndpointPair($journal,$uri)];
        $details=['actual_events'=>$journal->events,'contract_details'=>$journal->contracts];return $rows;
    } finally { unset($client);MongoDB\Driver\Monitoring\removeSubscriber($journal); }
});
$checks['owned_collection_removed']=true;
$columns=['Настоящий класс', 'Свойства = getter', 'final', 'Запись отклонена', 'new отклонён'];
ml125Report('mongodb125-lifecycle-event-contracts',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-event-contracts: OK
';
Ожидаемый результат
mongodb125-lifecycle-event-contracts: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Для ServerOpeningEvent сохраните только ['host'=>$event->getHost(),'port'=>$event->getPort()]. Измените уже этот обычный массив для подписи интерфейса. Это изменит вашу копию, а не реальное событие драйвера.

Opening есть, а подключение не удалось

Журнал содержит ServerOpening, а пользователь получил ошибку подключения. Противоречия нет: узел мог попасть в клиентское представление топологии до успешного общения с ним. Покажем этот случай, не останавливая рабочую MongoDB.

Исходные данные и понятия

Программа резервирует собственный случайный порт loopback без LISTEN. Он принадлежит этому запуску и не является чужим сервисом. Неудачный клиент получает connectTimeoutMS=300 и serverSelectionTimeoutMS=500. Его отдельный observer видит только четыре события жизненного цикла.

Путь данных и освобождения объектов
  1. Собственный порт без LISTEN → Manager пытается ping
  2. Узел уже добавлен → Opening есть, операция бросает ConnectionException
  3. Скопировать только имя ошибки → unset исключения и Manager → Closed
  4. Снять observer → обычный запрос рабочей базы → CUP успешно прочитан

Самая короткая запись действия

try { ml125Ping($client, $db); }
catch (MongoDB\Driver\Exception\ConnectionException $error) {
    $errorClass = get_class($error);
    unset($error); // Не оставляем ссылку на стек с Manager.
}
unset($client);

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

НаблюдениеРезультатЧто это означает
Рабочая базаtrueCUP
Собственный порт без LISTENfalseОжидаемый отказ
ServerOpening при отказеtrueУзел добавлен в топологию
TopologyClosed после освобожденияtrueКлиент освобождён

Как читать результат

После намеренного отказа код действительно заново читает CUP через рабочий URI. Поэтому таблица подтверждает доступность нормального стенда последующей операцией. Адрес отказа и его Opening/Closed сверяются отдельно. Объект исключения удаляется после копирования имени: его стек вызовов способен удерживать Manager и сдвигать закрытие.

Границы применения

Это локальный отказ соединения, не failover, не авария сервера и не испытание retry. Номинальные timeout не задают точного общего времени всей программы. Нельзя ловить любое Throwable и выдавать результат за ожидаемую сетевую ошибку: здесь обработан только класс ConnectionException, остальные ошибки проходят наружу. Для heartbeatFailed и содержимого hello есть отдельная глава.

Полная программа, helper, инструкция и архив
Opening есть, а подключение не удалосьСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
[$socket,$badUri]=ml125BoundFailure();$journal=new Ml125Journal;$client=null;$caughtClass=null;
    MongoDB\Driver\Monitoring\addSubscriber($journal);
    try {
        $client=ml125Manager($badUri,true);
        try { ml125Ping($client,$db);throw new RuntimeException('Unexpected connection success'); }
        catch (MongoDB\Driver\Exception\ConnectionException $error) { $caughtClass=get_class($error);unset($error); }
        unset($client);
        MongoDB\Driver\Monitoring\removeSubscriber($journal);
        $healthy=ml125Products(ml125Manager($uri),$namespace,['sku'=>'CUP'],1);
        $checks=['actual_connection_failure'=>$caughtClass!==null,'opening_is_not_success'=>$journal->count('serverOpening')===1,
            'own_failed_client_closed'=>ml125OneCycle($journal),'failure_endpoint_matched'=>ml125EndpointPair($journal,$badUri),
            'real_server_still_usable'=>$healthy===[['sku'=>'CUP','price'=>500]]];
        $details=['caught_class'=>$caughtClass,'owned_unlistened_endpoint'=>parse_url($badUri,PHP_URL_PORT),
            'healthy_product'=>$healthy,'actual_lifecycle'=>$journal->events];
        return [['Рабочая база',True,'CUP'],['Собственный порт без LISTEN',False,'Ожидаемый отказ'],
            ['ServerOpening при отказе',$journal->count('serverOpening')===1,'Узел добавлен в топологию'],
            ['TopologyClosed после освобождения',$journal->count('topologyClosed')===1,'Клиент освобождён']];
    } finally { unset($client);MongoDB\Driver\Monitoring\removeSubscriber($journal);fclose($socket); }
});
$checks['owned_collection_removed']=true;
$columns=['Наблюдение', 'Результат', 'Что это означает'];
ml125Report('mongodb125-lifecycle-connection-refused',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-connection-refused: OK
';
Ожидаемый результат
mongodb125-lifecycle-connection-refused: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Оставьте обработку ошибки вокруг ping, но запишите в пользовательский ответ нейтральное «база временно недоступна», а в ограниченный диагностический журнал — имя класса и свой request ID. Не возвращайте посетителю полный URI с будущими учётными данными.

Закрытие произошло за пределами подписки

Один журнал не содержит Closed, другой содержит. Это ещё не означает, что драйвер работал по-разному: первый подписчик могли снять слишком рано. Два наблюдателя одного реального клиента позволяют отделить событие от окна наблюдения.

Исходные данные и понятия

Оба subscriber зарегистрированы до первого запроса и видят одинаковый topologyId. После чтения CUP/LAMP первый удаляется через removeSubscriber, а второй остаётся. Затем удаляется последняя переменная клиента. Наблюдатели сохраняют независимые буферы.

Путь данных и освобождения объектов
  1. Observer early и complete → один Opening с одним ID
  2. Запрос вернул две карточки
  3. removeSubscriber(early) → он больше не слушает
  4. unset клиент → complete видит Closed; early уже не получает уведомление

Самая короткая запись действия

MongoDB\Driver\Monitoring\removeSubscriber($early);
unset($client);
// early уже не получает события; complete всё ещё зарегистрирован.

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

ПодписчикTopologyOpeningTopologyClosed
Снят перед unset10
Оставлен до unset11

Как читать результат

Одинаковая первая запись доказывает, что речь об одном клиенте. Полный наблюдатель получает пару открытия/закрытия и совпадающий endpoint. Ранний журнал остаётся без Closed, потому что подписка завершилась раньше. Наличие второго наблюдателя делает причину отсутствия проверяемой.

Границы применения

Отсутствие строки может объясняться временем регистрации, снятием, обрезкой буфера, persistence или удерживаемой ссылкой. Нельзя сразу называть его утечкой. Global подписка выбрана для полного жизненного цикла; Manager-scoped не обещает события до регистрации на уже созданном Manager. finally должен сохранить корректный порядок: сначала освобождение наблюдаемого клиента, затем окончательное снятие подписки.

Полная программа, helper, инструкция и архив
Закрытие произошло за пределами подпискиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
$early=new Ml125Journal;$complete=new Ml125Journal;$client=null;
    MongoDB\Driver\Monitoring\addSubscriber($early);MongoDB\Driver\Monitoring\addSubscriber($complete);
    try {
        $client=ml125Manager($uri);$products=ml125Products($client,$namespace);
        MongoDB\Driver\Monitoring\removeSubscriber($early);unset($client);
        $checks=['same_opening_seen'=>$early->ids('topologyOpening')===$complete->ids('topologyOpening') && count($early->ids('topologyOpening'))===1,
            'removed_observer_misses_close'=>$early->count('serverClosed')===0 && $early->count('topologyClosed')===0,
            'active_observer_sees_close'=>ml125OneCycle($complete),'endpoint_still_matches'=>ml125EndpointPair($complete,$uri),
            'query_returned_two'=>count($products)===2];
        $details=['early_observer'=>$early->events,'complete_observer'=>$complete->events,'products'=>$products];
        return [['Снят перед unset',$early->count('topologyOpening'),$early->count('topologyClosed')],
            ['Оставлен до unset',$complete->count('topologyOpening'),$complete->count('topologyClosed')]];
    } finally { unset($client);MongoDB\Driver\Monitoring\removeSubscriber($early);MongoDB\Driver\Monitoring\removeSubscriber($complete); }
});
$checks['owned_collection_removed']=true;
$columns=['Подписчик', 'TopologyOpening', 'TopologyClosed'];
ml125Report('mongodb125-lifecycle-unsubscribe-boundary',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-unsubscribe-boundary: OK
';
Ожидаемый результат
mongodb125-lifecycle-unsubscribe-boundary: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Снимите early сразу после регистрации, ещё до запроса. У него не должно быть ни Opening, ни Closed. Полный журнал и два товара останутся прежними. Добавьте в свой формат журнала явные границы начала и конца наблюдения.

Три задания и ограниченный журнал времени жизни

У короткого фонового задания диагностика полезна, но бесконечно растущий массив событий сам становится проблемой. Обработаем три конкретных задания CUP, LAMP, CUP и оставим только первые шесть подробных записей, сохранив общие счётчики.

Исходные данные и понятия

Каждое из трёх заданий читает ровно один товар. Отдельный клиент на задание выбран только для наглядного опыта. Подписчик учитывает четыре вида Opening/Closed, а остальные методы SDAMSubscriber пусты. Поэтому счётчики не зависят от количества heartbeat.

Путь данных и освобождения объектов
  1. Задание 1 → CUP → клиент освобождён
  2. Задание 2 → LAMP → клиент освобождён
  3. Задание 3 → CUP → клиент освобождён
  4. Из 12 выбранных lifecycle-записей сохранить 6 → discarded=6; счётчики каждого типа=3

Самая короткая запись действия

if (count($this->events) < 6) {
    $this->events[] = $row;
} else {
    ++$this->discarded;
} // Счётчик каждого типа обновляется перед ограничением буфера.

Это фрагмент текущего шага. Все переменные, регистрация observer, реальная коллекция, обработка ошибок и очистка включены в полную программу ниже.

Что получится

ЗаданиеТоварЦена, коп.Завершённых клиентов
1CUP5001
2LAMP12002
3CUP5003

Как читать результат

Таблица связывает завершение клиента с полезным результатом задания. В diagnostics видны первые шесть записей, discarded=6 и четыре счётчика по три. Буфер не растёт после лимита; отдельный словарь образцов содержит максимум четыре примитивные записи. Это конкретная политика хранения, а не молчаливая потеря информации.

Границы применения

Для длительного worker обычно разумно переиспользовать клиент; постоянное создание и уничтожение соединений имеет цену. Этот пример не рекомендует новый Manager для каждой строки и не сравнивает скорости. Ограниченный журнал не является надёжным аудитом: при необходимости полной истории нужна отдельная ограниченная очередь и доставка вне callback. Внешние отправки здесь не выполняются.

Полная программа, helper, инструкция и архив
Три задания и ограниченный журнал времени жизниСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/ml125-helper.php';
$runtime=ml125Runtime();[$uri,$db]=ml125Config();
$runtime['server']=ml125BuildVersion($uri,$db);
$fixture=ml125Manager($uri);$details=[];$checks=[];
$rows=ml125WithCatalog($fixture,$db,function(string $namespace) use($uri,$db,&$checks,&$details): array {
$journal=new Ml125Journal(6);$client=null;$rows=[];
    MongoDB\Driver\Monitoring\addSubscriber($journal);
    try {
        foreach (['CUP','LAMP','CUP'] as $index=>$sku) {
            $client=ml125Manager($uri);$products=ml125Products($client,$namespace,['sku'=>$sku],1);unset($client);
            $rows[]=[$index+1,$products[0]['sku'],$products[0]['price'],$journal->count('topologyClosed')];
        }
        $counts=$journal->counts;ksort($counts);
        $checks=['three_job_results'=>$rows===[[1,'CUP',500,1],[2,'LAMP',1200,2],[3,'CUP',500,3]],
            'all_four_counts_three'=>$counts===['serverClosed'=>3,'serverOpening'=>3,'topologyClosed'=>3,'topologyOpening'=>3],
            'bounded_six_records'=>count($journal->events)===6,'discarded_six_records'=>$journal->discarded===6,
            'bounded_four_samples'=>count($journal->samples)===4,'first_endpoint_matches'=>ml125EndpointPair($journal,$uri)];
        $details=['actual_first_six_events'=>$journal->events,'all_event_counts'=>$journal->counts,'discarded'=>$journal->discarded,
            'policy'=>'Only four lifecycle types; this is a three-job demonstration, not a durable audit log.'];return $rows;
    } finally { unset($client);MongoDB\Driver\Monitoring\removeSubscriber($journal); }
});
$checks['owned_collection_removed']=true;
$columns=['Задание', 'Товар', 'Цена, коп.', 'Завершённых клиентов'];
ml125Report('mongodb125-lifecycle-bounded-job-log',$columns,$rows,$checks,$runtime,$details);
echo 'mongodb125-lifecycle-bounded-job-log: OK
';
Ожидаемый результат
mongodb125-lifecycle-bounded-job-log: OK

Самостоятельный запуск на локальной MongoDB. События вызывает драйвер; HTML/JSON показывают фактические данные и журнал, а проверки отличают наблюдение от предположения.

Открыть таблицу и настоящий журнал · Данные таблицы JSON

Упражнение с направлением решения. Измените лимит с 6 на 4. Таблица трёх товаров должна совпасть, общие счётчики останутся по три, а discarded станет 8. Такой тест проверяет именно политику журнала, не подменяя результат бизнес-операции.

Вопрос из практики: What happens when connections to MongoDB are not closed?. В историческом цикле тысячи подключений открываются и закрываются, что приводит к исчерпанию локальных портов. Наш пример ограничен тремя заданиями и прямо не рекомендует такой lifecycle для каждой строки. Обсуждение задаёт проблему; актуальные контракты проверены по PHP.net, данные и решения этой главы написаны отдельно.

Четыре класса событий и все восемь методов чтения

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

Класс или методДля чего нужен
MongoDB\Driver\Monitoring\ServerOpeningEvent::getHostУзнать адрес узла, который добавлен в клиентскую топологию.
MongoDB\Driver\Monitoring\ServerOpeningEvent::getPortУвидеть порт узла при добавлении в топологию и отличить его от другого endpoint.
MongoDB\Driver\Monitoring\ServerOpeningEvent::getTopologyIdПривязать добавленный сервер к нужному клиенту.
MongoDB\Driver\Monitoring\ServerClosedEvent::getHostПонять, какой сервер удалён из наблюдаемой клиентской топологии.
MongoDB\Driver\Monitoring\ServerClosedEvent::getPortПрочитать порт удалённого серверного представления.
MongoDB\Driver\Monitoring\ServerClosedEvent::getTopologyIdЗакрыть именно нужную группу серверных событий.
MongoDB\Driver\Monitoring\TopologyOpeningEvent::getTopologyIdПолучить идентификатор открываемой клиентской топологии.
MongoDB\Driver\Monitoring\TopologyClosedEvent::getTopologyIdНайти закрытую топологию, когда освобождён отдельный клиент.
MongoDB\Driver\Monitoring\ServerOpeningEventСообщает о добавлении нового сервера в клиентскую топологию.
MongoDB\Driver\Monitoring\ServerClosedEventСообщает об удалении существующего сервера из клиентской топологии.
MongoDB\Driver\Monitoring\TopologyOpeningEventСообщает об открытии клиентской топологии и несёт её идентификатор.
MongoDB\Driver\Monitoring\TopologyClosedEventСообщает о закрытии клиентской топологии и позволяет сопоставить её с открытием.

От журнала к понятному владению клиентом

Чтобы объяснить отсутствие Closed, последовательно проверьте окно подписки, persistence и оставшиеся ссылки. Чтобы определить успешность операции, прочитайте её результат и обработайте исключение. Эти вопросы связаны, но ответ на один не заменяет другой.

Продолжение: hello и диагностика heartbeat, SDAMSubscriber и неизменяемые снимки, порции, чтение и срок жизни Cursor, начало и успешное окончание команды и серверные отказы и безопасный журнал. Для связи объектов полезны классы и объекты и владение зависимостями сервиса.

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

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

MongoDB\Driver\Monitoring\ServerOpeningEvent::getHostУзнать адрес узла, который добавлен в клиентскую топологию.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\ServerOpeningEvent::getHost(): string

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

Строка hostname сервера из события. На нашем прямом стенде это 127.0.0.1; в общем случае это может быть имя, поэтому метод не является преобразователем DNS-имени в IP.

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

ServerOpening не подтверждает успешный handshake. Один host может принадлежать нескольким клиентским топологиям; связывайте его с port и topologyId.

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

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

$row['host'] = $event->getHost();

Журнал двух клиентов подтверждает одинаковый host, но разные topologyId каталога и отчёта.

Полный пример с реальными данными, журналом и архивом

Проверить адрес намеренного отказа

$sameHost = $event->getHost() === parse_url($badUri, PHP_URL_HOST);

На собственном порту без LISTEN Opening тоже содержит 127.0.0.1; это адрес попытки, а не доказательство успешного подключения.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\ServerOpeningEvent::getPortУвидеть порт узла при добавлении в топологию и отличить его от другого endpoint.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\ServerOpeningEvent::getPort(): int

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

Целое число порта из события. Для нормального учебного сервера оно равно 27075; для намеренного отказа выбирается собственный динамический порт.

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

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

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

Проверить адрес каталога

$portMatches = $event->getPort() === parse_url($uri, PHP_URL_PORT);

Opening и Closed в итоговом журнале относятся к порту, заданному URI каталога.

Полный пример с реальными данными, журналом и архивом

Не перепутать рабочую базу и отказавший endpoint

$row['port'] = $event->getPort();

Фактический случайный порт сохраняется в diagnostics и сравнивается с собственным занятым сокетом; чужой сервис не затронут.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\ServerOpeningEvent::getTopologyIdПривязать добавленный сервер к нужному клиенту.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\ServerOpeningEvent::getTopologyId(): MongoDB\BSON\ObjectId

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

ObjectId идентификатора топологии, к которой относится сервер. Для ключа ограниченного журнала используется (string), сохраняющий значение ObjectId.

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

Это не _id товара и не server connectionId. По одному адресу нельзя найти клиента при нескольких Manager с отключённой persistence; ID генерируется драйвером и не задаётся в golden-таблице.

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

Отнести CUP и LAMP к разным клиентам

$row['topology_id'] = (string) $event->getTopologyId();

Два открытия одного endpoint попадают в разные группы; закрытие каталога не завершает группу отчёта.

Полный пример с реальными данными, журналом и архивом

Сопоставить серверное открытие и закрытие

$same = $opening['topology_id'] === $closed['topology_id'];

Helper сначала извлекает ID из настоящих событий и только затем сравнивает сохранённые значения в проверке пары.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\ServerClosedEvent::getHostПонять, какой сервер удалён из наблюдаемой клиентской топологии.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\ServerClosedEvent::getHost(): string

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

Строка hostname удалённого сервера. Значение относится к событию данного клиента; оно не является результатом текущего DNS-запроса.

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

Закрытие серверного представления не означает остановку mongod. Callback не должен запускать проверочный запрос на этот host: это изменило бы наблюдаемую работу.

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

Завершить адресную пару каталога

$row['host'] = $event->getHost();

После чтения двух товаров host закрытия сверяется с host открытия; массив товаров остаётся.

Полный пример с реальными данными, журналом и архивом

Подтвердить закрытие полным наблюдателем

$same = $closed['host'] === $opening['host'];

Только complete остаётся подписан до освобождения; его значения host получены реальными getter двух событий.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\ServerClosedEvent::getPortПрочитать порт удалённого серверного представления.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\ServerClosedEvent::getPort(): int

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

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

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

Нельзя считать этот вызов командой закрытия порта. Он читает данные уведомления; рабочая MongoDB продолжает слушать свой порт.

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

Связать закрытие с неслушающим портом

$same = $event->getPort() === parse_url($badUri, PHP_URL_PORT);

Closed соответствует именно собственному динамическому endpoint; затем выполняется реальный успешный запрос рабочего каталога.

Полный пример с реальными данными, журналом и архивом

Проверить единственную пару после последней переменной

$row['port'] = $event->getPort();

Одна пара host/port открывается до CUP и закрывается после удаления alias; чтение LAMP не создаёт второй клиент.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\ServerClosedEvent::getTopologyIdЗакрыть именно нужную группу серверных событий.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\ServerClosedEvent::getTopologyId(): MongoDB\BSON\ObjectId

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

ObjectId идентификатора топологии, из которой удалён сервер. Его значение сопоставляется с ID предыдущего открытия той же группы.

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

Не удаляйте все записи одинакового host при одном Closed: другой клиент того же адреса может продолжать работу. ID не измеряет число соединений.

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

Не завершить отчёт вместе с каталогом

$id = (string) $event->getTopologyId();

Первый Closed относится к каталогу, второй — к отчёту; исходные ID из Opening сохранены отдельно.

Полный пример с реальными данными, журналом и архивом

Убедиться, что закрыт удерживаемый Cursor клиент

$same = $opening['topology_id'] === $closed['topology_id'];

После unset Cursor приходит закрытие той же топологии, которая прочитала две строки; проверяется связь, а не только общий счётчик.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\TopologyOpeningEvent::getTopologyIdПолучить идентификатор открываемой клиентской топологии.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\TopologyOpeningEvent::getTopologyId(): MongoDB\BSON\ObjectId

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

ObjectId топологии. На одном Standalone внутри такой топологии известен один сервер, но топология и сервер — разные сущности.

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

Открытие может не наблюдаться при повторном использовании сохранённого libmongoc-клиента. В примерах observer зарегистрирован заранее, persistence отключена; для реального нового клиента события появляются при первом I/O.

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

Назначить диагностические метки двум клиентам

$catalogId = $journal->ids('topologyOpening')[0];

Метод helper читает сохранённые строковые значения getTopologyId, затем программа добавляет метки catalog/report, отсутствующие в самом ObjectId.

Полный пример с реальными данными, журналом и архивом

Учесть начало каждого из трёх заданий

$row['topology_id'] = (string) $event->getTopologyId();

Общий счётчик сохраняет все три открытия, а подробный буфер остаётся ограниченным; пропуски подсчитываются отдельно.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\TopologyClosedEvent::getTopologyIdНайти закрытую топологию, когда освобождён отдельный клиент.

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным. · mongodb

final public function MongoDB\Driver\Monitoring\TopologyClosedEvent::getTopologyId(): MongoDB\BSON\ObjectId

Как получить объект или вызвать метод

Параметров нет. Метод вызывается на настоящем объекте события, полученном соответствующим методом SDAMSubscriber; new для создания этого события недоступен.

Что получится

ObjectId закрытой топологии. Значение можно сопоставить с Opening, полученным ранее тем же наблюдателем.

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

Официальный контракт перечисляет MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Этот getter не выполняет сетевую команду и не сообщает о её успешности; ошибки исходного запроса обрабатываются отдельно.

Наблюдаемое TopologyClosed требует disableClientPersistence=true и освобождения Manager до завершения запроса PHP (RSHUTDOWN). Дополнительные ссылки, включая Cursor, задерживают освобождение. Снятие подписки раньше теряет это наблюдение.

Версии

С ext-mongodb 1.13.0. С 2.3.0 соответствующее значение также доступно через public readonly property; getter остаётся доступным.

Показать задержку из-за второй переменной

$row['topology_id'] = (string) $event->getTopologyId();

Закрытие с исходным ID появляется после последней переменной; до этого alias действительно читает LAMP.

Полный пример с реальными данными, журналом и архивом

Отделить отсутствие записи от отсутствия события

$ids = $complete->ids('topologyClosed');

Полный наблюдатель сохраняет ID закрытия, а снятый раньше — нет; оба видели одинаковое открытие.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\ServerOpeningEventСообщает о добавлении нового сервера в клиентскую топологию.

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter. · mongodb

final class MongoDB\Driver\Monitoring\ServerOpeningEvent {
    public readonly string $host;
    public readonly int $port;
    public readonly MongoDB\BSON\ObjectId $topologyId;
    final public function getHost(): string;
    final public function getPort(): int;
    final public function getTopologyId(): MongoDB\BSON\ObjectId;
}

Как получить объект или вызвать метод

Это класс значения события, а не вызываемая прикладная операция с параметрами. У него нет доступного пользовательского конструктора. Объект создаёт расширение и передаёт соответствующему callback SDAMSubscriber. Собственный observer реализует интерфейс; от final-класса события не наследуются.

Что получится

Сообщает о добавлении нового сервера в клиентскую топологию. Свойства public readonly доступны на чтение; полный перечень и методы приведены ниже. Callback получает объект, а не массив или JSON.

Все свойства класса

ИмяТипНазначение
hoststringHostname сервера
portintПорт сервера
topologyIdMongoDB\BSON\ObjectIdID клиентской топологии, не ID товара

Все собственные методы

getHost(): string, getPort(): int, getTopologyId(): MongoDB\BSON\ObjectId. Параметров у этих getter нет; подробные карточки находятся в этом же справочнике.

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

Попытка new этого класса отвергается Error из-за недоступного конструктора; переприсвоение readonly-свойства также даёт Error. Методы чтения имеют свои контракты InvalidArgumentException при неверных аргументах. Событие не подменяет catch исходной операции.

Не означает успешную аутентификацию, завершённый handshake или готовность запроса. Для доступности нужны фактический результат операции и соответствующая heartbeat-диагностика. В журнал копируются только разрешённые значения; объект события не нужен для дальнейших запросов.

Версии

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter.

Показать начало обслуживания каталога

$endpoint = [$event->host, $event->port];
$id = (string) $event->topologyId;

Настоящий callback сверяет эти readonly-свойства с getter, затем остаются только примитивные выводы; после запроса получены CUP/LAMP.

Полный пример с реальными данными, журналом и архивом

Объяснить Opening при недоступном адресе

$row = ['host'=>$event->getHost(), 'port'=>$event->getPort()];

Серверное представление добавляется даже для своего порта без LISTEN; реальный ping бросает ConnectionException, что не противоречит событию.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\ServerClosedEventСообщает об удалении существующего сервера из клиентской топологии.

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter. · mongodb

final class MongoDB\Driver\Monitoring\ServerClosedEvent {
    public readonly string $host;
    public readonly int $port;
    public readonly MongoDB\BSON\ObjectId $topologyId;
    final public function getHost(): string;
    final public function getPort(): int;
    final public function getTopologyId(): MongoDB\BSON\ObjectId;
}

Как получить объект или вызвать метод

Это класс значения события, а не вызываемая прикладная операция с параметрами. У него нет доступного пользовательского конструктора. Объект создаёт расширение и передаёт соответствующему callback SDAMSubscriber. Собственный observer реализует интерфейс; от final-класса события не наследуются.

Что получится

Сообщает об удалении существующего сервера из клиентской топологии. Свойства public readonly доступны на чтение; полный перечень и методы приведены ниже. Callback получает объект, а не массив или JSON.

Все свойства класса

ИмяТипНазначение
hoststringHostname сервера
portintПорт сервера
topologyIdMongoDB\BSON\ObjectIdID клиентской топологии, не ID товара

Все собственные методы

getHost(): string, getPort(): int, getTopologyId(): MongoDB\BSON\ObjectId. Параметров у этих getter нет; подробные карточки находятся в этом же справочнике.

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

Попытка new этого класса отвергается Error из-за недоступного конструктора; переприсвоение readonly-свойства также даёт Error. Методы чтения имеют свои контракты InvalidArgumentException при неверных аргументах. Событие не подменяет catch исходной операции.

Это не сигнал остановки mongod и не функция принудительного разрыва всех соединений. На нашем опыте удаление происходит при освобождении отдельного клиента. В другой топологии тот же адрес может продолжать использоваться.

Версии

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter.

Завершить только клиент каталога

$id = (string) $event->getTopologyId();
$endpoint = [$event->getHost(), $event->getPort()];

Два клиента имеют один endpoint; серверное закрытие связывается с нужным ID, отчёт живёт до своего unset.

Полный пример с реальными данными, журналом и архивом

Проверить контракт реального уведомления

$same = $event->port === $event->getPort();

После чтения CUP событие приходит от драйвера. В callback проверены все три свойства, final, readonly и недоступность new; сохранены только результаты.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\TopologyOpeningEventСообщает об открытии клиентской топологии и несёт её идентификатор.

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter. · mongodb

final class MongoDB\Driver\Monitoring\TopologyOpeningEvent {
    public readonly MongoDB\BSON\ObjectId $topologyId;
    final public function getTopologyId(): MongoDB\BSON\ObjectId;
}

Как получить объект или вызвать метод

Это класс значения события, а не вызываемая прикладная операция с параметрами. У него нет доступного пользовательского конструктора. Объект создаёт расширение и передаёт соответствующему callback SDAMSubscriber. Собственный observer реализует интерфейс; от final-класса события не наследуются.

Что получится

Сообщает об открытии клиентской топологии и несёт её идентификатор. Свойства public readonly доступны на чтение; полный перечень и методы приведены ниже. Callback получает объект, а не массив или JSON.

Все свойства класса

ИмяТипНазначение
topologyIdMongoDB\BSON\ObjectIdID клиентской топологии, не ID товара

Все собственные методы

getTopologyId(): MongoDB\BSON\ObjectId. Параметров у этих getter нет; подробные карточки находятся в этом же справочнике.

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

Попытка new этого класса отвергается Error из-за недоступного конструктора; переприсвоение readonly-свойства также даёт Error. Методы чтения имеют свои контракты InvalidArgumentException при неверных аргументах. Событие не подменяет catch исходной операции.

При использовании уже сохранённого libmongoc-клиента событие может не появиться. Observer должен быть зарегистрирован до интересующего открытия. Создание PHP Manager само не выполняет I/O: в нашем стенде событие связано с первым обращением.

Версии

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter.

Разнести независимые задачи одного адреса

$id = (string) $event->getTopologyId();

Два реальных открытия получают разные ID; программа добавляет понятные метки catalog/report и сопоставляет окончания.

Полный пример с реальными данными, журналом и архивом

Прочитать новое readonly-свойство

$same = (string) $event->topologyId === (string) $event->getTopologyId();

Для настоящего события проверяются равенство значений, запрет изменения и запрет прямого создания; вызова callback вручную нет.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

MongoDB\Driver\Monitoring\TopologyClosedEventСообщает о закрытии клиентской топологии и позволяет сопоставить её с открытием.

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter. · mongodb

final class MongoDB\Driver\Monitoring\TopologyClosedEvent {
    public readonly MongoDB\BSON\ObjectId $topologyId;
    final public function getTopologyId(): MongoDB\BSON\ObjectId;
}

Как получить объект или вызвать метод

Это класс значения события, а не вызываемая прикладная операция с параметрами. У него нет доступного пользовательского конструктора. Объект создаёт расширение и передаёт соответствующему callback SDAMSubscriber. Собственный observer реализует интерфейс; от final-класса события не наследуются.

Что получится

Сообщает о закрытии клиентской топологии и позволяет сопоставить её с открытием. Свойства public readonly доступны на чтение; полный перечень и методы приведены ниже. Callback получает объект, а не массив или JSON.

Все свойства класса

ИмяТипНазначение
topologyIdMongoDB\BSON\ObjectIdID клиентской топологии, не ID товара

Все собственные методы

getTopologyId(): MongoDB\BSON\ObjectId. Параметров у этих getter нет; подробные карточки находятся в этом же справочнике.

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

Попытка new этого класса отвергается Error из-за недоступного конструктора; переприсвоение readonly-свойства также даёт Error. Методы чтения имеют свои контракты InvalidArgumentException при неверных аргументах. Событие не подменяет catch исходной операции.

По официальному ограничению это событие наблюдается только при disableClientPersistence=true и освобождении Manager до RSHUTDOWN. Сохранённая переменная, Cursor или exception со стеком могут задерживать освобождение. Подписчик должен ещё быть зарегистрирован.

Версии

Класс и его getter доступны с ext-mongodb 1.13.0. Public readonly properties добавлены в 2.3.0. В примерах используется 2.5.5; на более старой поддерживаемой версии значения читаются через getter.

Объяснить отложенное закрытие выгрузки

$id = (string) $event->getTopologyId();

После полного toArray переменная Cursor удерживает Manager; Closed приходит лишь после её unset, а массив двух товаров остаётся.

Полный пример с реальными данными, журналом и архивом

Проверить полноту окна наблюдения

$closed = $complete->ids('topologyClosed');

Второй observer наблюдает реальное закрытие, пропущенное снятым первым; отсутствие строки в первом журнале не объявляется утечкой.

Полный пример с реальными данными, журналом и архивом

Связи: регистрация SDAMSubscriber и снимки топологии, успешность hello и отказы heartbeat, время жизни и чтение Cursor.

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

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

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

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

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