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

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

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

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

Глава 174 · После знакомства с переменной и вызовом PHP

MongoDB из PHP: от установки расширения до проверенного подключения

Путь от набора PHP и правильного ini до готовой карточки заказа. Подбор DLL, Composer, слои драйвера, ленивое соединение, повторное использование клиента и отличие сетевой ошибки от пустого результата.

С чего начать настройку PHP для каталога

Вы переносите PHP-каталог на новый компьютер. Как узнать, что вы установили всё нужное и ваша страница сможет получить заказ A-41? Начнём с самого PHP. Затем получим ответ сервера и прочитаем у заказа поле status со значением «принят».

Какие части нужны каталогу
  1. PHP читает ini и загружает расширение mongodb
  2. Приложение подключает библиотеку Composer, если использует Client
  3. Драйвер выбирает доступный сервер
  4. На экране «A-41 — принят» после чтения одного документа
var_dump(extension_loaded('mongodb')); // bool(true), если модуль загружен

extension_loaded проверяет модуль в том PHP, который выполняет строчку. Текст внутри кавычек — имя расширения. Круглые скобки передают имя функции, var_dump выводит тип и значение. Если результат false, сначала посмотрите инструкцию установки для своей ОС.

Для уже настроенного PHP перейдите к первому сетевому ответу и чтению через Client. Для нового окружения порядок такой: сверить версии, поставить модуль, определить загруженные настройки.

Официальное описание PHP Manual

Что даёт расширение MongoDB и куда идти за своей задачей

Вы хотите показать сотруднику готовые заказы. До выбора классов полезно решить, на каком уровне приложение будет описывать эту задачу. Расширение ext-mongodb даёт основные операции драйвера; библиотека mongodb/mongodb, которую устанавливают через Composer, предоставляет удобные прикладные методы поверх расширения. Это две части одного рабочего пути к данным.

Одна страница заказов проходит весь стек
  1. PHP задаёт фильтр готовых заказов, нужные поля, сортировку и limit=2
  2. PHPLIB Collection::find описывает прикладную операцию либо код использует Manager и Query напрямую
  3. ext-mongodb использует libmongoc для работы драйвера и libbson для BSON
  4. MongoDB выбирает документы; драйвер возвращает курсор
  5. Общий обработчик строит страницу: A-103, A-104

Низкоуровневое расширение отвечает за команды, запросы, запись, управление соединениями и преобразование BSON. Пользовательская PHP-библиотека добавляет более высокий API: отдельные методы операций, средства работы с коллекциями и GridFS. Официальное руководство рекомендует прикладным разработчикам рассмотреть совместное использование расширения и PHP Library. Такое разделение оставляет в расширении основные задачи, для которых существенна реализация на его уровне; из него нельзя вывести, что обход библиотеки обязательно ускорит ваш экран. Назначение двух уровней в PHP Manual.

Для одного и того же списка уже есть сравнение двух настоящих чтений: одинаковые фильтр, поля и порядок дали одинаковые A-103 и A-104. Пример проверяет поведение, а не скорость. Если Manager найден, а Client — нет, обратитесь к проверке расширения и автозагрузки Composer. Снова устанавливать окружение для знакомства с этой картой не нужно.

Полный маршрут по разделам MongoDB

В официальной книге девять верхних направлений. Выберите вопрос, который возник сейчас; вложенные главы и методы остаются доступны в полном дереве справочника. Таблица объясняет назначение направления и ведёт к уже написанному материалу.

НаправлениеКакой вопрос решает
Установка и настройкаКакой PHP выполняет программу, как загрузить подходящий модуль и какие ini-настройки он читает.
Предопределённые константыКак узнать версию и статус выпуска именно расширения в этом PHP-процессе.
Практические руководстваPHPLIB: получить данные страницы; APM: наблюдать выполнение операций.
Архитектура и внутреннее устройствоРоли компонентов, жизнь соединений и правила преобразования PHP-значений в BSON.
БезопасностьГде проверять пользовательские значения и как отделять данные запроса от операторов и исполняемого кода.
Классы и интерфейс DriverКто описывает действие, кто исполняет его и какой объект возвращается. Все 21 позиции перечислены ниже.
Типы, классы и функции BSONКак сохранить форму документа, числа, даты, идентификаторы и другие типы, а также распознать исторические API.
Наблюдение за драйверомКакие события относятся к командам, серверам и топологии и что наблюдатель действительно может установить.
ИсключенияГде перехватить отказ, как выбрать подходящий класс и почему пустая выборка не является исключением.

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

Подобрать совместимые PHP, расширение и библиотеку

Проверьте среду того процесса, в котором заработает программа. Терминал, планировщик и веб-сервер могут выбирать разные интерпретаторы. Манифест версии 2.5.5 требует PHP от 8.1 до 9.0 (не включая 9.0), ext-date и ext-json. Для PHPLIB 2.5.0 нужны PHP 8.x начиная с 8.1, ext-mongodb ветки 2.x начиная с 2.5 и Composer runtime API 2. У учебных программ здесь свой, более узкий стенд.

php -v
php --ini
php --ri mongodb
Что сопоставитьКак узнатьПочему важно
Версию и архитектуру PHPPHP_VERSION и PHP_INT_SIZE × 8Для Windows DLL должны совпасть версия PHP и разрядность
Режим потоковPHP_ZTS: true для TS, false для NTSМодуль для другого режима не загрузится
Версию работающего расширенияphpversion('mongodb')false означает, что модуль в этом процессе не загружен
Библиотеку приложенияcomposer show mongodb/mongodb; composer check-platform-reqsComposer проверяет совместимость установленных пакетов, а php.ini определяет расширения

Расширение использует библиотеки C: libbson для представления и преобразования BSON, libmongoc для работы с MongoDB. При обычной сборке используются поставляемые версии. Можно собрать с системными библиотеками. Поддержка TLS требует соответствующей криптографической библиотеки. Cyrus SASL нужен при выборе Kerberos. Поддержку TLS и пакеты разработки проверяют при сборке, например OpenSSL и pkg-config.

На 32-разрядном PHP серверные целые int64 могут не поместиться в PHP int. Драйвер возвращает BSON Int64. Для этих примеров выбран 64-разрядный PHP. В Windows CLI проекта проверены PHP 8.5.11 NTS x64, ext-mongodb 2.5.5, PHPLIB 2.5.0 и отдельный MongoDB 8.0.34. Требования расширения 2.5.5 · Требования библиотеки 2.5.0.

Официальное описание PHP Manual

Установить расширение для своего PHP и подключить автозагрузку

Сервер mongod хранит данные и принимает подключения. Пакет расширения добавляет драйвер в PHP. Пакет Composer устанавливает файлы библиотеки вашего проекта. Начните с нужного вам слоя, затем проверьте его в конкретном интерпретаторе.

PIE и существующие установки через PECL

Официальная инструкция рекомендует PHP Installer for Extensions (PIE). Укажите подходящий PHP, проверьте путь к утилите и выполните команду установки расширения. На машинах, которые ещё обслуживаются через PECL, существует отдельная команда. Выберите один способ для данного PHP.

pie install mongodb/mongodb-extension
# Для окружения, обслуживаемого PECL:
pecl install mongodb

