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

Дерево руководства
Глава 178 · После первого запроса к MongoDB и знакомства с ключом данных
MongoDB: как устроить коллекцию с поиском по зашифрованным полям
Как клиентская карта, серверная схема, vault и служебные коллекции связаны с результатом реального чтения весов и зон.
Коробка с видимым номером и защищённой массой
На складе хотят найти коробки нужного веса. Оператору нужен результат «BOX-50 — 50», а сама база должна хранить weight в защищённом виде. В этой главе построим коллекцию для такой задачи, сохраним коробку и прочитаем её обычным и настроенным клиентами. Затем подключим поиск по значению.
- PHP знает, что BOX-50 весит 50 условных единиц
- Приложение явно шифрует целое число и записывает через QE-клиент
- Обычный клиент находит BOX-50, но у weight видит BSON Binary подтипа 6
- Клиент приложения получает из той же записи число 50
Queryable Encryption (QE) позволяет искать по предусмотренным схемой полям с зашифрованными значениями. Приложение называет поле, его тип, ключ и допустимый вид поиска. Драйвер и сервер поддерживают структуры, необходимые для выполнения запросов. В главе о классическом CSFLE разобран другой механизм. Здесь используется схема QE для индексируемых полей.
$map = $plainClient->getManager()->getEncryptedFieldsMap();
var_dump($map); // NULL: у этого Manager локальная карта не заданаПеременная $plainClient хранит клиент библиотеки MongoDB. Стрелка вызывает метод объекта. Первый вызов возвращает низкоуровневый Manager, второй читает его настройки. Пустые скобки означают, что здесь нет аргументов. Сам вызов не читает коробку. Сравнить два объекта и фактическую карточку.
Разобрать поля схемы и передать её на обе стороны
| Часть маршрута | Значение в наших примерах | Кто использует |
|---|---|---|
| Путь поля | weight внутри документа коробки | Клиент и сервер при согласовании описания |
| Тип BSON | int, например 50 | Клиент при подготовке значения и серверная схема |
| Идентификатор ключа | UUID отдельного DEK этого поля | Клиент с доступом к указанному vault |
| Разрешённый поиск | range, целые значения от 0 до 200 включительно | Индексируемая схема и явно подготовленный запрос |
| Имя пространства | database.collection как одна строка | Ключ локальной таблицы encryptedFieldsMap |
| Где проверить серверную схему | options.encryptedFields | Ответ listCollections в нужной базе |
Представьте карту маршрута: локальная копия у отправителя и фактическая схема у получателя должны описывать одни и те же поля. Здесь аналогия заканчивается: изменение карты в конфигурации PHP не меняет серверную коллекцию. Заданная доверенная карта позволяет клиенту опираться на собственные параметры вместо слепого принятия присланной схемы.
$fields = ['fields' => [[
'path'=>'weight', 'bsonType'=>'int', 'keyId'=>$weightId,
'queries'=>['queryType'=>'range', 'contention'=>0, 'min'=>0, 'max'=>200],
]]];
$db->createCollection($collectionName, ['encryptedFields'=>$fields]);
$options['encryptedFieldsMap'] = [$dbName.'.'.$collectionName => $fields];К моменту этого фрагмента у приложения есть DEK и его UUID в $weightId. Один и тот же документ правил передаётся в двух разных местах. Первая операция создаёт коллекцию средствами PHPLIB. Следующая строка лишь готовит опции клиента. Отдельные хранилище и идентификаторы DEK разобраны раньше. Для каждого защищённого пути создавайте отдельный DEK.
Схема 8.0 задаёт допустимые виды поиска. Для массы явно заданы целочисленные границы. В учебном наборе contention=0 согласован с contentionFactor=0 при подготавливаемых операциях. Это настройка демонстрации. Для рабочей нагрузки выбор диапазона и contention зависит от данных и конкурирующих записей. encryptedFieldsMap не следует смешивать со schemaMap классического CSFLE для того же namespace.
Какие коллекции и ключи обеспечивают поиск
- Коллекция коробок хранит _id, открытые поля и защищённое значение
- Служебные ESC и ECOC поддерживают информацию для поиска
- У документа есть служебный __safeContent__, у коллекции — связанный индекс
- Vault хранит документы ключей, а клиент приложения знает локальный учебный CMK
Цена поиска — дополнительные операции записи и хранение служебного состояния. Служебные коллекции появляются для конкретной зашифрованной коллекции. Их содержимое нельзя использовать как данные прикладного экрана. Для операций удаления или обслуживания действующей коллекции следуйте правилам MongoDB. Наши программы очищают только изолированные коллекции текущего эксперимента.
В явном режиме код сам передаёт encrypt исходное значение и опции. Конфигурация клиента содержит autoEncryption и bypassQueryAnalysis=true. Это отключает автоматический анализ исходящего запроса и позволяет работать без crypt_shared и mongocryptd. Готовый шифропакет по-прежнему должен пройти через настроенный клиент. В частности, для индексируемых и диапазонных операций bypassAutoEncryption остаётся false (по умолчанию).
Запуск на отдельной одноузловой реплике
Восемь автономных PHP-программ рассчитаны на отдельную одноузловую реплику phpmanRs с MongoDB Community 8.0.34, PHP 8.5.11, ext-mongodb 2.5.5 с libmongocrypt и официальной библиотекой mongodb/mongodb 2.5.0. Воспроизводимые команды для нового стенда, включая запуск mongod, однократную инициализацию и установку Composer-библиотеки, даны в каждом README.
php --ri mongodb
$env:PHPMAN_MONGODB_URI='mongodb://127.0.0.1:27076/?replicaSet=phpmanRs&retryWrites=false'
$env:PHPMAN_MONGODB_DATABASE='php_manual_txn_lab'
$env:PHPMAN_MONGODB_AUTOLOAD=(Resolve-Path C:\qe-lab\library\vendor\autoload.php).Path
php example.phpРаспакуйте example.php и qec133-helper.php в отдельную папку. result.html покажет таблицу, result.json содержит те же значения. Проверяется существование собственных имён до создания, после создания и после очистки. Мастер-ключ генерируется в памяти. В отчёт попадают только заранее отобранные учебные значения и числовые результаты проверок.
Узнать, настроен ли клиент PHP для этой коллекции
Сервис склада видит коробку A-12. У одного клиента поле weight приходит бинарным, а экран другого показывает массу 42. Начнём с простого вопроса: какую карту полей получил конкретный клиент PHP?
- У обычного Manager карта отсутствует
- У Manager приложения найти правило по полному имени коллекции
- Выбрать путь weight из описания полей
- Отдельным запросом приложения прочитать число 42
Короткий вызов
$plainMap = $plain->getManager()->getEncryptedFieldsMap(); // null
$qeMap = $client->getManager()->getEncryptedFieldsMap();
$schema = ((array) $qeMap)[$namespace] ?? null;Результат
| Что проверили | Результат |
|---|---|
| Карта обычного клиента | NULL |
| Для этой коллекции карта есть | true |
| Поле в карте | weight |
| Масса из карточки | 42 |
Как получены эти значения
Getter читает опцию encryptedFieldsMap данного Manager. Если опцию не указали, возвращается null. Поле weight и его числовое значение видны в разных местах: имя — в настройках, число — в результате запроса зашифрованного документа.
Ограничения
Локальная карта может отсутствовать даже при наличии реальной зашифрованной коллекции на сервере. Успешная расшифровка приложением зависит также от конфигурации клиента, доступности ключа и структуры сохранённого значения.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
$plainMap = $lab['plain']->getManager()->getEncryptedFieldsMap();
$qeMap = $lab['client']->getManager()->getEncryptedFieldsMap();
$rule = qecField($qeMap,$lab['ns']);
$fields = qecField($rule,'fields');
$field = qecField($fields,0);
qecWriteBox($lab,'A-12',42,'осмотрена');
$card = $lab['collection']->findOne(['_id'=>'A-12'],['projection'=>['_id'=>0,'weight'=>1]]);
$checks = ['plain_unconfigured'=>$plainMap === null,'local_map_exists'=>$qeMap !== null,'namespace_found'=>$rule !== null,
'weight_mapped'=>qecField($field,'path') === 'weight','correct_type'=>qecField($field,'bsonType') === 'int','read_back'=>$card->weight === 42];
$rows = [['Карта обычного клиента',$plainMap],['Для этой коллекции карта есть',$rule !== null],
['Поле в карте',qecField($field,'path')],['Масса из карточки',$card->weight]];
return [$rows,$checks];
}, false);
qecReport('mongodb133-qec-client-map', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-client-map: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-client-map: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Откройте результат под обычным и настроенным Manager. Составьте для поддержки два разных сообщения: «локальная карта отсутствует» и «для этой коллекции найдено правило». Не выводите карту целиком.
Вопрос о несовместимости версии расширения и программы. Отсутствующий метод отличается от существующего метода, честно вернувшего null. Вызываемый PHP-метод getEncryptedFieldsMap отсутствует на Stack Overflow.
Проверить, что правило относится к нужной базе и коллекции
На тестовом и рабочем контурах могут быть коллекции с одинаковыми короткими именами. Экран диагностики должен сообщить, нашёл ли он описание именно текущей коллекции.
- Составить строку из имени базы, точки и имени коллекции
- Найти её в локальной карте
- Сравнить с поиском по короткому имени и имени в другой базе
- Показать найденное правило range и статус учебной коробки
Короткий вызов
$map = (array) $manager->getEncryptedFieldsMap();
$namespace = $databaseName.'.'.$collectionName;
$matched = array_key_exists($namespace, $map);Результат
| Что проверили | Результат |
|---|---|
| Полное имя подошло | true |
| Только имя коллекции подошло | false |
| Имя в другой базе подошло | false |
| Вид поиска | range |
| Открытая метка | взвешена |
Как получены эти значения
Ключ массива карты — полная строка database.collection. В эксперименте проверяются три ключа одной конфигурации. Проверка строки с другой базой выполняется локально, без обращения к ней.
Ограничения
Если приложение сменило имя базы, не исправляйте ситуацию подбором похожих ключей. Получите доверенную конфигурацию нужного окружения, затем сравните с серверной схемой.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
$map = $lab['client']->getManager()->getEncryptedFieldsMap();
$byFull = qecField($map,$lab['ns']);
$byShort = qecField($map,$lab['name']);
$otherDatabase = qecField($map,'other_lab.'.$lab['name']);
$field = qecField(qecField($byFull,'fields'),0);
$queries = qecQueryRule($field);
qecWriteBox($lab,'NS-1',35,'взвешена');
$tag = $lab['collection']->findOne(['_id'=>'NS-1'],['projection'=>['_id'=>0,'label'=>1]]);
$checks = ['full_namespace_found'=>$byFull !== null,'short_name_absent'=>$byShort === null,
'wrong_database_absent'=>$otherDatabase === null,'key_matches'=>qecField($field,'keyId') == $lab['weightId'],
'range_rule'=>qecField($queries,'queryType') === 'range', 'box_found'=>$tag->label === 'взвешена'];
$rows = [['Полное имя подошло',$byFull !== null],['Только имя коллекции подошло',$byShort !== null],
['Имя в другой базе подошло',$otherDatabase !== null],['Вид поиска',qecField($queries,'queryType')],['Открытая метка',$tag->label]];
return [$rows,$checks];
}, false);
qecReport('mongodb133-qec-namespace', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-namespace: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-namespace: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Измените только регистр символа в своем локальном ключе поиска. Поясните, почему точное совпадение строк помогает обнаружить ошибку конфигурации.
Сверить карту PHP с параметрами коллекции на сервере
При обновлении сервиса нужно проверить, что карта PHP и параметры коллекции согласованы. Одна сторона может показывать правильный локальный файл при другой версии серверной схемы.
- Getter возвращает заданное приложению правило
- listCollections читает настройки именно этой коллекции
- Сравнить поле, тип, границы и идентификатор ключа
- В отчёте показать выбранные значения и флаг совпадения ключа
Короткий вызов
$items = $db->listCollections(['filter' => ['name' => $collectionName]]);
foreach ($items as $item) {
$serverSchema = $item->getOptions()['encryptedFields'];
}Результат
| Что проверили | Результат |
|---|---|
| Путь на сервере | weight |
| Тип BSON | int |
| Минимальная масса | 0 |
| Максимальная масса | 200 |
| Ключ для поля совпал | true |
Как получены эти значения
В этом опыте PHP действительно читает каталог сервера. Сравниваем смысл нужных полей, а не побайтовое совпадение документов: сервер может добавить нормализованные параметры и имена служебных коллекций. Рамки 0 и 200 заданы в выбранной учебной схеме.
Ограничения
Обновление локального encryptedFieldsMap не переконфигурирует свойства существующей коллекции. Переход на другой набор индексируемых полей или вид запроса — отдельная миграция со схемой назначения и проверкой данных.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
$map = $lab['client']->getManager()->getEncryptedFieldsMap();
$local = qecField(qecField(qecField($map,$lab['ns']),'fields'),0);
$catalog = iterator_to_array($lab['db']->listCollections(['filter'=>['name'=>$lab['name']]]),false);
$serverFields = $catalog[0]->getOptions()['encryptedFields'];
$remote = qecField(qecField($serverFields,'fields'),0);
$rule = qecQueryRule($remote);
$localRule = qecQueryRule($local);
$sameKey = qecField($local,'keyId') == qecField($remote,'keyId');
$sameBounds = qecField($rule,'min') === qecField($localRule,'min') && qecField($rule,'max') === qecField($localRule,'max');
$checks = ['server_collection'=>count($catalog) === 1 && $catalog[0]->getName() === $lab['name'],
'path_on_server'=>qecField($remote,'path') === 'weight','int_on_server'=>qecField($remote,'bsonType') === 'int',
'rule_on_server'=>qecField($rule,'queryType') === 'range','range_matches'=>$sameBounds && qecField($rule,'min') === 0 && qecField($rule,'max') === 200,
'same_field_key'=>$sameKey,'local_and_remote_path'=>qecField($local,'path') === qecField($remote,'path')];
$rows = [['Путь на сервере',qecField($remote,'path')],['Тип BSON',qecField($remote,'bsonType')],
['Минимальная масса',qecField($rule,'min')],['Максимальная масса',qecField($rule,'max')],['Ключ для поля совпал',$sameKey]];
return [$rows,$checks];
}, false);
qecReport('mongodb133-qec-server-schema', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-server-schema: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-server-schema: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Сравните с сервером также queryType, оставив ключи и полные схемы вне журнала. При расхождении остановите именно ввод несовместимой версии приложения.
Автор хочет изменить набор queryable-полей. Раздел сопоставляет локальные параметры и наблюдаемую серверную схему. Изменение карты уже созданной коллекции на Stack Overflow.
Найти коллекцию коробок, служебные коллекции и индекс
После появления одной коллекции складских коробок в каталоге видны ещё имена с префиксом enxcol_. Определим, какие из них обеспечивают поиск и где лежат ключи.
- Vault содержит документ ключа данного поля
- Основная коллекция хранит box STORE-1
- ESC и ECOC обслуживают состояние зашифрованного поиска
- В основной коллекции проверить наличие __safeContent__ и его индекса
Короткий вызов
foreach ($collection->listIndexes() as $index) {
$usesSearchMetadata = isset($index->getKey()['__safeContent__']);
}Результат
| Что проверили | Результат |
|---|---|
| Создано пространств имён | 4 |
| Есть ESC | true |
| Есть ECOC | true |
| Документ содержит служебное поле | true |
| Есть индекс по __safeContent__ | true |
Как получены эти значения
Проверка имён использует точный список четырёх созданных пространств. При штатном создании коллекции через PHPLIB появляются структуры для поиска. Пример запрашивает описание индексов и присутствие служебного поля, без публикации содержимого метаданных.
Ограничения
Эти коллекции и поле обслуживает механизм Queryable Encryption. Ручное изменение служебных записей может нарушить результаты запросов. Четыре имени включают отдельный vault этого опыта. В другом проекте один vault может обслуживать несколько коллекций.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
qecWriteBox($lab,'STORE-1',60,'на полке');
$raw = $lab['raw']->findOne(['_id'=>'STORE-1']);
$hasSafeContent = isset($raw->__safeContent__);
$hasQueryIndex = false;
foreach ($lab['raw']->listIndexes() as $description) {
if (($description->getKey()['__safeContent__'] ?? null) === 1) {$hasQueryIndex = true;}
}
$checks = ['business_exists'=>in_array($lab['name'],$lab['seen'],true),'vault_exists'=>in_array($lab['vault'],$lab['seen'],true),
'esc_exists'=>in_array($lab['esc'],$lab['seen'],true),'ecoc_exists'=>in_array($lab['ecoc'],$lab['seen'],true),
'metadata_field'=>$hasSafeContent,'metadata_index'=>$hasQueryIndex,'protected_subtype'=>$raw->weight->getType() === 6];
$rows = [['Создано пространств имён',count($lab['seen'])],['Есть ESC',$checks['esc_exists']],['Есть ECOC',$checks['ecoc_exists']],
['Документ содержит служебное поле',$hasSafeContent],['Есть индекс по __safeContent__',$hasQueryIndex]];
return [$rows,$checks];
}, false);
qecReport('mongodb133-qec-storage-parts', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-storage-parts: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-storage-parts: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Сравните список полей сырого документа и проекцию для экрана. В прикладной отчёт передайте только названия покупательских полей, которым он действительно пользуется.
Прочитать одну коробку обычным и настроенным клиентом
Техник и оператор смотрят одну и ту же коробку по её открытому номеру. Технику нужно подтвердить сохранение защищённого типа, оператору — увидеть массу 50.
- Зашифровать int 50 по схеме range и записать через QE-клиент
- Обычным клиентом найти BOX-50 по открытому _id
- Настроенным клиентом найти ту же BOX-50
- Сопоставить тип веса и общую открытую подпись
Короткий вызов
$raw = $plainCollection->findOne(['_id' => 'BOX-50']);
$opened = $qeCollection->findOne(['_id' => 'BOX-50']);
echo $raw->weight->getType(), ' / ', $opened->weight; // 6 / 50Результат
| Что проверили | Результат |
|---|---|
| Коробка | BOX-50 |
| Подтип weight у обычного клиента | 6 |
| Масса на экране приложения | 50 |
| Открытая подпись | упакована |
Как получены эти значения
В коллекции находится одна запись. В первом ответе поле представлено как BSON Binary подтипа 6. Второй клиент использует доступ к ключам для расшифрования ответа, поэтому приложению доступен int. Результаты сверяются по одному _id.
Ограничения
Присутствие Binary подтипа 6 подтверждает представление поля, но само по себе не доказывает работу зашифрованных предикатов. В следующей главе есть реальный поиск по значению.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
qecWriteBox($lab,'BOX-50',50,'упакована');
$raw = $lab['raw']->findOne(['_id'=>'BOX-50'],['projection'=>['_id'=>1,'weight'=>1,'label'=>1]]);
$opened = $lab['collection']->findOne(['_id'=>'BOX-50'],['projection'=>['_id'=>1,'weight'=>1,'label'=>1]]);
$checks = ['same_box'=>$raw->_id === $opened->_id && $raw->_id === 'BOX-50',
'raw_is_binary'=>$raw->weight instanceof Binary,'raw_subtype'=>$raw->weight->getType() === 6,
'application_integer'=>is_int($opened->weight),'application_weight'=>$opened->weight === 50,
'label_unchanged'=>$raw->label === $opened->label && $raw->label === 'упакована'];
$rows = [['Коробка',$opened->_id],['Подтип weight у обычного клиента',$raw->weight->getType()],
['Масса на экране приложения',$opened->weight],['Открытая подпись',$opened->label]];
return [$rows,$checks];
}, false);
qecReport('mongodb133-qec-raw-and-application', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-raw-and-application: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-raw-and-application: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Получите только _id и label обычным клиентом. Для такого списка чтение массы вообще не требуется.
Выводить массу только на экране, где она нужна
Обзор склада показывает только номер коробки и полку. Масса нужна в подробной карточке. Чтобы собрать два разных экрана, ограничим поля прямо в двух запросах.
- Создать две коробки с открытыми метками и защищённой массой
- Обычным клиентом выбрать номера и полки для списка
- Расположить строки по открытому _id
- Настроенным клиентом запросить только массу выбранной P-2
Короткий вызов
$list = $plainCollection->find([], [
'projection' => ['_id'=>1,'label'=>1], 'sort'=>['_id'=>1], 'limit'=>2,
]);
$detail = $qeCollection->findOne(['_id'=>'P-2'], ['projection'=>['_id'=>0,'weight'=>1]]);Результат
| Что проверили | Результат |
|---|---|
| Первая строка списка | P-1 — полка A |
| Вторая строка списка | P-2 — полка B |
| Масса выбранной P-2 | 75 |
Как получены эти значения
Сервер применяет сортировку и проекцию списка. Список не содержит веса и служебного поля. Второе обращение возвращает расшифрованную массу единственного документа. Порядок получен по открытому номеру, а не по двоичным шифротекстам.
Ограничения
Проекция ограничивает выдачу данного запроса. Она не выдаёт и не отзывает права доступа. Для ограничения действительных полномочий отдельно настройте права клиента и доступ к ключам.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
qecWriteBox($lab,'P-2',75,'полка B');
qecWriteBox($lab,'P-1',25,'полка A');
$labels = [];
foreach ($lab['raw']->find([],['sort'=>['_id'=>1],'projection'=>['_id'=>1,'label'=>1],'limit'=>2]) as $doc) {
if (isset($doc->weight) || isset($doc->__safeContent__)) {throw new RuntimeException('Projection mismatch');}
$labels[] = $doc->_id.' — '.$doc->label;
}
$detail = $lab['collection']->findOne(['_id'=>'P-2'],['projection'=>['_id'=>0,'weight'=>1]]);
$checks = ['two_labels'=>count($labels) === 2,'first_label'=>$labels[0] === 'P-1 — полка A','second_label'=>$labels[1] === 'P-2 — полка B',
'selected_weight'=>$detail->weight === 75,'detail_excludes_label'=>!isset($detail->label),'detail_excludes_id'=>!isset($detail->_id)];
$rows = [['Первая строка списка',$labels[0]],['Вторая строка списка',$labels[1]],['Масса выбранной P-2',$detail->weight]];
return [$rows,$checks];
}, false);
qecReport('mongodb133-qec-public-projection', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-public-projection: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-public-projection: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Измените сортировку _id на -1, сохранив limit=1. Убедитесь, что список содержит P-2 и возвращает только номер и подпись.
Убедиться, что сервер отклоняет открытый вес
Старый загрузчик пользуется обычным Manager и пытается записать вес как открытое число. Проверим реальный ответ сервера и обратным чтением узнаем, могло ли появиться такое значение.
- Поместить открытое int 50 в BulkWrite обычного клиента
- Отправить запись в созданную защищённую коллекцию
- Получить код ошибки элемента и проверить отсутствие BAD-50
- Вставить GOOD-50 с подготовленным payload через QE-клиент и прочитать массу
Короткий вызов
$wrong = new MongoDB\Driver\BulkWrite();
$wrong->insert(['_id'=>'BAD-50', 'weight'=>50]);
// В полной программе plain Manager отправляет запись, catch читает код WriteError.Результат
| Что проверили | Результат |
|---|---|
| Код ошибки для открытой записи | 121 |
| Строк для BAD-50 | 0 |
| Масса GOOD-50 после корректной записи | 50 |
| Подтип сохранённого поля | 6 |
Как получены эти значения
На данном MongoDB 8.0.34 серверная проверка документа отвергла открытую запись с кодом 121. После ошибки у BAD-50 нет строки. Корректный путь сначала вызывает encrypt с алгоритмом Range, а затем отправляет операцию через QE-клиент. У него тот же исходный int сохраняется как защищённое поле.
Ограничения
Проверяются конкретные версия, схема и присутствующее в документе поле weight. Этот результат нельзя автоматически перенести на обычную коллекцию, у которой описана лишь локальная клиентская карта, или на документ, в котором поле отсутствует. Обработчик повторно выбрасывает неожиданные ошибки.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
$wrong = new MongoDB\Driver\BulkWrite();
$wrong->insert(['_id'=>'BAD-50','label'=>'не должна появиться','weight'=>50]);
$itemCode = null;
try {
$lab['plain']->getManager()->executeBulkWrite($lab['ns'],$wrong);
} catch (MongoDB\Driver\Exception\BulkWriteException $error) {
$errors = $error->getWriteResult()->getWriteErrors();
if (count($errors) !== 1 || $errors[0]->getCode() !== 121) {throw $error;}
$itemCode = $errors[0]->getCode();
}
$absent = $lab['raw']->countDocuments(['_id'=>'BAD-50']);
qecWriteBox($lab,'GOOD-50',50,'прошла проверку');
$good = $lab['collection']->findOne(['_id'=>'GOOD-50'],['projection'=>['_id'=>1,'weight'=>1]]);
$physical = $lab['raw']->findOne(['_id'=>'GOOD-50'],['projection'=>['_id'=>0,'weight'=>1]]);
$checks = ['validation_error'=>$itemCode === 121,'failed_doc_absent'=>$absent === 0,
'exactly_one_good'=>$lab['raw']->countDocuments(['_id'=>'GOOD-50']) === 1,'correct_id'=>$good->_id === 'GOOD-50',
'valid_input_round_trip'=>$good->weight === 50,'stored_as_cipher'=>$physical->weight->getType() === 6];
$rows = [['Код ошибки для открытой записи',$itemCode],['Строк для BAD-50',$absent],
['Масса GOOD-50 после корректной записи',$good->weight],['Подтип сохранённого поля',$physical->weight->getType()]];
return [$rows,$checks];
}, false);
qecReport('mongodb133-qec-reject-plaintext', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-reject-plaintext: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-reject-plaintext: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Добавьте отдельное сообщение оператору о несовместимой версии загрузчика. В публичную ошибку включите бизнес-идентификатор и код, исключив тело команды с защищёнными значениями.
В исходном вопросе смешиваются настройки клиентского шифрования и проверка записи сервером в другом продукте и версии. Ниже отдельно воспроизводится поведение настоящей QE-коллекции на MongoDB Community 8.0.34. Где реально проверяется шифрование поля на Stack Overflow.
Согласовать два защищённых поля с разными способами поиска
Карточка отгрузки содержит текстовую зону и числовую массу. Для зоны нужен поиск точного значения, для массы — диапазон. Подготовим оба поля так, чтобы следующий урок мог строить эти запросы.
- Создать независимые DEK для zone и weight
- Связать строку zone с equality, целое weight с range
- Записать оба подготовленных значения в одной коробке
- Сравнить сохранённые типы и прикладные значения east и 80
Короткий вызов
$zoneCipher = $crypto->encrypt('east', [
'algorithm'=>MongoDB\Driver\ClientEncryption::ALGORITHM_INDEXED,
'keyId'=>$zoneId, 'contentionFactor'=>0,
]);Результат
| Что проверили | Результат |
|---|---|
| Поиск поля weight | range |
| Поиск поля zone | equality |
| Для полей разные DEK | true |
| Зона в карточке | east |
| Масса в карточке | 80 |
Как получены эти значения
В этой схеме вид поиска задаётся для каждого пути. При записи zone выбираем алгоритм Indexed, а вес шифруем как Range. Настроенный клиент возвращает типы исходных значений. Проверка здесь читает коробку по открытому _id, с реальными запросами по зашифрованным значениям вы познакомитесь в следующей главе.
Ограничения
У поля выбирают подходящий тип запроса до создания целевой коллекции. Строка «80» и число 80 по смыслу различны. Одна запись под двумя DEK не делает текстовое поле доступным для произвольного поиска по подстроке.
Полная самостоятельная программа и архив
<?php
declare(strict_types=1);
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as Encryption;
require __DIR__.'/qec133-helper.php';
set_exception_handler(static function(Throwable $error): void {
// Print only the class: a message or trace may contain protected data.
fwrite(STDERR,'QE example failed: '.$error::class.PHP_EOL);
exit(1);
});
[$rows,$checks,$runtime,$counts] = qecFixture(static function(array $lab): array {
$zoneOptions = ['keyId'=>$lab['zoneId'],'algorithm'=>Encryption::ALGORITHM_INDEXED,'contentionFactor'=>0];
$weightCipher = $lab['crypto']->encrypt(80,$lab['rangeOptions']);
$zoneCipher = $lab['crypto']->encrypt('east',$zoneOptions);
$lab['collection']->insertOne(['_id'=>'DUAL-1','weight'=>$weightCipher,'zone'=>$zoneCipher,'label'=>'отгрузка']);
$opened = $lab['collection']->findOne(['_id'=>'DUAL-1'],['projection'=>['_id'=>0,'weight'=>1,'zone'=>1]]);
$raw = $lab['raw']->findOne(['_id'=>'DUAL-1'],['projection'=>['_id'=>0,'weight'=>1,'zone'=>1]]);
$map = $lab['client']->getManager()->getEncryptedFieldsMap();
$rules = [];
foreach (qecField(qecField($map,$lab['ns']),'fields') as $field) {
$rules[qecField($field,'path')] = qecField(qecQueryRule($field),'queryType');
}
$checks = ['two_field_rules'=>count($rules) === 2,'range_weight'=>$rules['weight'] === 'range','zone_equality'=>$rules['zone'] === 'equality',
'independent_keys'=>$lab['weightId'] != $lab['zoneId'],'two_cipher_fields'=>$raw->weight->getType() === 6 && $raw->zone->getType() === 6,
'opened_weight'=>$opened->weight === 80,'opened_zone'=>$opened->zone === 'east'];
$rows = [['Поиск поля weight',$rules['weight']],['Поиск поля zone',$rules['zone']],['Для полей разные DEK',$checks['independent_keys']],
['Зона в карточке',$opened->zone],['Масса в карточке',$opened->weight]];
return [$rows,$checks];
}, true);
qecReport('mongodb133-qec-two-fields', ['Что проверили', 'Результат'], $rows,$checks,$runtime,$counts);
echo 'mongodb133-qec-two-fields: OK', PHP_EOL;
Ожидаемый результат
mongodb133-qec-two-fields: OK
Запуск создаёт собственную QE-коллекцию, её служебные пространства и vault, выполняет наблюдение и проверяет очистку. Секретные байты отделены от публичного результата.
Таблица фактически полученных значений · Скачать JSON результата
Попробуйте самостоятельно. Сверьте тип и правило обоих путей с задачей двух экранов. Выберите, как приложение проверит введённую строку массы до передачи значения в encrypt.
Перейти от готовой коллекции к поисковому экрану
Перед первым поиском сверьте две вещи: тип и способ поиска в серверной схеме, а также то, какой клиент запишет и прочитает данные. Карта установлена, код 121 проверен, поля нужного экрана доступны. Теперь вместо обращения по открытому _id можно найти коробки по зоне и диапазону веса.
Карточка getEncryptedFieldsMap · Жизненный цикл ключей · Фильтр и проекция для экрана заказа.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
MongoDB\Driver\Manager::getEncryptedFieldsMapПолучить заданную Manager локальную карту пространств имён и схем Queryable Encryption.
ext-mongodb 1.14.0 и новее · mongodb
final public function MongoDB\Driver\Manager::getEncryptedFieldsMap(): array|object|nullКому принадлежит карта
Метод читает параметр encryptedFieldsMap, которым был сконфигурирован этот Manager. При использовании высокоуровневого Client сначала получите его Manager методом getManager().
Вход и выход
Параметров нет. Результат имеет тип array|object|null. Строками ключей служат полные пространства имён. Значением для каждого из них является описание encryptedFields. Если параметр не указан, вернётся null; отсутствие конкретной строки проверяйте отдельно. Метод появился в ext-mongodb 1.14.0.
Проверить настройку приложения
$map = $client->getManager()->getEncryptedFieldsMap();
$hasLocalMap = $map !== null;
$hasRule = $hasLocalMap && array_key_exists($dbName.'.'.$name, (array) $map);У обычного клиента первый флаг false. У настроенного приложения true для обоих флагов. Так поддержка может определить, какой конфигурацией запущен сервис, без сериализации полной карты. Проверить точное имя базы и коллекции.
Сверить параметры перед запуском загрузчика
$map = $client->getManager()->getEncryptedFieldsMap();
$local = ((array) $map)[$dbName.'.'.$name] ?? null;
$listed = $db->listCollections(['filter'=>['name'=>$name]]);
foreach ($listed as $collectionInfo) {
$remote = $collectionInfo->getOptions()['encryptedFields'] ?? null;
}Прежде чем писать значения, сравните путь, тип BSON, вид поиска и нужный UUID. В программе по ссылке PHP получает локальные настройки и отдельно выполняет серверный запрос. Для произвольной базы обработайте также случай пустого курсора. Работающая сверка с таблицей результата.
Отказы и границы
Официальная страница указывает InvalidArgumentException при ошибке разбора аргументов. Нулевой результат является обычным исходом. Сообщение «undefined method» указывает на несовместимость фактически работающего расширения с кодом. Проверьте php --ri mongodb и конфигурацию того SAPI, где возникает ошибка. Сверить CLI и Composer с используемым PHP.
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


