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

Дерево руководства
Глава 179 · После подготовки коллекции Queryable Encryption
MongoDB: поиск по зашифрованной зоне и диапазону веса
От точного поиска и пустого рейса до схемы границ, проверки полей формы, агрегатного выражения и двух страниц ведомости.
Какие посылки попадут в ведомость
Для рейса нужны посылки зоны N, а для тележки — отправления от 30 до 80 кг. Если и зона, и вес хранятся закрытыми, база всё ещё может отобрать нужные строки по заранее настроенному индексу Queryable Encryption (QE). Начнём с одной посылки и двух способов её увидеть. Затем диспетчер получит готовую ведомость без загрузки всего склада в PHP.
| Отправление | Учебная зона | Учебный вес, кг |
|---|---|---|
| Q-01 | N | 20 |
| Q-02 | S | 30 |
| Q-03 | N | 50 |
| Q-04 | S | 79 |
| Q-05 | N | 80 |
| Q-06 | S | 90 |
Это исходные данные нашего собственного приложения до шифрования. В уже сохранённой коллекции обычное чтение увидит для zone и weight по одному значению BSON Binary с подтипом 6.
- В PHP известны границы 30 и 80, поле weight имеет тип int
- Клиент формирует зашифрованное условие для настроенного индекса
- MongoDB выбирает подходящие записи; клиент раскрывает поля ответа
- Шаблон печатает Q-02 · 30 кг, Q-03 · 50 кг и Q-04 · 79 кг
$minimum = 30;
$maximum = 80;
echo $minimum, '–', $maximum; // 30–80Эти две строки с присваиванием задают ровно числа, которые выбрал диспетчер. Значок $ обозначает переменную, = сохраняет значение. Для базы этого ещё мало: надо указать имя поля, смысл границ и тип разрешённого поиска. Для проверки входа из браузера отдельно понадобится разбор полей GET.
Два вида поиска и порядок выдачи
| Нужна выдача | Конфигурация поля | Что формирует PHP |
|---|---|---|
| Отправления зоны N | zone · string · equality | encrypt("N", … QUERY_TYPE_EQUALITY) создаёт значение для фильтра по точному равенству. |
| Посылки 30 ≤ вес < 80 кг | weight · int · range · допустимо 0–200 | encryptExpression(…) создаёт диапазонное условие. Выполняет запрос объект коллекции. |
| Две строки и кнопка «Дальше» | _id виден, значение уникально | sort и limit задают серверу размер страницы и порядок по открытому _id. |
Схему создают до вставки отправлений. Секретный ключ поля, его BSON-тип и параметры запроса должны соответствовать этой схеме. Со стороны PHP работают два клиента: обычный — для проверки представления в хранилище, и настроенный на QE — для отправки подготовленных операций и чтения результата. Механику ключей объясняет предыдущий материал, схему коллекции — глава о подготовке QE.
Для equality и range здесь используются разные DEK. При записи вызывается encrypt с Indexed или Range без queryType. При поиске передаётся соответствующий queryType. Режим bypassQueryAnalysis=true оставляет построение предикатов нашему коду, при этом клиент продолжает обработку зашифрованных операций. Простое выключение autoEncryption этот режим не заменяет.
Запустить локальный пример и открыть экран
Нужны PHP CLI x64, расширение mongodb со сборкой libmongocrypt, PHP-библиотека и настроенный однопользовательский учебный стенд. Рабочая связка — PHP 8.5.11, ext-mongodb 2.5.5, PHPLIB 2.5.0, MongoDB Community 8.0.34. Используется replica set phpmanRs из одного основного узла на 127.0.0.1:27076. Для новой отдельной реплики команды приведены в README.txt каждого ZIP.
composer require mongodb/mongodb:2.5.0
$env:PHPMAN_MONGODB_URI='mongodb://127.0.0.1:27076/?replicaSet=phpmanRs&retryWrites=false'
$env:PHPMAN_MONGODB_DATABASE='php_manual_txn_lab'
php example.phpТерминал напечатает результат предметной задачи. Откройте result.html, а для сравнения значений — result.json. В каждом опыте заранее проверяется отсутствие четырёх своих случайных имён. Создаются key vault, коллекция отправлений и две служебные коллекции QE (ESC и ECOC). Проверка очистки выполняется по этим именам в finally, включая частично созданные служебные коллекции. Принудительное завершение процесса может прервать очистку.
Учебный локальный мастер-ключ живёт только в памяти процесса. В отчётах присутствуют вымышленные значения, количество записей и метаданные проверок. Публичные данные не включают ключи, запросные токены, шифротексты и внутреннее содержимое QE. Здесь не проверяются отказоустойчивость, облачный KMS или криптографические свойства промышленной системы.
Прочитать одну отправку двумя клиентами
Склад читает номер отправления. Приложение диспетчера дополнительно показывает зону и вес. Почему две программы, прочитав один и тот же документ, видят разную форму значения?
Исходные значения
Клиент $plain читает коллекцию без настройки расшифровки. У $collection клиент знает карту полей и имеет доступ к ключам. Оба выбирают один и тот же открытый _id.
- Q-02 → обычный findOne → номер и два Binary подтипа 6
- Тот же Q-02 → настроенный клиент → строка S и целое 30
- PHP строит таблицу двух представлений
Короткая запись
$raw = $plain->findOne(['_id' => 'Q-02'], ['projection' => ['weight' => 1]]);
$opened = $collection->findOne(['_id' => 'Q-02'], ['projection' => ['weight' => 1]]);Что покажет ведомость
Отправление Q-02 · зона S · 30 кг
Посмотреть экран из реального запуска · Те же строки в JSON
| Поле | Обычное чтение | Клиент с ключами |
|---|---|---|
| Номер | Q-02 | Q-02 |
| Зона | Binary 6 | S |
| Вес | Binary 6 | 30 |
Объяснение
Шифрование меняет сохранённое представление конкретных полей. На открытый номер и поле kind оно не распространяется. Подтип показывает, что драйвер прочитал закрытое значение. Он один не определяет, какие запросы были разрешены для этого поля.
Границы примера
Видимость двух представлений демонстрируется одним учебным процессом. Настоящие права диспетчера на конкретный заказ следует проверять отдельно. Схема поисковых возможностей создаётся вместе с коллекцией; одна лишь правка карты клиента не перестраивает существующие данные.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-stored-types', 'Прочитать одну отправку двумя клиентами', ['Поле', 'Обычное чтение', 'Клиент с ключами'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
$options = ['projection' => ['_id'=>1, 'zone'=>1, 'weight'=>1, 'kind'=>1]];
$raw = $plain->findOne(['_id'=>'Q-02'], $options);
$opened = $collection->findOne(['_id'=>'Q-02'], $options);
$checks['number_stays_open'] = $raw['_id'] === $opened['_id'] && $opened['_id'] === 'Q-02';
$checks['weight_binary_six'] = $raw['weight'] instanceof Binary && $raw['weight']->getType() === 6;
$checks['zone_binary_six'] = $raw['zone'] instanceof Binary && $raw['zone']->getType() === 6;
$checks['zone_readable'] = $opened['zone'] === 'S';
$checks['integer_weight'] = $opened['weight'] === 30;
$checks['kind_visible'] = $raw['kind'] === $opened['kind'];
$rows = [['Номер', $raw['_id'], $opened['_id']], ['Зона', 'Binary '.$raw['zone']->getType(), $opened['zone']],
['Вес', 'Binary '.$raw['weight']->getType(), $opened['weight']]];
return [$rows, $checks, 'Отправление '.$opened['_id'].' · зона '.$opened['zone'].' · '.$opened['weight'].' кг', ''];
}
);
Ожидаемый результат
Отправление Q-02 · зона S · 30 кг
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Выберите Q-05. Обычный клиент снова покажет подтип 6, а настроенный прочитает зону N и вес 80.
Меняет ли правка клиентской карты возможности поиска уже существующей коллекции? Указать оба запросных поля при создании и сравнить одно настоящее сохранённое значение при обычном и осведомлённом чтении. Постановка вопроса на Stack Overflow.
Найти отправления одной зоны
Машина выезжает в северную зону N. Из шести записей требуются только её отправления, хотя в базе текст зоны закрыт.
Исходные значения
У zone в схеме записан вид поиска equality и тип string. Объект $eq содержит algorithm=Indexed, keyId этого поля и contentionFactor=0.
- В форме выбрано N → строится значение для поискового равенства
- QE-клиент отправляет фильтр в MongoDB
- PHP получает три отправления зоны N и выводит только номер и зону
Короткая запись
$zoneToFind = 'N';
$needle = $crypto->encrypt($zoneToFind, $eq + ['queryType' => E::QUERY_TYPE_EQUALITY]);
$result = $collection->find(['zone' => $needle]);Что покажет ведомость
Северная зона · Q-01, Q-03, Q-05
Посмотреть экран из реального запуска · Те же строки в JSON
| Отправление | Зона |
|---|---|
| Q-01 | N |
| Q-03 | N |
| Q-05 | N |
Объяснение
Для сохранения зоны метод encrypt вызывается с опциями Indexed без queryType. Для сравнения с N добавляется QUERY_TYPE_EQUALITY. Возвращённый предикат применяет клиент с autoEncryption. Обход курсора происходит уже по совпадениям, отсортированным сервером.
Границы примера
Равенство здесь касается всей строки. Поиск всех зон на букву N, поиск части адреса и обычное регулярное выражение — другие операции. Детерминированное шифрование из классического CSFLE и Indexed у QE используют разные схемы.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-zone-match', 'Найти отправления одной зоны', ['Отправление', 'Зона'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
$needle = $crypto->encrypt('N', $eq + ['queryType'=>E::QUERY_TYPE_EQUALITY]);
$cursor = $collection->find(['zone'=>$needle], ['projection'=>['_id'=>1,'zone'=>1], 'sort'=>['_id'=>1], 'limit'=>10]);
$rows = [];
foreach ($cursor as $doc) {
$rows[] = [$doc['_id'], $doc['zone']];
}
$checks['three_hits'] = count($rows) === 3;
$checks['expected_ids'] = array_column($rows,0) === ['Q-01','Q-03','Q-05'];
$checks['all_north'] = array_unique(array_column($rows,1)) === ['N'];
$checks['query_value_is_binary'] = $needle instanceof Binary;
return [$rows, $checks, 'Северная зона · '.implode(', ', array_column($rows,0)), ''];
}
);
Ожидаемый результат
Северная зона · Q-01, Q-03, Q-05
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Укажите S вместо N. Правильная выборка будет Q-02, Q-04, Q-06.
Можно ли искать на сервере, если исходное поле закрыто до отправки? Запросить точную строку зоны по QE equality, затем показать только совпадающие номера и расшифрованные у клиента зоны. Постановка вопроса на Stack Overflow.
Кто готовит значение для поиска по закрытому полю? В вопросе разработчика о поиске по зашифрованному адресу эту задачу ставят для C# и классического CSFLE. Посмотрите, как она решается в этом примере на PHP с Queryable Encryption. Приложение передаёт выбранную зону N в encrypt() с параметром QUERY_TYPE_EQUALITY. Полученный BSON Binary становится значением поля zone в фильтре. Сервер выполняет поиск, а приложение получает три отправления — Q-01, Q-03 и Q-05. Явное шифрование в MongoDB 8.0.
Показать диспетчеру пустой рейс
Для нового маршрута Z ещё не зарегистрировали коробки. Пустая страница должна сообщить об этом, а сбой доступа к базе или ключу должен попасть в обработчик ошибки.
Исходные значения
Для Z подготовлено допустимое значение того же строкового типа. Запрос завершается и возвращает пустой курсор. Доступность учебных данных отдельно видна на Q-01.
- Зона Z → корректное значение для поиска
- Сервер успешно завершает поиск → 0 документов
- Ограниченный результат [] → сообщение о пустом рейсе
Короткая запись
$items = $collection->find(['zone' => $needle], ['projection' => ['_id' => 1], 'limit' => 5])->toArray();
if ($items === []) {
echo 'В зоне Z сейчас нет отправлений';
}Что покажет ведомость
В зоне Z сейчас нет отправлений
Посмотреть экран из реального запуска · Те же строки в JSON
| На экране | Значение |
|---|---|
| Искомая зона | Z |
| Найдено | 0 |
| Контрольная отправка | Q-01 |
Объяснение
Совпадений может не быть в исправной базе. В условие $items === [] приложение попадает после успешного выполнения запроса и чтения. Если ошибка возникла раньше, такой результат не получен.
Границы примера
Проверка известного номера здесь иллюстрирует разницу состояний. Для каждого пустого результата в рабочей системе дополнительное контрольное чтение обычно не нужно. Условие if ($cursor) проверяло бы PHP-объект, а не количество найденных документов.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-empty-zone', 'Показать диспетчеру пустой рейс', ['На экране', 'Значение'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
$wanted = 'Z';
$needle = $crypto->encrypt($wanted, $eq + ['queryType'=>E::QUERY_TYPE_EQUALITY]);
$items = $collection->find(['zone'=>$needle], ['projection'=>['_id'=>1], 'limit'=>5])->toArray();
$control = $collection->findOne(['_id'=>'Q-01'], ['projection'=>['_id'=>1,'zone'=>1]]);
$checks['successful_empty'] = $items === [];
$checks['control_document_exists'] = $control['_id'] === 'Q-01';
$checks['control_zone'] = $control['zone'] === 'N';
$checks['real_query_value'] = $needle instanceof Binary;
$message = $items === [] ? 'В зоне Z сейчас нет отправлений' : 'Найдены отправления зоны Z';
return [[['Искомая зона',$wanted],['Найдено',count($items)],['Контрольная отправка',$control['_id']]], $checks, $message, ''];
}
);
Ожидаемый результат
В зоне Z сейчас нет отправлений
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Сравните результат для N с результатом для Z. Не обращайтесь к первому элементу, пока не обработан случай пустой выдачи.
Отобрать отправления от 30 до 80 кг
Тележку выделили для посылок весом не меньше 30 и строго меньше 80 кг. Посылку ровно 80 кг отвезёт другая бригада. На схеме можно проверить судьбу каждой из шести коробок.
Исходные значения
У поля weight тип BSON int и возможность range. Домены min=0 и max=200 заданы при создании схемы и повторены в rangeOpts. Они не являются диапазоном сегодняшнего поиска.
- Границы сегодняшнего фильтра 30 и 80 → $gte и $lt
- Два сравнения одного поля под $and → encryptExpression()
- find() возвращает Q-02 (30), Q-03 (50), Q-04 (79)
Короткая запись
$expr = ['$and' => [
['weight' => ['$gte' => 30]],
['weight' => ['$lt' => 80]],
]];
$predicate = $crypto->encryptExpression($expr, $range + ['queryType' => E::QUERY_TYPE_RANGE]);
$cursor = $collection->find($predicate);Что покажет ведомость
От 30 включительно до 80 кг исключительно · Q-02, Q-03, Q-04
Посмотреть экран из реального запуска · Те же строки в JSON
| Отправление | Вес, кг |
|---|---|
| Q-02 | 30 |
| Q-03 | 50 |
| Q-04 | 79 |
Объяснение
Первая ветвь допускает значение, равное нижней границе. Вторая требует число меньше верхней. Граница запроса входит в выражение; rangeOpts описывает целый поддерживаемый домен индекса. Возвращённый объект — условие запроса, а не строка с расшифрованными данными.
Границы примера
В этой программе вес — целое число килограммов. Если предметная область требует дробных весов, заранее определите BSON-тип, точность и единицы измерения. Нельзя тихо округлять 79.9 до 79 и считать фильтр проверенным для исходных значений.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-weight-window', 'Отобрать отправления от 30 до 80 кг', ['Отправление', 'Вес, кг'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
require_once __DIR__.'/weight-view.php';
$min = 30;
$max = 80;
$predicate = weightPredicate($crypto, $range, $min, $max);
$rows = readWeights($collection, $predicate);
$checks['ids'] = array_column($rows,0) === ['Q-02','Q-03','Q-04'];
$checks['weights'] = array_column($rows,1) === [30,50,79];
$checks['contains_lower'] = in_array($min,array_column($rows,1),true);
$checks['excludes_upper'] = !in_array($max,array_column($rows,1),true);
$checks['predicate_object'] = is_object($predicate);
$inputs = array_map(static fn($entry) => [$entry[0],$entry[2]], $seed);
$html = weightWindowView($inputs, $rows, $min, $max);
return [$rows, $checks, 'От 30 включительно до 80 кг исключительно · '.implode(', ', array_column($rows,0)), $html];
}
);
Ожидаемый результат
От 30 включительно до 80 кг исключительно · Q-02, Q-03, Q-04
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Поставьте верхнюю границу 81. На экране появится также Q-05 с весом 80 кг. Для нового диапазона сравните подсветку входных коробок с таблицей результата.
Согласовать границы смены двух бригад
Если две бригады обе считают посылку 80 кг своей, её могут включить в две ведомости. Если обе исключат границу, отправление пропустят. Диспетчер сравнивает правила на реальных граничных значениях.
Исходные значения
$gt — строго больше, $gte — больше или равно; $lt и $lte аналогичны для верхней границы. Даже одностороннее условие для encryptExpression оформляем под верхним $and.
- Обе границы включены → 30, 50, 79, 80
- Обе исключены → 50, 79
- Только нижняя включённая 80 → 80, 90
Короткая запись
$heavy = ['$and' => [['weight' => ['$gte' => 80]]]];
$predicate = $crypto->encryptExpression($heavy, $range + ['queryType' => E::QUERY_TYPE_RANGE]);Что покажет ведомость
Закрытый интервал · 4 посылки, открытый · 2
Посмотреть экран из реального запуска · Те же строки в JSON
| Правило бригады | Выбранные веса, кг | Количество |
|---|---|---|
| От 30 до 80, обе границы включены | 30, 50, 79, 80 | 4 |
| Строго между 30 и 80 | 50, 79 | 2 |
| От 80 включительно | 80, 90 | 2 |
Объяснение
Заменился только оператор сравнения и набор границ. Схема индекса и ключ поля остались теми же. В отчёте значения приведены по стабильному открытому ID; это не сортировка зашифрованного веса.
Границы примера
Из результата этого набора не следует, что в другой коллекции между границами обязательно будет запись. На рабочих маршрутах закрепите единицы, нижнюю и верхнюю границы в одном соглашении для всех потребителей.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-edge-rules', 'Согласовать границы смены двух бригад', ['Правило бригады', 'Выбранные веса, кг', 'Количество'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
$tests = [
['От 30 до 80, обе границы включены', [['weight'=>['$gte'=>30]], ['weight'=>['$lte'=>80]]]],
['Строго между 30 и 80', [['weight'=>['$gt'=>30]], ['weight'=>['$lt'=>80]]]],
['От 80 включительно', [['weight'=>['$gte'=>80]]]],
];
$rows = [];
foreach ($tests as [$label,$conditions]) {
$predicate = $crypto->encryptExpression(['$and'=>$conditions], $range + ['queryType'=>E::QUERY_TYPE_RANGE]);
$hits = readWeights($collection,$predicate);
$rows[] = [$label, implode(', ',array_column($hits,1)), count($hits)];
}
$checks['closed_includes_both'] = $rows[0][1] === '30, 50, 79, 80';
$checks['open_excludes_both'] = $rows[1][1] === '50, 79';
$checks['single_edge'] = $rows[2][1] === '80, 90';
$checks['counts'] = array_column($rows,2) === [4,2,2];
return [$rows, $checks, 'Закрытый интервал · 4 посылки, открытый · 2', ''];
}
);
Ожидаемый результат
Закрытый интервал · 4 посылки, открытый · 2
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Составьте соседние правила [30, 80) и [80, 200]. Найдите Q-05 и проверьте, что она попала только во вторую ведомость.
Проверить поля веса до запроса
Диспетчер вводит вес в два поля. Нам нужно отличить ошибку ввода «80 / 30» от корректного диапазона с пустой выдачей, и не передавать в crypto массив из подменённого GET-параметра.
Исходные значения
Для примера формы принято правило: десятичная запись целого 0–200 без знака и ведущих нулей, нижняя граница строго меньше верхней. В PHP нормальные значения полей GET приходят строками, а запись min[]=30 дала бы массив.
- Проверка формы → «30» и «80» становятся целыми 30, 80
- Проверка порядка → строим только осмысленное условие [30, 80)
- Для остальных пяти входов показана ошибка, запрос диапазона не выполняется
Какие поля заполняет диспетчер
<form method="get">
<label>От, включительно <input name="min" type="number" min="0" max="200" step="1"></label>
<label>До, исключительно <input name="max" type="number" min="0" max="200" step="1"></label>
<button>Найти</button>
</form>Имена min и max становятся ключами массива $_GET. Даже input type="number" присылает текст; вручную составленный URL может передать и массив. В CLI-примере значение «—» на ошибочном вводе означает, что PHP остановился до поиска.
Короткая запись
$min = $parseBound($rawMin);
$max = $parseBound($rawMax);
if ($min !== null && $max !== null && $min < $max) {
$predicate = weightPredicate($crypto, $range, $min, $max);
$rows = readWeights($collection, $predicate);
}Что покажет ведомость
Из шести пар границ выполнен один поиск · найдено 3
Посмотреть экран из реального запуска · Те же строки в JSON
| Введено | Состояние формы | Совпадений |
|---|---|---|
| 30 / 80 | Готово | 3 |
| 80 / 30 | Верхняя граница должна быть больше нижней | — |
| 30 / 30 | Верхняя граница должна быть больше нижней | — |
| Дробь | Введите целые числа от 0 до 200 | — |
| Массив | Введите целые числа от 0 до 200 | — |
| За доменом | Введите целые числа от 0 до 200 | — |
Объяснение
Приведение (int) выполняется только после проверки типа, записи и допустимого диапазона. Поэтому "30.5" не превращается молча в 30, а число 0 не считается отсутствующим. Регулярное выражение в helper ограничивает длину ещё до преобразования. В этом уроке CLI подаёт те же формы значений, которые может получить обработчик.
Границы примера
Тире в колонке совпадений означает, что поиск не отправлялся. Если вход корректен и сервер вернул ноль строк, на экране будет числовой 0. Равные границы отклоняет правило именно этой формы с исключённым верхом; для закрытого интервала [30,30] смысл был бы другим.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-validate-bounds', 'Проверить поля веса до запроса', ['Введено', 'Состояние формы', 'Совпадений'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
$parseBound = static function (mixed $value): ?int {
if (!is_string($value) || !preg_match('/\A(?:0|[1-9][0-9]{0,2})\z/D', $value)) {
return null;
}
$number = (int)$value;
return $number <= 200 ? $number : null;
};
$rows = [];
$queries = 0;
foreach ([
['30 / 80','30','80'], ['80 / 30','80','30'], ['30 / 30','30','30'],
['Дробь','30.5','80'], ['Массив',['30'],'80'], ['За доменом','30','201'],
] as [$label,$rawMin,$rawMax]) {
$min = $parseBound($rawMin);
$max = $parseBound($rawMax);
if ($min === null || $max === null) {
$rows[] = [$label,'Введите целые числа от 0 до 200','—'];
continue;
}
if ($min >= $max) {
$rows[] = [$label,'Верхняя граница должна быть больше нижней','—'];
continue;
}
++$queries;
$hits = readWeights($collection, weightPredicate($crypto,$range,$min,$max));
$rows[] = [$label,'Готово',count($hits)];
}
$checks['only_valid_search_issued'] = $queries === 1;
$checks['three_found'] = $rows[0] === ['30 / 80','Готово',3];
$checks['reversed_and_empty_rejected'] = $rows[1][1] === $rows[2][1] && $rows[1][2] === '—';
$checks['three_invalid_shapes'] = $rows[3][1] === $rows[4][1] && $rows[4][1] === $rows[5][1] && $rows[5][2] === '—';
$checks['zero_is_valid_bound'] = $parseBound('0') === 0;
$checks['number_is_not_form_text'] = $parseBound(30) === null;
return [$rows, $checks, 'Из шести пар границ выполнен один поиск · найдено '.$rows[0][2], ''];
}
);
Ожидаемый результат
Из шести пар границ выполнен один поиск · найдено 3
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Добавьте пару "0" и "30". Пройдёт проверку и вернёт только Q-01 весом 20 кг. Сопоставьте этот ответ с ошибкой для строки "00".
Получить тот же диапазон в отчёте aggregate
Ведомость для печати строится через конвейер aggregate. Хотим отбросить неподходящие записи на сервере и проверить, что граничные правила ведомости совпадают с обычным экраном.
Исходные значения
В форме выражения агрегации оператор стоит снаружи, а аргументами идут ссылка на поле "$weight" и целое число. Верхний $and сохранён. Полученный объект помещают в $expr внутри этапа $match.
- Агрегатная форма $gte:["$weight",30] и $lt:["$weight",80] → криптографическое выражение
- Первый этап $match/$expr применяет условие → $project оставляет ID и вес
- Сортировка по открытому _id → Q-02, Q-03, Q-04
Короткая запись
$expr = ['$and' => [
['$gte' => ['$weight', 30]],
['$lt' => ['$weight', 80]],
]];
$encrypted = $crypto->encryptExpression($expr, $range + ['queryType' => E::QUERY_TYPE_RANGE]);
$pipeline = [['$match' => ['$expr' => $encrypted]], ['$project' => ['_id' => 1, 'weight' => 1]]];Что покажет ведомость
В отчёте aggregate и в find совпали 3 строки
Посмотреть экран из реального запуска · Те же строки в JSON
| Отправление | Вес для отчёта, кг |
|---|---|
| Q-02 | 30 |
| Q-03 | 50 |
| Q-04 | 79 |
Объяснение
Совпадение проверяется сравнением двух отдельно выполненных запросов к одной учебной коллекции. В find использовалась форма документного фильтра, в aggregate — форма сравнения двух операндов. Строка "$weight" внутри одинарных кавычек PHP попадает в MongoDB как путь к полю; это не имя PHP-переменной.
Границы примера
В этом отчёте проверены $match/$expr, $project, $sort по открытому полю и $limit. Дополнительные вычисления над закрытым весом, включая произвольную арифметику и суммирование в MongoDB, не следуют из поддержки этих сравнений.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-pipeline-range', 'Получить тот же диапазон в отчёте aggregate', ['Отправление', 'Вес для отчёта, кг'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
$expression = ['$and' => [
['$gte'=>['$weight',30]],
['$lt'=>['$weight',80]],
]];
$protected = $crypto->encryptExpression($expression, $range+['queryType'=>E::QUERY_TYPE_RANGE]);
$pipeline = [
['$match'=>['$expr'=>$protected]],
['$project'=>['_id'=>1, 'weight'=>1]],
['$sort'=>['_id'=>1]],
['$limit'=>10],
];
$rows = [];
foreach ($collection->aggregate($pipeline) as $doc) {
$rows[] = [$doc['_id'],$doc['weight']];
}
$fromFind = readWeights($collection,weightPredicate($crypto,$range,30,80));
$checks['same_actual_rows'] = $rows === $fromFind;
$checks['expected_ids'] = array_column($rows,0) === ['Q-02','Q-03','Q-04'];
$checks['expected_kg'] = array_column($rows,1) === [30,50,79];
$checks['upper_excluded'] = !in_array('Q-05',array_column($rows,0),true);
$checks['structured_condition'] = is_object($protected);
return [$rows, $checks, 'В отчёте aggregate и в find совпали '.count($rows).' строки', ''];
}
);
Ожидаемый результат
В отчёте aggregate и в find совпали 3 строки
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Для сверки замените обе верхние границы на включающие 80. Менять нужно и выдачу для экрана, и выражение отчёта. После этого в результаты добавится Q-05.
Показать две отправки рейса и продолжить список
На планшете нужны две отправки выбранной зоны, а после кнопки «Дальше» — оставшиеся. Полная коллекция, полный документ и повторная сортировка в PHP экрану не требуются.
Исходные значения
В этой учебной зоне S ровно три посылки, так что у первой страницы есть последняя строка. Предикат зоны зашифрован для equality; номер отправления открыт и уникален. Имена Q-01…Q-06 имеют одинаковую длину и дают ожидаемый строковый порядок.
- Сервер отбирает S, сортирует по _id и ограничивает результат → Q-02, Q-04
- Последний номер Q-04 идёт в следующее условие _id > Q-04
- Вторая страница → Q-06; PHP видит только _id и расшифрованный вес
Короткая запись
$options = ['projection' => ['_id'=>1, 'weight'=>1], 'sort' => ['_id'=>1], 'limit'=>2];
$first = $collection->find(['zone'=>$needle], $options)->toArray();
$last = $first[array_key_last($first)]['_id'];
$next = $collection->find(['zone'=>$needle, '_id'=>['$gt'=>$last]], $options);Что покажет ведомость
Южный рейс · первая страница Q-02 и Q-04 · следующая Q-06
Посмотреть экран из реального запуска · Те же строки в JSON
| Страница | Отправление | Вес, кг |
|---|---|---|
| 1 | Q-02 | 30 |
| 1 | Q-04 | 79 |
| 2 | Q-06 | 90 |
Объяснение
Положение между страницами привязано к открытому номеру, который можно сравнивать и сортировать обычным образом. Оно не зависит от байтов зашифрованных zone или weight. Шаблон получает ровно два поля. Вычисление массы по зашифрованному содержимому на сервере здесь не используется.
Границы примера
В реальной форме перед чтением последнего элемента обработайте пустую первую страницу. Между двумя запросами содержимое живой базы может измениться. Видимость номера и порядка выдачи является частью модели данных и должна соответствовать полномочиям пользователя.
Полная программа и ZIP
<?php
declare(strict_types=1);
require __DIR__.'/query-lab.php';
use MongoDB\Collection;
use MongoDB\BSON\Binary;
use MongoDB\Driver\ClientEncryption as E;
runQueryExercise('mongodb133-qes-dispatch-screen', 'Показать две отправки рейса и продолжить список', ['Страница', 'Отправление', 'Вес, кг'],
function (Collection $collection, Collection $plain, E $crypto, array $eq, array $range, array $seed): array {
$needle = $crypto->encrypt('S', $eq+['queryType'=>E::QUERY_TYPE_EQUALITY]);
$filter = ['zone'=>$needle];
$options = ['projection'=>['_id'=>1,'weight'=>1],'sort'=>['_id'=>1],'limit'=>2];
$page1 = $collection->find($filter,$options)->toArray();
$lastNumber = $page1[array_key_last($page1)]['_id'];
$page2 = $collection->find($filter+['_id'=>['$gt'=>$lastNumber]],$options)->toArray();
$rows = [];
foreach ([1=>$page1,2=>$page2] as $page=>$items) {
foreach ($items as $doc) {
$keys = array_keys($doc);
sort($keys);
$checks['only_requested_columns'] = ($checks['only_requested_columns'] ?? true) && $keys === ['_id','weight'];
$rows[] = [$page,$doc['_id'],$doc['weight']];
}
}
$checks['first_page'] = array_column($page1,'_id') === ['Q-02','Q-04'];
$checks['second_page'] = array_column($page2,'_id') === ['Q-06'];
$checks['partial_last_page'] = count($page1)===2 && count($page2)===1;
$checks['ordered_unique_ids'] = array_column($rows,1)===['Q-02','Q-04','Q-06'];
$checks['correct_decrypted_weights'] = array_column($rows,2)===[30,79,90];
return [$rows,$checks,'Южный рейс · первая страница Q-02 и Q-04 · следующая '.$page2[0]['_id'],''];
}
);
Ожидаемый результат
Южный рейс · первая страница Q-02 и Q-04 · следующая Q-06
Одна самостоятельная программа создаёт учебные данные и выполняет запрос. Вспомогательный файл query-lab.php и README.txt находятся в том же архиве.
Самостоятельное изменение
Измените размер страницы на 1. Вторая страница начнётся после Q-02 и покажет Q-04. Для Q-06 понадобится ещё один запрос с новым последним номером.
Пользователю нужна понятная сортировка выдачи. Достаточно ли включить шифрование или конфигурацию диапазона? Фильтровать закрытый вес поддерживаемым диапазоном, а ограниченную страницу упорядочить по открытому уникальному идентификатору. Постановка вопроса на Stack Overflow.
От диапазона к готовому обработчику
Разобрать договор метода по параметрам можно в карточке encryptExpression(). Если результат отличается от ожидаемого, сравните настройки полей коллекции, обе числовые границы и точные операторы в форме проверки включения границ. Преобразование курсора в HTML-экран подробнее разобрано в примерах очереди заказов.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
MongoDB\Driver\ClientEncryption::encryptExpressionПостроить зашифрованное условие диапазона для find или выражения агрегации.
ext-mongodb 1.16.0; текущий Range на стенде ext-mongodb 2.5.5 и MongoDB 8.0.34 · mongodb
final public function MongoDB\Driver\ClientEncryption::encryptExpression(array|object $expr, ?array $options = null): objectЧто передавать в $expr
В первом аргументе передайте документ PHP (массив или объект), который описывает поддерживаемое сравнение одного поля диапазонного индекса. Выберите верхний $and и хотя бы одну границу с $gt, $gte, $lt или $lte. Для двух границ используйте одно нижнее и одно верхнее условие. Имя должно совпасть с реальным именем зашифрованного поля в коллекции. Правая граница должна иметь подходящий BSON-тип.
| Где используется | Одна ветвь в верхнем $and |
|---|---|
| find() или документ фильтра стадии $match | ['weight' => ['$gte' => 30]] — слева имя поля, в нём условие нижней границы. |
| Выражение агрегации, например внутри $match/$expr | ['$gte' => ['$weight', 30]] — ключом является оператор, первым операндом ссылка на поле. |
Как выбрать $options
Сигнатура допускает null, но для построения работающего range-предиката нужны настройки. Передайте algorithm=ALGORITHM_RANGE и queryType=QUERY_TYPE_RANGE. Укажите ровно один способ выбора ключа: keyId как Binary UUID подтипа 4 либо keyAltName с его строковым именем. Во всех рабочих примерах передан keyId того DEK, которым зашифрован weight. contentionFactor и rangeOpts согласованы с настройками индекса. В лаборатории contentionFactor=0 и rangeOpts содержит min=0, max=200 для BSON int.
min и max задают предметный домен поля и имеют тот же BSON-тип. Для double или decimal в общем формате rangeOpts параметры min/max/precision согласуют совместно. precision определяет число учитываемых знаков; sparsity и trimFactor влияют на работу диапазонного индекса и стоимость записей и чтений. В этой главе они оставлены по умолчанию. PHP Manual отдельно указывает ограничение поддержки диапазонного поиска Decimal128 расширением. Здесь выполнены примеры именно для int32.
Что возвращается и как выполнить
Результат — объект с подготовленным выражением. Он помещается в find() либо в нужное место pipeline. Вызов encryptExpression сам не читает отправления. Настроенный Manager должен иметь опцию autoEncryption. При ручном построении предикатов bypassQueryAnalysis=true допустим, а bypassAutoEncryption должен оставаться false. Служебные данные результата нельзя принимать за конечные подходящие документы.
Границы, ошибки и версии
Ошибке разбора аргументов может соответствовать InvalidArgumentException, отказу при построении зашифрованного выражения — EncryptionException. Ошибка самого запроса к базе относится к следующему шагу. Ввод формы проверяйте до криптографических и сетевых вызовов. Успешная пустая выдача — отдельный исход. Для точного равенства по полю equality используется encrypt() с QUERY_TYPE_EQUALITY. Этот метод строит выражение диапазона.
Доступен с ext-mongodb 1.16.0. В 1.20 добавлен параметр trimFactor и sparsity стала необязательной. Для данного стека MongoDB 8.0 используется действующий алгоритм Range. Старый экспериментальный rangePreview требует отдельной миграции. Проверяемая связка — ext-mongodb 2.5.5 и MongoDB Community 8.0.34.
Ведомость по весу
$match = ['$and'=>[['weight'=>['$gte'=>30]], ['weight'=>['$lt'=>80]]]];
$filter = $crypto->encryptExpression($match, $range + ['queryType'=>E::QUERY_TYPE_RANGE]);
$cursor = $collection->find($filter);Для шести учебных отправок [30,80) оставляет 30, 50 и 79 кг. Открыть графическую ведомость и полный пример.
Тот же критерий внутри агрегатного отчёта
$source = ['$and'=>[['$gte'=>['$weight',30]], ['$lt'=>['$weight',80]]]];
$condition = $crypto->encryptExpression($source, $range + ['queryType'=>E::QUERY_TYPE_RANGE]);
$pipeline = [['$match'=>['$expr'=>$condition]], ['$project'=>['_id'=>1,'weight'=>1]]];Здесь выражение использует ссылку $weight внутри массива операндов. Три возвращённые строки сверяются с независимым find. Открыть полный отчёт aggregate.
См. также Настройка коллекции и карты полей, encrypt() для одного значения и одна или две границы.
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