В Unix после сборки проверьте extension_dir и строку extension=mongodb.so в ini данного SAPI. Для PECL версии драйвера начиная с 1.17 установщик спрашивает параметры сборки. Значения по умолчанию используют поставляемые библиотеки. Если нужно управлять ими в автоматизированной сборке, сверяйте --configureoptions с package.xml этой версии. После установки проверьте php --ri mongodb.

Windows

С официальной страницы релизов mongo-php-driver скачайте ZIP для вашей версии PHP, TS/NTS и x64/x86. Распакуйте php_mongodb.dll в каталог extension_dir активного PHP. Добавьте в его ini приведённую строку. Зависимость libsasl.dll должна быть доступна через PATH рабочего процесса. Ошибка «Unable to load dynamic library» требует сверки версии DLL, её зависимостей и ini именно этого процесса.

extension=php_mongodb.dll

Готовые Windows-сборки расширения. После изменения закройте и заново запустите CLI-команду. Для сайта примените изменения к нужному worker или пулу и сравните узкий набор диагностических полей в закрытом служебном интерфейсе.

macOS с Homebrew

Поставьте формулу php для выбранной версии и пользуйтесь утилитами этой установки. Старый репозиторий homebrew/php больше не содержит отдельных расширений. В руководстве также описана готовая формула стороннего tap для конкретной версии PHP, например mongodb@8.4. Фактическая версия расширения в tap зависит от текущего наличия. Для собственной сборки подготовьте openssl и pkgconf.

Сборка из исходников

Для воспроизводимого выпуска выбирайте тег расширения. Пример ниже задаёт 2.5.5. В Unix используйте инструменты разработки именно целевого PHP, библиотеки для TLS и pkg-config. Вызов configure --help показывает поддерживаемые флаги. Если поставлены совместимые системные libbson, libmongoc и libmongocrypt, можно выбрать --with-mongodb-system-libs=yes.

git clone --branch 2.5.5 --recurse-submodules https://github.com/mongodb/mongo-php-driver.git
cd mongo-php-driver
phpize
./configure
make
make install

После make install сверьте выведенный каталог установки с extension_dir, затем подключите mongodb.so. Когда в системе несколько OpenSSL или PHP, проверьте пути phpize, php-config и PKG_CONFIG_PATH. Проверка платформы в этом выпуске выполнена на Windows CLI. Ветки для macOS и Unix — инструкции для соответствующих сред.

Composer и учебный сервер

В папке приложения выполните команду один раз для записи согласованных зависимостей. При переносе сохраните composer.lock и выполните composer install. Из PHP подключите vendor/autoload.php. В учебных ZIP путь к нему задан переменной PHPMAN_MONGODB_AUTOLOAD. Для занятий нужен отдельный пустой каталог данных и сервер, привязанный к loopback; команды запуска даны в README каждого примера.

composer require mongodb/mongodb:2.5.0
composer check-platform-reqs

Расширение можно использовать непосредственно через Manager без классов PHPLIB. Проверить доступность классов до и после автозагрузки.

Официальное описание PHP Manual

Найти загруженный ini и понять, куда пишется диагностическая трасса

php.ini задаёт параметры текущего PHP. Команда php --ini показывает основной файл и дополнительные ini, прочитанные CLI. php_ini_loaded_file() вернёт путь основного файла или false. php_ini_scanned_files() перечислит дополнительные. Проверяйте эти значения из того же SAPI, где возникла ошибка.

echo PHP_SAPI, PHP_EOL;
var_dump(php_ini_loaded_file(), php_ini_scanned_files());
var_dump(extension_loaded('mongodb'), ini_get('mongodb.debug'));

На стенде вызов этого же PHP с ключом -n действительно показал false для активного ini, расширения и класса Manager. Обычный процесс с подготовленным ini сообщил cli и загруженный mongodb. Для проверки web-SAPI выводите лишь нужные поля в закрытом диагностическом обработчике. Имена файлов и места настройки у веб-сервера могут отличаться.

Значение mongodb.debugЭффект
пустое, 0, off, no, falseТрассировка расширения выключена (по умолчанию пусто)
stderr / stdoutНаправляет подробности в соответствующий поток процесса
1, on, yes, trueНовый временный файл в системном каталоге временных файлов
Путь к каталогуВременный файл в выбранном каталоге. Если он недоступен, используется системный временный каталог

Директива имеет область INI_ALL. На стенде 2.5.5 чтение ini_get вернуло пустую строку. Не публикуйте неочищенную трассу: в неё могут попасть документы и параметры доступа. Для конкретного запроса хватит наблюдения за командой find.

Официальное описание PHP Manual

Проследить путь документа от PHP до ответа MongoDB

Архитектуру подключения удобнее разбирать по трём вопросам. Кто выполняет действие? Как долго живёт соединение? Как PHP-значение становится сохраняемым документом? Например, у карточки есть строковый артикул «007» и вложенная ширина 12 мм. Запрос по числу 7 не найдёт её.

Одна карточка через весь стек
  1. Массив PHP с _id «007» и spec.width_mm 12
  2. Драйвер кодирует типы в BSON
  3. mongod проверяет фильтр и выбирает нужные поля
  4. Курсор возвращает доступный товар с артикулом «007»

Роли компонентов · Жизненный цикл соединения · Преобразование объектов и BSON при сохранении · Практический разбор карточки.

Официальное описание PHP Manual

Различить библиотеку PHP, расширение и сервер

УчастникКонкретная работа при чтении A-41
Прикладной PHPБерёт номер заказа, задаёт допустимый фильтр и поля для экрана
PHPLIB, пакет mongodb/mongodbClient и Collection предоставляют findOne и другие привычные операции
ext-mongodbManager, Query и Cursor связывают операции PHP с драйвером и BSON-типами
libmongoc и libbsonРабота с топологией, транспортом, BSON и ответами
mongodИщет документ и возвращает заказанные поля

PHPLIB следует контрактам высокоуровневых драйверов MongoDB и предоставляет готовые прикладные операции. Расширение обеспечивает низкоуровневое выполнение запросов, команд, записи, кодирование и декодирование BSON, сериализацию PHP-объектов и курсоры. При необходимости собственная библиотека или ODM может использовать расширение напрямую. Две части развиваются и выпускаются отдельно.

Пример с чтением A-41 проверяет ReflectionClass: Manager — внутренний класс расширения, Client — класс библиотеки Composer. Следующий счётчик готовых заказов использует драйвер напрямую. Оба маршрута возвращают значения, вычисленные одной базой.

Официальное описание PHP Manual

Driver: кто создаёт запрос, кто выполняет и кто возвращает результат

После вызова в переменной может оказаться описание запроса, настоящий курсор или квитанция записи. Чтобы не перепутать их, начните с действия: «описать», «исполнить», «прочитать ответ» или «задать политику». Ниже сохранены все 21 позиции официального списка Driver, в том числе CursorInterface и исторический CursorId. Это не список 21 актуального класса.

Ответственность объектов на одном заказе
  1. Query описывает выбор A-103; Manager исполняет запрос
  2. Cursor выдаёт поля A-103; CursorInterface задаёт общий договор чтения
  3. BulkWrite подготавливает изменение статуса; Manager отправляет его
  4. WriteResult сообщает счётчики, WriteError указывает отказавшую операцию
  5. ReadPreference, ReadConcern и WriteConcern задают разные политики; Server и описания помогают понять окружение
Элемент API и его подробная карточкаОткуда берётся и какую работу выполняет
ManagerСоздаёт приложение: хранит настройки соединения и исполняет Query, Command и очереди записи. Само создание Manager ещё не доказывает доступность сервера.
CommandСоздаёт приложение: документ серверной команды, например ping или aggregate. Manager исполняет её для базы; обычный результат чтения приходит через курсор.
QueryСоздаёт приложение: фильтр и опции чтения одной коллекции. Описание запроса отделено от его исполнения и возвращённых документов.
BulkWriteСоздаёт приложение: очередь insert, update и delete для одной коллекции. Исполняющий метод возвращает WriteResult; локальный count ещё не является итогом записи.
BulkWriteCommandСоздаёт приложение: пакет изменений, в котором операции могут адресовать разные коллекции. Требует соответствующего современного сервера; его ответ — BulkWriteCommandResult.
SessionПолучают от Manager::startSession: связывает операции одного логического сеанса, причинную последовательность и транзакции. Сеанс MongoDB не равен HTTP-сессии посетителя.
ClientEncryptionПолучают от Manager::createClientEncryption: создаёт и использует ключи, шифрует и расшифровывает отдельные значения. Не заменяет права доступа, TLS и защиту самих ключей.
ServerApiСоздаёт приложение: настраивает версию и строгость Stable API при обращении к серверу. Это договор серверного API, а не версия расширения из MONGODB_VERSION.
WriteConcernСоздаёт приложение: задаёт требуемое подтверждение записи. Само наличие объекта ещё не сообщает, что конкретная запись подтверждена.
ReadPreferenceСоздаёт приложение: задаёт предпочтение серверов, с которых разрешено читать. Выбор сервера и видимость версии данных — разные вопросы.
ReadConcernСоздаёт приложение: задаёт требование к видимости и согласованности читаемых данных. Его сочетают с подходящей топологией и сценарием, а не используют как сортировку результата.
CursorВозвращает драйвер после чтения: последовательно выдаёт документы и при необходимости получает следующие партии. Это не готовый PHP-массив всех строк.
CursorIdИсторически возвращал Cursor::getId: объект номера серверного курсора. Класс удалён в ext-mongodb 2.0; текущий результат getId — MongoDB\BSON\Int64.
CursorInterfaceИнтерфейс общего договора чтения: прикладная функция может принять подходящий курсорный результат без привязки к конкретному способу его получения. Сам интерфейс не создают через new.
ServerВозвращает драйвер: представляет выбранный сервер, его сведения и операции исполнения. Отдельный Server не обозначает всю топологию соединения.
ServerDescriptionПолучают от драйвера или события наблюдения: снимок описания одного сервера — тип, адрес и сведения обнаружения. Это описание состояния, а не команда его изменить.
TopologyDescriptionПолучают от драйвера или события наблюдения: снимок известной топологии и описаний её серверов. Позволяет рассмотреть окружение выбранного узла.
WriteConcernErrorПолучают из результата/диагностики записи: ошибка требуемого подтверждения. Она не тождественна отказу отдельной строки и сама по себе не доказывает откат записи.
WriteErrorПолучают в сведениях об отдельных отказах: код, сообщение, индекс операции и доступные дополнительные данные. Индекс нужно связать с собственным источником.
WriteResultПолучают после классического BulkWrite либо из его исключения: суммарные счётчики, ошибки и сервер. Документы текущей коллекции читают отдельным запросом.
BulkWriteCommandResultПолучают после BulkWriteCommand: сводные показатели и, при verboseResults, результаты успешных отдельных операций. Не создаёт атомарную транзакцию между коллекциями.

Название Result не означает массив документов, а название Error не означает, что этот объект сам нужно перехватывать в catch. Для обычного чтения пройдите Query → Manager → Cursor. Для импорта одной коллекции — BulkWrite → Manager → WriteResult. В задаче по нескольким коллекциям используется другая пара BulkWriteCommand и BulkWriteCommandResult; её доступность проверяют по версиям расширения и сервера.

Потребность в специальном классе возникает из требования: Session — согласовать операции, ClientEncryption — защитить значения, ServerApi — зафиксировать договор API. Объект политики не выполняет саму операцию. Снимок ServerDescription не остаётся постоянно обновляющейся панелью состояния. Для конкретных параметров и ограничений используйте точную карточку из соответствующей строки. Официальный список Driver.

Проверьте себя. Экран должен показать два заказа, а после изменения статуса — подтвердить одну запись. Какие объекты относятся к первой операции, а какие ко второй?

Разобрать выбор

Для чтения подготовьте Query, исполните его через Manager и обработайте Cursor. Для изменения создайте BulkWrite, отправьте его через Manager и разберите WriteResult; при отказе операции понадобятся сведения WriteError. Если экрану нужна текущая карточка после записи, выполните отдельное чтение. Квитанция записи не превращается в документ заказа.

Как Manager выбирает и повторно использует соединение

Создание Manager проверяет и сохраняет его параметры. При первом сетевом вызове нижележащий клиент выбирает сервер и открывает соединение. Уже открытый клиент может использоваться повторно. Поэтому для здоровья сервиса имеет смысл проверять конкретный ping и необходимое прикладное чтение.

Начиная с ext-mongodb 1.2 драйвер поддерживает клиент libmongoc в пределах одного PHP worker. Ключ строится из аргументов конструктора, включая строку URI и массивы настроек. При совпадении ключа доступны уже открытые соединения, состояние аутентификации и известная топология. Различная запись адреса или параметра способна дать отдельный ключ, даже если конечный сервер тот же. disableClientPersistence=true отключает повторное использование этого клиента.

Два компонента внутри процесса
  1. Компонент A создаёт Manager и выполняет hello
  2. Драйвер сохраняет состояние клиента в этом worker
  3. Компонент B создаёт Manager с теми же аргументами
  4. На нашем standalone два hello возвращают совпадающий connectionId

В полноценном пуле у каждого worker собственное состояние. Между запросами могут потребоваться дополнительные hello, восстановление связи или выбор другого узла. В Unix после fork без exec рекомендации драйвера предписывают не переиспользовать наследованный объект Manager в дочернем процессе. Планируйте жизненный цикл клиента в самом дочернем процессе и отдельно учитывайте особенности платформы.

До версии расширения 1.2 использовались PHP Streams. На каждый Manager создавался новый клиент libmongoc, повторно могли использоваться отдельные сокеты по хешу хоста, порта и URI. Массивы настроек от конструктора в старый ключ не входили. Сведения об аутентификации и топологии не переносились. В версиях от 1.1.8 до 1.2.0 (исключая 1.2.0) SSL-сокеты не сохранялись. Современный пример ниже сравнивает поведение 2.5.5 в одном работающем CLI.

Посмотреть результаты сравнения реальных соединений · Как Manager обнаруживает сервер.

Официальное описание PHP Manual

Определить, какой PHP и какой ini выполняют задачу

Крон запускает импорт заказов из терминала. Перед установкой DLL узнайте, какой именно PHP выполняет этот импорт. В терминале короткий фрагмент напечатает полный путь к интерпретатору, затем активный конфигурационный файл и статус расширения.

Путь проверки
  1. Текущий процесс сообщает SAPI и путь к ini
  2. ReflectionExtension получает версию загруженного модуля
  3. Сохраняем краткий профиль и читаем его из учебной базы

Короткий вызов

echo PHP_BINARY, PHP_EOL;
var_dump(php_ini_loaded_file(), extension_loaded('mongodb'));

Результат запуска

Что проверилиРезультат
Способ запускаcli
Используется php.initrue
Имя файла iniphp.ini
Разрядность PHP64
Расширение mongodb загруженоtrue

Как это работает

PHP_BINARY и php_ini_loaded_file() относятся к выполняющемуся процессу. Отсутствие загруженного основного ini возвращает false. Для отчёта публикуем только имя файла. Сохранённое поле bits взято из PHP_INT_SIZE в этом процессе.

Условия и ограничения

В учебном запуске файл называется php.ini. У вас имя и расположение могут отличаться. При первой диагностике используйте короткий фрагмент или php --ini: им для вывода не нужно работающее соединение с MongoDB.

Полная самостоятельная программа и архив
Определить, какой PHP и какой ini выполняют задачуСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    $ini = php_ini_loaded_file();
    $extension = new ReflectionExtension('mongodb');
    $bits = PHP_INT_SIZE * 8;
    $loaded = is_string($ini) && is_file($ini);
    $profile = ['_id'=>'cli-profile', 'sapi'=>PHP_SAPI, 'bits'=>$bits, 'loaded_ini'=>$loaded];
    su131Insert($manager,$ns,[$profile]);
    $saved = su131Read($manager,$ns,['_id'=>'cli-profile'],['_id'=>0,'sapi'=>1,'bits'=>1,'loaded_ini'=>1])[0];
    $checks = [
        'cli' => $saved['sapi'] === 'cli', 'ini_found' => $saved['loaded_ini'] === true,
        'extension_found' => extension_loaded('mongodb'), 'driver_version_known' => $extension->getVersion() !== false,
        'php_binary_exists' => is_file(PHP_BINARY), 'x64' => $saved['bits'] === 64,
    ];
    $rows = [['Способ запуска', $saved['sapi']], ['Используется php.ini', $saved['loaded_ini']],
        ['Имя файла ini',basename($ini)], ['Разрядность PHP', $saved['bits']], ['Расширение mongodb загружено', extension_loaded('mongodb')]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-cli-profile', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-cli-profile: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-cli-profile: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Сравните php --ini с выводом php -n --ini. Ключ -n временно отключает чтение ini только для этого вызова. Сопоставьте extension_loaded в двух запусках.

Автор смешал имена разных расширений и варианты Windows-сборки. Пример отделяет свойства PHP-процесса от работающей службы MongoDB. Какой DLL нужен для установленного PHP на Stack Overflow.

Узнать версию расширения, даже когда подключение к базе ещё не настроено

Крон сообщает «модуль MongoDB не найден», хотя сайт работает. Сначала получите небольшой отчёт именно от PHP, который запускает крон. Ему не нужен пароль, URI или доступный сервер: расширение может сообщить собственную версию локально. Если модуль отсутствует, полезным результатом будет понятная подсказка вместо ошибки обращения к неизвестной константе.

Проверка до чтения версии
  1. extension_loaded("mongodb") выясняет, загружен ли модуль текущим PHP
  2. defined проверяет наличие имён MONGODB_VERSION и MONGODB_STABILITY
  3. Только после обеих проверок программа читает соответствующее значение
  4. Отчёт показывает версию/статус выпуска либо подсказку о загрузке модуля
  5. Состояние сервера остаётся неизвестным: к нему не обращались
КонстантаТип и значениеЧего она не сообщает
MONGODB_VERSIONstring: версия расширения в форме x.y.z, например 2.5.5 на учебном стенде.Версию PHP, пакета Composer mongodb/mongodb или сервера MongoDB.
MONGODB_STABILITYstring: статус выпуска расширения, например alpha, beta или stable.Доступен ли сервер и успешно ли выполнится запрос. stable не является проверкой базы.

Константа — именованное значение, которое читают без знака $ перед именем. Эти два имени предоставляет загруженное расширение: если оно не подключено, имена недоступны. Строка имени в defined() не читает саму константу. После проверки constant() получает значение по этому строковому имени. Сначала объясним ровно один вывод версии; полный файл ниже добавляет оба поля и подсказку.

if (extension_loaded('mongodb') && defined('MONGODB_VERSION')) {
    echo constant('MONGODB_VERSION'), PHP_EOL;
} else {
    echo 'Версия расширения недоступна', PHP_EOL;
}

if выбирает ветвь по условию. && требует истинности обеих проверок. Фигурные скобки объединяют инструкции ветви, else задаёт ответ при невыполненном условии. echo печатает результат, PHP_EOL добавляет перевод строки терминала. В этом фрагменте нет new Manager, поэтому он не проверяет соединение. Отдельно доступны extension_loaded(), defined() и constant().

Один файл, два отдельных запуска

Обычный запуск использует php.ini с расширением. Второй запуск php -n example.php запускает отдельный PHP-процесс без ini. Файл программы в обоих случаях одинаков. В учебной Windows-среде модуль динамический; у PHP со статически встроенным mongodb флаг -n его не удалит, и для второй проверки нужен другой интерпретатор без этого модуля. Скрипт сам не запускает дочерние процессы.

Получить версию расширения или подсказку о его отсутствииСамостоятельный пример
<?php
declare(strict_types=1);

$loaded = extension_loaded('mongodb');
$hasVersion = defined('MONGODB_VERSION');
$hasStability = defined('MONGODB_STABILITY');

echo 'Расширение mongodb: ', $loaded ? 'загружено' : 'не загружено', PHP_EOL;

if ($loaded && $hasVersion) {
    echo 'Версия расширения: ', constant('MONGODB_VERSION'), PHP_EOL;
} else {
    echo 'Версия расширения: недоступна', PHP_EOL;
}

if ($loaded && $hasStability) {
    echo 'Стабильность выпуска: ', constant('MONGODB_STABILITY'), PHP_EOL;
} else {
    echo 'Стабильность выпуска: недоступна', PHP_EOL;
}

echo 'Сервер MongoDB: не проверялся', PHP_EOL;

if ($loaded && $hasVersion && $hasStability) {
    echo 'Следующий шаг: выберите PHPLIB или Driver для своей задачи.', PHP_EOL;
} else {
    echo 'Следующий шаг: проверьте загрузку модуля mongodb в этом PHP.', PHP_EOL;
}
Ожидаемый результат
Расширение mongodb: загружено
Версия расширения: 2.5.5
Стабильность выпуска: stable
Сервер MongoDB: не проверялся
Следующий шаг: выберите PHPLIB или Driver для своей задачи.

Локальный CLI-отчёт: запросов к MongoDB и сетевых действий нет. Основной ожидаемый вывод относится к ext-mongodb 2.5.5, stable; для процесса без расширения ниже дан отдельный результат.

Ожидаемый вывод второго процесса без расширения

Расширение mongodb: не загружено
Версия расширения: недоступна
Стабильность выпуска: недоступна
Сервер MongoDB: не проверялся
Следующий шаг: проверьте загрузку модуля mongodb в этом PHP.

В обоих нормальных исходах код завершения равен 0: задача скрипта — выдать диагностику, и подсказка об отсутствующем модуле является её результатом. В этом файле нет внешнего состояния, которое необходимо имитировать. Версия 2.5.5 и stable — ожидаемые значения выбранной учебной среды; на другом установленном выпуске фактические строки будут другими.

Чтобы понять различие четырёх версий, подпишите их источник: PHP_VERSION относится к интерпретатору, MONGODB_VERSION — к расширению, версия Composer-пакета — к пользовательской библиотеке, а версия сервера узнаётся отдельным обращением к самому серверу. Сравнение этих компонентов уже разобрано в требованиях к окружению. Не заменяйте версию сервера значением MONGODB_VERSION.

Попробуйте сами. Запустите один файл обычным способом и с -n. Какие строки изменились, а какое утверждение о сервере осталось прежним?

Ожидаемое объяснение

В динамическом учебном PHP обычный процесс увидит загруженный mongodb и его две константы. Без ini появятся «не загружено» и «недоступна», затем подсказка. Строка «Сервер MongoDB: не проверялся» одинакова: ни один запуск не выполняет сетевую проверку. Если оба процесса видят модуль, проверьте способ его включения и выбранный PHP, а не подменяйте вывод ожидаемым текстом.

Следующий шаг после подтверждения модуля — проверка Composer-слоя или первый настоящий ping. Обе предопределённые константы MongoDB.

Разобраться, почему есть Manager, а Client не найден

На машине установлен модуль mongodb, но приложение сообщает «Class MongoDB\Client not found». У этих классов разные поставщики. Дойдём от проверки класса до реальной карточки A-41.

Путь проверки
  1. PHP загружает ext-mongodb вместе с Manager
  2. Composer регистрирует автозагрузчик
  3. Client находит заказ через Collection::findOne
  4. В заказе виден статус «принят»

Короткий вызов

var_dump(class_exists(MongoDB\Driver\Manager::class, false));
require __DIR__.'/vendor/autoload.php';
var_dump(class_exists(MongoDB\Client::class));

Результат запуска

Что проверилиРезультат
Класс Manager при стартеtrue
Класс Client уже загружен при стартеfalse
Класс Client после автозагрузкиtrue
Статус из базыпринят

Как это работает

В свежем CLI до загрузки библиотек class_exists с false проверяет только уже загруженные классы. После require_once проверка с автозагрузкой подхватывает PHP-класс Client. Короткий фрагмент предполагает соседнюю папку vendor. В полной программе путь к её autoload.php задаётся явно.

Условия и ограничения

Если приложение уже подключило Composer в bootstrap, класс может быть загружен раньше. Ошибка «класс не найден» возникает до попытки отправить команду серверу.

Полная самостоятельная программа и архив
Разобраться, почему есть Manager, а Client не найденСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    $driverAvailable = class_exists(Manager::class, false);
    $clientAlreadyLoaded = class_exists(MongoDB\Client::class, false);
    $autoload = getenv('PHPMAN_MONGODB_AUTOLOAD');
    if (!$autoload || !is_file($autoload)) {
        throw new RuntimeException('Set PHPMAN_MONGODB_AUTOLOAD to the installed vendor/autoload.php');
    }
    require_once $autoload;
    $clientAvailable = class_exists(MongoDB\Client::class);
    $client = new MongoDB\Client($uri, $options);
    su131Insert($manager,$ns,[['_id'=>'A-41','status'=>'принят']]);
    $orders = $client->selectCollection($db,$name);
    $document = $orders->findOne(['_id'=>'A-41'],['projection'=>['_id'=>0,'status'=>1]]);
    $libraryVersion = Composer\InstalledVersions::getPrettyVersion('mongodb/mongodb');
    $checks = ['ext_manager'=>$driverAvailable, 'without_autoload'=>!$clientAlreadyLoaded, 'client_available'=>$clientAvailable,
        'package_installed'=>is_string($libraryVersion), 'internal_manager'=>(new ReflectionClass(Manager::class))->isInternal(),
        'userland_client'=>!(new ReflectionClass(MongoDB\Client::class))->isInternal(), 'real_read'=>$document->status === 'принят'];
    $rows = [['Класс Manager при старте',$driverAvailable],['Класс Client уже загружен при старте',$clientAlreadyLoaded],
        ['Класс Client после автозагрузки',$clientAvailable],['Статус из базы',$document->status]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-composer-layers', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-composer-layers: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-composer-layers: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Измените проекцию так, чтобы вернуть также _id. Напечатайте поля полученной карточки. Установка пакета нужна один раз для проекта, а require выполняется при его запуске.

В веб-приложении возникает ошибка загрузки PHP-класса. Разбираем только границу между расширением и автозагрузкой PHPLIB. Расширение установлено, но MongoDB Client не найден на Stack Overflow.

Выяснить, когда драйвер действительно обращается к серверу

Панель показывает «подключено» сразу после new Manager. Сам конструктор мог только проверить и сохранить аргументы. Здесь панель получит сначала ответ ping, затем реальное значение из коллекции.

Путь проверки
  1. Создать отдельный свежий клиент
  2. Посчитать известные ему серверы
  3. Выполнить ping и прочитать ok
  4. Получить поле state заявки READY-1

Короткий вызов

$manager = new MongoDB\Driver\Manager($uri);
$command = new MongoDB\Driver\Command(['ping' => 1]);
$reply = $manager->executeCommand($db, $command)->toArray();

Результат запуска

Что проверилиРезультат
Известные серверы сразу после new0
Ответ на ping1
Известные серверы после ping1
Состояние заявкипринято

Как это работает

Для этой проверки disableClientPersistence=true даёт свежий нижележащий клиент. До первого сетевого вызова его список серверов пуст. После ping в списке появился используемый standalone. Ping проверяет способность выполнить эту команду. Чтение заявки дополнительно проверяет прикладной запрос.

Условия и ограничения

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

Полная самостоятельная программа и архив
Выяснить, когда драйвер действительно обращается к серверуСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    $probe = new Manager($uri, $options, ['disableClientPersistence'=>true]);
    $before = count($probe->getServers());
    $ping = $probe->executeCommand($db, new Command(['ping'=>1]))->toArray()[0];
    $after = $probe->getServers();
    su131Insert($manager,$ns,[['_id'=>'READY-1','state'=>'принято']]);
    $docs = su131Read($probe,$ns,['_id'=>'READY-1'],['_id'=>0,'state'=>1]);
    $checks = ['fresh_client_empty'=>$before === 0, 'ping_ack'=>(float)$ping->ok === 1.0, 'one_endpoint'=>count($after) === 1,
        'standalone'=>$after[0]->getType() === Server::TYPE_STANDALONE, 'state_read'=>$docs === [['state'=>'принято']],
        'same_port'=>$after[0]->getPort() === $runtime['observed_port']];
    $rows = [['Известные серверы сразу после new',$before],['Ответ на ping',(int)$ping->ok],
        ['Известные серверы после ping',count($after)],['Состояние заявки',$docs[0]['state']]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-lazy-ping', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-lazy-ping: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-lazy-ping: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Посмотрите getServers до и после ping в двух клиентах с одинаковыми параметрами. Сравните результат с опытом о повторном использовании соединений ниже.

Отличить отсутствующий заказ от недоступной базы

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

Путь проверки
  1. A-10 найден на рабочем сервере
  2. По A-11 сервер успешно вернул пустой результат
  3. Отдельный Manager направлен на занятый, но не прослушиваемый локальный порт
  4. Первый сетевой вызов вернул ConnectionTimeoutException

Короткий вызов

try {
    $documents = $manager->executeQuery($ns, $query)->toArray();
    $message = $documents === [] ? 'Заказ не найден' : 'Заказ найден';
} catch (MongoDB\Driver\Exception\ConnectionTimeoutException $error) {
    $message = 'Не удалось связаться с базой';
}

Результат запуска

Что проверилиРезультат
Заказ A-10в работе
Строк для A-110
Клиент по другому адресу созданtrue
Ошибка выбора сервераtrue

Как это работает

Программа удерживает выбранный системой порт без listen до конца опыта и закрывает его в finally. Адрес своей базы не меняется. Пустой массив появляется после успешного обращения. Исключение сообщает, что подходящий сервер не был выбран в заданный срок.

Условия и ограничения

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

Полная самостоятельная программа и архив
Отличить отсутствующий заказ от недоступной базыСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    su131Insert($manager,$ns,[['_id'=>'A-10','state'=>'в работе']]);
    $found = su131Read($manager,$ns,['_id'=>'A-10'],['_id'=>0,'state'=>1]);
    $missing = su131Read($manager,$ns,['_id'=>'A-11'],['_id'=>0,'state'=>1]);
    $guard = stream_socket_server('tcp://127.0.0.1:0',$number,$reason,STREAM_SERVER_BIND);
    if (!$guard) {throw new RuntimeException('Cannot reserve a non-listening loopback port');}
    try {
        $endpoint = stream_socket_get_name($guard,false);
        $unreachable = new Manager('mongodb://'.$endpoint.'/?directConnection=true',
            ['connectTimeoutMS'=>150,'serverSelectionTimeoutMS'=>300], ['disableClientPersistence'=>true]);
        $errorClass = null;
        try {
            $unreachable->executeCommand($db,new Command(['ping'=>1]));
        } catch (ConnectionTimeoutException $error) {
            $errorClass = get_class($error);
        }
    } finally {
        fclose($guard);
    }
    $checks = ['known_record'=>$found === [['state'=>'в работе']], 'unknown_record'=>$missing === [],
        'two_distinct_ids'=>count($found) === 1 && count($missing) === 0,
        'constructed_without_network'=>$unreachable instanceof Manager, 'selection_error'=>$errorClass === ConnectionTimeoutException::class,
        'after_failure_original_works'=>su131Read($manager,$ns,['_id'=>'A-10'],['_id'=>0,'state'=>1]) === $found];
    $rows = [['Заказ A-10',$found[0]['state']],['Строк для A-11',count($missing)],
        ['Клиент по другому адресу создан',$unreachable instanceof Manager],['Ошибка выбора сервера',$errorClass === ConnectionTimeoutException::class]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-empty-or-offline', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-empty-or-offline: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-empty-or-offline: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Добавьте в интерфейс отдельные состояния для найденной записи, пустого результата и временной недоступности. В публичном сообщении не выводите URI с учётными данными.

У автора один клиент получает ответ, в то время как команда из PHP завершается исключением. Различаем создание объекта, вызов сервера и пустую выдачу после успешного запроса. Отказ выбора сервера в PHP на Stack Overflow.

Увидеть повторное использование соединения в одном процессе

В сервисе два компонента отдельно создают Manager с одинаковыми параметрами. Значит ли это, что они открыли две TCP-связи? Посмотрим, какой номер соединения сам сервер вернул для каждого обращения.

Путь проверки
  1. Создать два разных PHP-объекта с одинаковым профилем
  2. В том же процессе получить их connectionId через hello
  3. Создать третий объект с disableClientPersistence
  4. Прочитать одну сохранённую задачу через оба клиента

Короткий вызов

$a = new MongoDB\Driver\Manager($uri, $options);
$b = new MongoDB\Driver\Manager($uri, $options);
// Сравните connectionId из двух ответов hello в полной программе.

Результат запуска

Что проверилиРезультат
Объекты PHP различаютсяtrue
Первое и второе соединения совпалиtrue
Отдельный клиент получил другой connectionIdtrue
Что прочёл отдельный клиентсохранено

Как это работает

На одном стабильном standalone первые два ответа содержат один и тот же номер соединения. При отключённом повторном использовании получен другой номер. Сравнение относится к конкретным командам и их фактически работающим соединениям. Новые соединения к той же базе видят сохранённый документ.

Условия и ограничения

Этот опыт выполняется в одном CLI-процессе. У рабочих процессов PHP-FPM свои клиенты. Сеть, смена узла или переподключение могут изменить connectionId.

Полная самостоятельная программа и архив
Увидеть повторное использование соединения в одном процессеСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    $work = ['appName'=>'phpman131-worker','connectTimeoutMS'=>500,'serverSelectionTimeoutMS'=>1500];
    $first = new Manager($uri,$work);
    $idA = $first->executeCommand($db,new Command(['hello'=>1]))->toArray()[0]->connectionId;
    $second = new Manager($uri,$work);
    $idB = $second->executeCommand($db,new Command(['hello'=>1]))->toArray()[0]->connectionId;
    $separate = new Manager($uri,$work,['disableClientPersistence'=>true]);
    $idC = $separate->executeCommand($db,new Command(['hello'=>1]))->toArray()[0]->connectionId;
    su131Insert($first,$ns,[['_id'=>'job-1','state'=>'сохранено']]);
    $one = su131Read($second,$ns,['_id'=>'job-1'],['_id'=>0,'state'=>1]);
    $two = su131Read($separate,$ns,['_id'=>'job-1'],['_id'=>0,'state'=>1]);
    $checks = ['distinct_php_objects'=>$first !== $second,'positive_ids'=>$idA > 0 && $idB > 0 && $idC > 0,
        'shared_network_connection'=>$idA == $idB, 'isolated_connection'=>$idC != $idA,
        'stored_value'=>$one === [['state'=>'сохранено']], 'both_see_data'=>$one === $two];
    $rows = [['Объекты PHP различаются',$first !== $second],['Первое и второе соединения совпали',$idA == $idB],
        ['Отдельный клиент получил другой connectionId',$idC != $idA],['Что прочёл отдельный клиент',$two[0]['state']]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-worker-reuse', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-worker-reuse: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-worker-reuse: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Создайте третий обычный Manager с другим appName и сравните ответы hello. В журнал достаточно внести результат сравнения, не параметры URI.

Получить число готовых заказов одной командой

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

Путь проверки
  1. В коллекции два готовых заказа и один отменённый
  2. Создать Command с фильтром по готовым
  3. Изменить исходный PHP-массив и создать вторую Command
  4. Сервер возвращает два разных счётчика

Короткий вызов

$command = new MongoDB\Driver\Command([
    'count' => $collectionName, 'query' => ['state' => 'готов'],
]);
$result = $manager->executeReadCommand($db, $command)->toArray()[0];
echo $result->n; // 2

Результат запуска

Что проверилиРезультат
Готовы к выдаче2
Отменены1
Состояние заказа Bготов

Как это работает

Command хранит неизменяемое значение переданного документа. Изменив PHP-массив после конструктора, мы не изменили первую команду. Сервер применил каждый фильтр и вернул ограниченные результаты. В данном примере используется команда count на одном узле. Прикладной код на PHPLIB также может использовать countDocuments().

Условия и ограничения

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

Полная самостоятельная программа и архив
Получить число готовых заказов одной командойСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    su131Insert($manager,$ns,[['_id'=>'A','state'=>'готов'],['_id'=>'B','state'=>'готов'],['_id'=>'C','state'=>'отменён']]);
    $document = ['count'=>$name, 'query'=>['state'=>'готов'], 'maxTimeMS'=>1500];
    $command = new Command($document);
    $document['query'] = ['state'=>'отменён'];
    $first = $manager->executeReadCommand($db,$command)->toArray()[0];
    $second = $manager->executeReadCommand($db,new Command($document))->toArray()[0];
    $known = su131Read($manager,$ns,['_id'=>'B'],['_id'=>0,'state'=>1]);
    $checks = ['ready_count'=>(int)$first->n === 2,'cancel_count'=>(int)$second->n === 1,
        'ready_ok'=>(float)$first->ok === 1.0,'cancel_ok'=>(float)$second->ok === 1.0,
        'frozen_command'=>(int)$first->n !== (int)$second->n,'known_order'=>$known === [['state'=>'готов']]];
    $rows = [['Готовы к выдаче',(int)$first->n],['Отменены',(int)$second->n],['Состояние заказа B',$known[0]['state']]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-command-stock', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-command-stock: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-command-stock: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Добавьте второй отменённый заказ. Убедитесь, что растёт только второй счётчик. Для рабочего каталога сравните этот пример с подсчётом по индексу.

Провести карточку от массива PHP до вложенного поля BSON

На ценнике артикул 007. Если при вводе преобразовать его в число 7, карточка исчезнет из выдачи. Здесь проследим, что происходит с тремя разными типами данных между PHP и сервером.

Путь проверки
  1. PHP передаёт строку «007», число 12 и логическое true
  2. Расширение кодирует их типы в BSON
  3. Сервер применяет фильтр по артикулу и вложенной ширине
  4. Курсор декодирует только выбранные поля

Короткий вызов

$filter = ['_id' => '007', 'spec.width_mm' => ['$gte' => 10]];
$projection = ['_id' => 1, 'spec.width_mm' => 1, 'available' => 1];

Результат запуска

Что проверилиРезультат
Артикул007
Ширина в мм12
В наличииtrue
Найдено по числу 70

Как это работает

Драйвер передаёт данные в бинарном формате BSON. Сервер сравнивает строковый идентификатор со строковым параметром, а числовую ширину — с числовой границей. Маленький курсор преобразован в массив по явной карте типов. В ответе нет служебного поля unit.

Условия и ограничения

Настройка карты типов меняет представление данных на стороне PHP. Она не меняет тип поля, сохранённого в коллекции, и не исправляет запрос с неверным ключом.

Полная самостоятельная программа и архив
Провести карточку от массива PHP до вложенного поля BSONСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    $product = ['_id'=>'007','spec'=>['width_mm'=>12,'unit'=>'мм'],'available'=>true];
    su131Insert($manager,$ns,[$product]);
    $selected = su131Read($manager,$ns,['_id'=>'007','spec.width_mm'=>['$gte'=>10]],['_id'=>1,'spec.width_mm'=>1,'available'=>1]);
    $wrongType = su131Read($manager,$ns,['_id'=>7],['_id'=>1]);
    $item = $selected[0];
    $checks = ['one_matching_card'=>count($selected) === 1, 'sku_is_string'=>is_string($item['_id']),
        'leading_zero_kept'=>$item['_id'] === '007', 'width_is_int'=>$item['spec']['width_mm'] === 12,
        'available_bool'=>$item['available'] === true, 'wrong_key_does_not_match'=>$wrongType === [],
        'projection_only'=>array_keys($item['spec']) === ['width_mm']];
    $rows = [['Артикул',$item['_id']],['Ширина в мм',$item['spec']['width_mm']],['В наличии',$item['available']],
        ['Найдено по числу 7',count($wrongType)]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-bson-document', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-bson-document: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-bson-document: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Добавьте карточку с отдельным строковым артикулом «7». Покажите, какие фильтры находят первую и вторую. Сохраните выбор типов на входе формы.

Отделить доступность MongoDB от готовности каталога

После установки приложение связывается с MongoDB, но на экран ещё нельзя выдавать неподготовленный каталог. Чтобы решить, запускать ли страницу, проверим известный документ состояния отдельно от доступности сервера.

Путь проверки
  1. Получить ok=1 на ping
  2. Прочитать метку release в пустой учебной коллекции
  3. Подготовить состояние revision=3
  4. Выбрать только state и revision для приложения

Короткий вызов

$ready = $card['state'] === 'готов' && $card['revision'] >= 3;

Результат запуска

Что проверилиРезультат
Сервер отвечаетtrue
Служебная карточка до подготовки0
Версия каталога3
Можно показывать каталогtrue

Как это работает

До записи метки сервер отвечал нормально. Только прикладное чтение установило, что нужная версия каталога готова. Код записывает метку для построения учебных данных. В рабочей системе за неё отвечает процесс импорта, а веб-страница только читает.

Условия и ограничения

Право на ping не гарантирует разрешения на все чтения и записи. Для мониторинга задайте ровно те привилегии и краткий запрос, которые нужны проверяемой функции приложения.

Полная самостоятельная программа и архив
Отделить доступность MongoDB от готовности каталогаСамостоятельный пример
<?php
declare(strict_types=1);

use MongoDB\Driver\{Manager, Command, Query, ReadPreference, Server};
use MongoDB\Driver\Exception\ConnectionTimeoutException;

require __DIR__.'/su131-helper.php';
$settings = su131Settings();
$db = $settings['database'];
$uri = $settings['uri'];
$options = ['connectTimeoutMS'=>500, 'serverSelectionTimeoutMS'=>1500, 'socketTimeoutMS'=>2500];
$manager = new Manager($uri, $options);
$server = $manager->selectServer(new ReadPreference(ReadPreference::PRIMARY));
if ($server->getType() !== Server::TYPE_STANDALONE) {
    throw new RuntimeException('Use the separate training standalone server');
}
$runtime = [
    'php'=>PHP_VERSION, 'extension'=>phpversion('mongodb'), 'server'=>su131Command($manager,$db,['buildInfo'=>1])[0]['version'],
    'execution'=>'database', 'observed_host'=>$server->getHost(), 'observed_port'=>$server->getPort(),
];
$owned = [];
try {
    $ns = su131Create($manager,$db,$owned);
    $name = $owned[0];
    $ping = $manager->executeCommand($db,new Command(['ping'=>1]))->toArray()[0];
    $initial = su131Read($manager,$ns,['_id'=>'release'],['_id'=>0,'state'=>1]);
    su131Insert($manager,$ns,[['_id'=>'release','state'=>'готов','revision'=>3,'internal'=>'private']]);
    $metadata = su131Read($manager,$ns,['_id'=>'release'],['_id'=>0,'state'=>1,'revision'=>1]);
    $card = $metadata[0];
    $ready = $card['state'] === 'готов' && $card['revision'] >= 3;
    $checks = ['server_ok'=>(int)$ping->ok === 1,'initial_marker_missing'=>$initial === [],
        'marker_now_exists'=>count($metadata) === 1,'revision_matches'=>$card['revision'] === 3,
        'state_ready'=>$ready,'internal_hidden'=>!array_key_exists('internal',$card)];
    $rows = [['Сервер отвечает',(int)$ping->ok === 1],['Служебная карточка до подготовки',count($initial)],
        ['Версия каталога',$card['revision']],['Можно показывать каталог',$ready]];
} finally {
    foreach ($owned as $ownedName) {
        su131Command($manager,$db,['drop'=>$ownedName],true);
    }
}
$checks['owned_collection_removed'] = count($owned) === 1 && !su131Exists($manager,$db,$name);
su131Report('mongodb131-setup-readiness', ['Что проверили', 'Результат'], $rows, $checks, $runtime);
echo 'mongodb131-setup-readiness: OK', PHP_EOL;
Ожидаемый результат
mongodb131-setup-readiness: OK

Программа создаёт собственную временную коллекцию на отдельном standalone и сохраняет строки отчёта из наблюдений PHP и реального ответа MongoDB.

Таблица фактического результата · Данные результата в JSON

Упражнение. Повысьте требуемую версию до 4. Отдельно покажите результат проверки сети и состояние готовности страницы. Курьер увидит «каталог готовится» при доступном сервере.

Перейти от диагностики к данным страницы

Для страницы приложения пройдите путь последовательно. Узнайте, какой PHP работает. Затем убедитесь, что доступен класс нужного слоя. Дождитесь ответа от сервера и прочитайте ограниченный набор полей реального документа. В журнале зафиксируйте, на каком шаге получен результат или ошибка.

Первый полный каталог · Справочник Manager · Справочник Command.

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

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

MongoDB\Driver\ManagerСоздать клиента и выполнить запрос, запись или диагностическую команду.

ext-mongodb 1.0.0 и новее · mongodb

final class MongoDB\Driver\Manager

Manager отвечает за подключения к standalone, набору реплик или шардированному кластеру. Получите его через new с параметрами подключения. Это точка входа в низкоуровневый PHP API. Возвращаемое значение зависит от последующего вызова: сетевые команды и запросы дают Cursor, записи — соответствующие объекты результата.

Параметры и создание

Сигнатура конструктора MongoDB\Driver\Manager::__construct(?string $uri = null, ?array $uriOptions = null, ?array $driverOptions = null).

$uri = 'mongodb://127.0.0.1:27075/?directConnection=true&retryWrites=false';
$manager = new MongoDB\Driver\Manager($uri);
echo get_class($manager), PHP_EOL; // MongoDB\Driver\Manager

На экране появится имя созданного класса. Конструктор сохраняет параметры учебного подключения. Проверку доступности сервера выполнит отдельная команда ping ниже. URI называет развёртывание. Второй аргумент задаёт параметры соединения и выбора сервера, третий — опции PHP-драйвера, в том числе disableClientPersistence. Перечень параметров, ошибки формата и значения по умолчанию разобраны в __construct.

Методы по задаче

ЗадачаМетоды
Открыть сессию и узнать параметры соединенияstartSession, getReadConcern, getWriteConcern, getReadPreference
Посмотреть известные серверы и выбрать нужныйgetServers, selectServer
Получить строки и отправить записиexecuteQuery, executeBulkWrite, executeBulkWriteCommand
Отправить команду подходящего типаexecuteCommand, executeReadCommand, executeWriteCommand, executeReadWriteCommand
Наблюдать за событиями драйвераaddSubscriber, removeSubscriber
Создать клиент шифрования и прочитать карту шифруемых полейcreateClientEncryption, getEncryptedFieldsMap

Параметры шифрования можно сверить с PHP Manual. Создание ClientEncryption · Получение карты зашифрованных полей.

Проверить доступность адреса

$manager = new MongoDB\Driver\Manager($uri, ['serverSelectionTimeoutMS' => 1500]);
$reply = $manager->executeCommand($db, new MongoDB\Driver\Command(['ping' => 1]));
echo (int) $reply->toArray()[0]->ok; // 1

Новый Manager может существовать до установления соединения. Перехватывайте исключение при выполнении операции. Для корректного, но недоступного адреса выбор сервера может завершиться ConnectionTimeoutException. Иная причина (например, ошибка авторизации) требует другого сообщения. Рабочий и недоступный адреса в одной программе.

Диагностировать повторные подключения

$a = new MongoDB\Driver\Manager($uri, $options);
$b = new MongoDB\Driver\Manager($uri, $options);
$separate = new MongoDB\Driver\Manager($uri, $options, ['disableClientPersistence' => true]);

Два компонента могут получить разные объекты PHP, но воспользоваться одним нижележащим клиентом. На учебном standalone сравнение connectionId показывает одинаковые первые два номера и другой номер у третьего клиента. Условия опыта — один стабильный процесс и один сервер.

MongoDB\Driver\CommandСобрать документ команды для сервера и прочитать ответ.

ext-mongodb 1.0.0 и новее · mongodb

final class MongoDB\Driver\Command

Command хранит неизменяемый BSON-документ, представляющий команду сервера. Это final-класс с одним публичным конструктором. Для повторяющихся команд можно написать свою функцию или объект, который собирает документ и возвращает Command.

Вход и результат

Сигнатура конструктора MongoDB\Driver\Command::__construct(array|object $document, ?array $commandOptions = null).

$ping = new MongoDB\Driver\Command(['ping' => 1]);
echo get_class($ping), PHP_EOL; // MongoDB\Driver\Command

В $document первым полем указывается имя команды. Следующие поля относятся к выбранной команде, включая поддерживаемый ею maxTimeMS. В $commandOptions передаются только поддержанные опции исполнения. С версии расширения 1.4 это maxAwaitTimeMS — положительный предел ожидания getMore для подходящего tailable-курсора. Создание Command не посылает запрос. Менеджер исполняет его для указанной базы и возвращает Cursor. Полная карточка Command::__construct.

Проверить, отвечает ли MongoDB

$ping = new MongoDB\Driver\Command(['ping' => 1]);
$reply = $manager->executeCommand($db, $ping)->toArray()[0];
echo (int) $reply->ok; // 1

Параметры проверяются при конструировании. InvalidArgumentException при разборе параметров отличается от исключения, которое вернул менеджер после выполнения неподдерживаемой команды или сбоя связи. Сравнить состояние клиента до и после команды.

Составить команду для счётчика заказов

function countOrders(string $collection, string $state): MongoDB\Driver\Command
{
    return new MongoDB\Driver\Command([
        'count' => $collection, 'query' => ['state' => $state], 'maxTimeMS' => 1500,
    ]);
}
$reply = $manager->executeReadCommand($db, countOrders($name, 'готов'))->toArray()[0];
echo $reply->n; // 2 на учебных данных

Функция ограничивает клиентский ввод значением состояния, имя коллекции берёт из приложения. В упражнении команда и последующее изменение исходного массива видны два отдельных счётчика. Для однотипных повторяющихся операций библиотека PHPLIB также предоставляет готовые методы коллекции.

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

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

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

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

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