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

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

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

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

Глава 169 · После первой коллекции; вход и структура запроса разобраны по шагам

MongoDB: входные данные, управляемые запросы и BSON Javascript

От поля формы к проверенному фильтру: типы, доступ, разрешённые изменения, ограниченная выдача, расчёты и просмотр BSON-кода без исполнения.

От поля формы к управляемому запросу

Покупатель вводит артикул, каталог показывает нужный товар, сотрудник меняет разрешённый заголовок. Во всех трёх задачах человек передаёт значения, а приложение решает, какое условие поиска или изменения составить. Внешний запрос не должен становиться готовой командой базы. Сначала построим эту границу на одном поле, затем добавим доступ, сортировку, расчёты и просмотр старого BSON Javascript как данных.

$sku = 'BOX-1';
$filter = ['sku' => $sku];

Первая строка сохраняет текст в переменную. Во второй PHP-массив описывает условие: ключ sku — поле документа, значение BOX-1 — искомый артикул. Знак => соединяет ключ и значение массива. Этот массив ещё не запрос: ниже его передают MongoDB\Driver\Query и executeQuery. При правильном вводе база вернёт Органайзер; при массиве вместо строки программа остановится до поиска.

Кто управляет запросом
  1. Человек выбирает значение BOX-1
  2. PHP проверяет строку, затем задаёт поле sku
  3. MongoDB исполняет созданный приложением фильтр
  4. Проекция возвращает только нужные поля; PHP выводит их как текст

Если названия ещё незнакомы: переменная, ключи и значения массива, HTML-форма и GET/POST, первая коллекция и Query. Здесь повторяем необходимые шаги; полные программы дальше включают helper и инструкцию запуска.

MongoDB Security: три отдельные границы приложения

Официальная ветка MongoDB Security разделяет внедрение структуры запроса и внедрение сценария. В первом случае вместо ожидаемого значения появляется оператор или другая форма данных. Во втором внешнее значение встраивают в текст исполняемой программы. У MongoDB нет обычной SQL-строки, но это не отменяет необходимость отделять данные от управляющей структуры.

ГраницаЧто разрешеноЧто выбирает программаПример результата
Ввод SKUОдна строка 1..32 байтПоле sku, equality-фильтрBOX-1 → Органайзер
Изменение карточкиПоле title со строкойФильтр владельца и $setМеняется title, role остаётся reader
КаталогИмя разрешённой сортировки и число строкПроекция, sort и предел limitДва нужных товара
Старый JavascriptЧтение известного BSON-значенияТолько getter и текстовый отчётКод и scope видны, но не выполнены

Проверка типа, проверка прав и ограничение нагрузки решают разные задачи. Даже строковый ObjectId не даёт права читать чужой документ. Даже безопасно составленный фильтр может быть дорогим без индекса. Даже корректно прочитанный код нельзя выводить исполняемым HTML. Это глава о построении прикладных запросов; настройка сетевого доступа, учётных записей MongoDB, TLS и резервного копирования остаётся отдельной частью эксплуатации.

Официальная ветка MongoDB Security; дальнейшие примеры раскрывают обе её темы с современными операторами.

Request injection: массив вместо строки из формы

У обычного поля <input name="sku"> PHP обычно получает строку. Но имя со скобками позволяет передать массив: sku[$ne]=unused после разбора даёт ключ sku, внутри которого лежит оператор $ne. Если без проверки поместить этот массив в условие поля sku, его структура станет частью запроса. Поэтому проверяем форму значения до конструктора Query; набор операторов остаётся в коде приложения.

<form method="get" action="catalog.php">
  <label for="sku">Артикул</label>
  <input id="sku" name="sku" value="BOX-1">
  <button type="submit">Найти товар</button>
</form>

Это HTML формы для гипотетического обработчика catalog.php: поле name связывает ввод со $_GET["sku"]. В URL получится catalog.php?sku=BOX-1. Сам обработчик здесь не развёрнут; ниже воспроизводим разбор той же query string в CLI и выполняем настоящие запросы базы. Для POST поля формы аналогично попадают в $_POST; выбор метода сам по себе не запрещает подменить тип.

parse_str('sku=BOX-1', $input);
$sku = $input['sku']; // строка BOX-1

parse_str('sku%5B%24ne%5D=unused', $input);
// $input['sku'] теперь массив ['$ne' => 'unused'], а не строка.
Проверка стоит между разбором и запросом
  1. name=sku → $_GET[sku] либо воспроизводимый parse_str
  2. is_string + договор о значении → разрешённая строка
  3. Программа сама создаёт [sku => строка]
  4. Массив/пустое значение → понятный отказ; Query не создан

Та же проблема относится не только к find, но и к фильтрам update, upsert, delete и findAndModify. Не передавайте весь внешний JSON как filter, update или pipeline. Разрешённые имена полей и операторов задают явно. Если нужна буквальная строка в поиске по шаблону, отдельно учитывайте правила regex; HTML-экранирование не меняет смысл оператора MongoDB.

Контракт PHP Manual: request injection. Ниже первый пример показывает обычный поиск, два массива, пустую строку и корректную строку без совпадений.

Script injection: внешние значения и исполняемый код

Если склеивать текст JavaScript с вводом пользователя, кавычки и другие символы могут менять саму программу. Основной принцип официальной страницы — передавать значения отдельно от текста кода. Её примеры относятся к историческим API: серверная команда eval удалена в MongoDB4.2 после устаревания в 3.0. Их нельзя просто копировать как современный рабочий обработчик.

Для текущей разработки сначала выбирайте обычный запрос или aggregation. Цена × количество выражается через $multiply; выбор нужных документов — через $match и стандартные предикаты. $expr сам по себе не запускает JavaScript. В MongoDB8.0 server-side операции $where, $function и $accumulator объявлены устаревшими; mapReduce устарел раньше, в 5.0. Наши программы ни одну из этих операций не исполняют.

Заменить исполняемый текст явной операцией
  1. Задача: стоимость позиции заказа
  2. Разрешённые поля price и qty + проверенные числовые данные
  3. Стадия $project: line_total = $multiply(price,qty)
  4. База возвращает BOX 5000 / TRAY 3600; текст JS не нужен

BSON Javascript — формат значения, который может встретиться в старых документах. Constructor и getter не исполняют его. Scope хранит отдельный документ параметров, но это не универсальное разрешение безопасного исполнения: code-with-scope (BSON type15) устарел, современный $where больше его не принимает; использование такого типа для $where устарело с 4.2.1. Поэтому в конце главы только инвентаризируем и переносим данные старого формата.

Источники ограничений: PHP Manual о script injection, MongoDB8.0: server-side JavaScript, $where и code-with-scope, разъяснение MongoDB об удалении eval.

Запустить программы и увидеть таблицы

Десять архивов — самостоятельные CLI-программы с настоящими чтением/записью в отдельной MongoDB. Нужны PHP CLI8.4+ x64, ext-mongodb2.5+, MongoDB8.0+; среда проверки PHP8.5.11/mongodb2.5.5/MongoDB8.0.34. Composer не требуется. В ZIP находятся example.php, mq128-helper.php и README с включением расширения, отдельным mongod и запуском. Поддерживающий helper вынесен из коротких учебных фрагментов, но полностью доступен в архиве.

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

Откройте полученный result.html: таблица построена из результата программы, а не изображения кода. result.json содержит те же строки, metrics.json — версии, проверки и промежуточные значения. Каждая программа создаёт свою случайную коллекцию, ограничивает выдачу, затем удаляет именно её в finally и подтверждает отсутствие. После принудительного завершения процесса finally может не выполниться. Это учебный loopback-стенд; его настройки без аутентификации не являются конфигурацией публичного сервера.

Принять строку из формы и отклонить массив вместо неё

В каталоге человек вводит артикул BOX-1 и должен получить один нужный товар. HTML-поле предполагает текст, но HTTP-запрос можно составить без этой формы. Поэтому сервер сначала проверяет, что под именем sku действительно пришла строка, и только потом строит условие поиска.

Входные данные и понятия

У формы ниже input name="sku". При method="get" браузер помещает значение в query string: ?sku=BOX-1. PHP заполняет $_GET["sku"]. Имя sku[] или sku[$ne] превращает это значение в массив; %5B и %5D — закодированные квадратные скобки, %24 — знак доллара. В программе parse_str разбирает ровно такие строки в $input, чтобы видеть все пять случаев при CLI-запуске.

Путь данных в этом примере
  1. Поле name=sku → query string sku=BOX-1
  2. parse_str → $input[sku] = строка BOX-1
  3. Проверка типа и длины → программа строит [sku => BOX-1]
  4. MongoDB возвращает sku/title → таблица «Органайзер»; массивы отвергнуты до запроса

Минимальная запись

$sku = $input['sku'] ?? null;
if (!is_string($sku) || $sku === '') { throw new InvalidArgumentException('Нужна строка SKU'); }
$filter = ['sku' => $sku]; // Имя поля выбирает программа; строка — значение.

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

Результат

Query stringТип после разбораРезультат
sku=BOX-1stringОрганайзер
sku%5B%24ne%5D=unusedarrayОтказ до запроса
sku%5B%5D=BOX-1arrayОтказ до запроса
sku=stringОтказ до запроса
sku=BOX-9stringНе найдено

Почему получился такой результат

Нормальная строка BOX-1 находит Органайзер. BOX-9 тоже корректна как ввод, но записи нет: это отдельный исход. Два массива и пустая строка дают отказ до обращения к коллекции. Конструкция ?? null означает «взять значение, а при его отсутствии/null — null»; is_string проверяет тип без приведения. Полный mq128Text дополнительно ограничивает длину 1..32 байт и запрещает NUL. foreach перебирает фиксированные запросы, try/catch превращает только ожидаемый отказ валидации в строку результата.

Границы и типичные ошибки

Проверена обработка query string через parse_str в CLI и реальные запросы MongoDB, а не HTTP-отправка формы. Обработчик формы должен передать $_GET в ту же проверку и сформировать понятный HTTP-ответ при отказе. Не исправляйте массив приведением (string): это не пользовательский артикул. Проверка HTML-поля на клиенте помогает человеку, но не заменяет серверный контракт. Поиск подстроки — другая операция: строку нельзя молча объявлять регулярным выражением.

Полная самостоятельная программа и архив
Принять строку из формы и отклонить массив вместо неёСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
mq128Insert($manager,$namespace,[['_id'=>1,'sku'=>'BOX-1','title'=>'Органайзер'],['_id'=>2,'sku'=>'BOX-2','title'=>'Контейнер']]);
    $cases=['sku=BOX-1','sku%5B%24ne%5D=unused','sku%5B%5D=BOX-1','sku=','sku=BOX-9'];$rows=[];$accepted=0;$rejected=0;
    foreach($cases as $query){parse_str($query,$input);try{$sku=mq128Text($input['sku']??null,1,32);$accepted++;
        $found=mq128Find($manager,$namespace,['sku'=>$sku],['_id'=>0,'sku'=>1,'title'=>1]);
        $rows[]=[$query,get_debug_type($input['sku']??null),$found===[]?'Не найдено':$found[0]['title']];
    }catch(InvalidArgumentException){$rejected++;$rows[]=[$query,get_debug_type($input['sku']??null),'Отказ до запроса'];}}
    $checks=['accepted_two'=>$accepted===2,'rejected_three'=>$rejected===3,'normal_match'=>$rows[0][2]==='Органайзер','unknown_is_empty'=>$rows[4][2]==='Не найдено'];
    $details=['accepted_queries'=>$accepted,'rejected_before_database'=>$rejected];return $rows;
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-scalar-contract',['Query string', 'Тип после разбора', 'Результат'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-scalar-contract: OK
';
Ожидаемый результат
mongodb128-safe-scalar-contract: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Добавьте sku=0. Это непустая строка и допустимый артикул по текущим правилам, поэтому ожидается «Не найдено», а не отказ. Если бизнес разрешает только определённый формат, введите это отдельным правилом и объясните его пользователю.

Вопрос из практики: preparated statements with mongodb. Автор передаёт GET-параметры в Query и ищет аналог prepared statements. Обсуждение показывает путаницу между отсутствием SQL-строки и отсутствием инъекций. Это источник постановки проблемы; приведённый здесь код и решение написаны заново по актуальным официальным контрактам.

Найти документ по ID только внутри доступного аккаунта

В личном кабинете ссылка открывает заказ по ID. Одной проверки 24 hex-символов недостаточно: ID чужого заказа тоже может быть совершенно правильным. Запрос должен одновременно искать нужный документ и ограничивать его доступным аккаунтом.

Входные данные и понятия

Два документа принадлежат shop-a и shop-b. $trustedTenant="shop-a" — учебная замена уже проверенного серверного контекста. Это не реализованный вход в систему. Первый запрос передаёт свой ObjectId, второй — чужой и поддельное поле tenant_id, третий пытается передать массив вместо ID.

Путь данных в этом примере
  1. Внешний id → строка ровно 24 hex-символа
  2. ObjectId constructor → BSON-идентификатор
  3. AND в одном фильтре: _id + доверенный tenant_id=shop-a
  4. Найден свой заказ; чужой скрыт; массив отвергнут до поиска

Минимальная запись

$id = mq128ObjectId($input['id'] ?? null);
$filter = ['_id' => $id, 'tenant_id' => $trustedTenant];
// $trustedTenant получен из проверенного серверного контекста, не из формы.

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

Результат

ЗапросРезультат для shop-a
1Заказ A
2Нет доступной записи
3Неверный формат ID

Почему получился такой результат

Два условия одного фильтра должны выполняться одновременно. Поле tenant_id из внешнего массива вообще не участвует в построении фильтра. Поэтому второй запрос не открывает Заказ B. Ответ «Нет доступной записи» объединяет отсутствие и недоступность; он не подтверждает пользователю существование чужого объекта. Преобразование строки в ObjectId меняет BSON-тип сравнения, но не выдаёт право доступа.

Границы и типичные ошибки

Настоящее приложение отдельно проверяет сессию/токен и получает доверенный tenant_id на сервере. Здесь этого HTTP-процесса нет. При чтении, обновлении и удалении сохраняйте ограничение доступа в самом запросе; проверка только во время показа кнопки не помогает другому запросу. При изменениях роли/членства учитывайте выбранную модель доступа и актуальность серверного контекста.

Полная самостоятельная программа и архив
Найти документ по ID только внутри доступного аккаунтаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
$a='659200800000000000000001';$b='659200800000000000000002';
    mq128Insert($manager,$namespace,[['_id'=>new MongoDB\BSON\ObjectId($a),'tenant_id'=>'shop-a','title'=>'Заказ A'],['_id'=>new MongoDB\BSON\ObjectId($b),'tenant_id'=>'shop-b','title'=>'Заказ B']]);
    $trustedTenant='shop-a';$cases=[['id'=>$a],['id'=>$b,'tenant_id'=>'shop-b'],['id'=>['$ne'=>null]]];$rows=[];$blocked=0;
    foreach($cases as $i=>$input){try{$id=mq128ObjectId($input['id']??null);
        $found=mq128Find($manager,$namespace,['_id'=>$id,'tenant_id'=>$trustedTenant],['_id'=>0,'title'=>1]);
        $rows[]=[$i+1,$found===[]?'Нет доступной записи':$found[0]['title']];
    }catch(InvalidArgumentException){$blocked++;$rows[]=[$i+1,'Неверный формат ID'];}}
    $checks=['own_document'=>$rows[0][1]==='Заказ A','foreign_document_not_returned'=>$rows[1][1]==='Нет доступной записи','array_rejected'=>$blocked===1];
    $details=['trusted_tenant'=>$trustedTenant,'external_tenant_field_used'=>false];return $rows;
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-owned-id',['Запрос', 'Результат для shop-a'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-owned-id: OK
';
Ожидаемый результат
mongodb128-safe-owned-id: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Поменяйте доверенный контекст на shop-b и ожидаемые ответы: первый ID перестанет быть доступным, второй станет доступен. Поле tenant_id из формы по-прежнему не должно выбирать контекст.

Изменить только разрешённое поле без массового присваивания

Форма редактирования позволяет изменить заголовок карточки. Если передать весь POST-массив в обновление, туда могут попасть поля, которых на форме вообще нет. Покажем запись нового title и одновременно убедимся, что role и чужая карточка не изменились.

Входные данные и понятия

Разрешено только поле title. В коллекции свои и чужие данные, role=reader. Первый вход содержит новый текст; второй дополнительно role=admin; третий — массив вместо title. Это искусственные входные массивы: реальный POST проходит тот же договор о полях, а проверка отправителя формы выполняется отдельно.

Путь данных в этом примере
  1. Входной массив → проверить список имён полей
  2. title → проверить строку и 1..80 байт
  3. Собрать собственный $set для title + фильтр владельца
  4. Выполнить одно update, затем прочитать сохранённые title/role

Минимальная запись

$allowed = ['title'];
if (array_diff(array_keys($input), $allowed) !== []) { throw new InvalidArgumentException('Лишние поля'); }
$update = ['$set' => ['title' => mq128Text($input['title'] ?? null, 1, 80)]];
// $set и title принадлежат коду; вход не становится готовым документом обновления.

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

Результат

СлучайРезультат
1Сохранено
2Отказ до записи
3Отказ до записи
ИтогНовый заголовок / reader

Почему получился такой результат

array_keys получает имена входных полей, array_diff обнаруживает имена вне списка title. $set здесь — оператор обновления MongoDB, а не PHP-присваивание и не одноимённая стадия aggregation. Он изменяет только перечисленные поля. multi=false запрещает массовую запись, upsert=false не создаёт новый документ при промахе. getMatchedCount проверяет, что нужная существующая запись найдена; итоговая таблица показывает Новый заголовок / reader.

Границы и типичные ошибки

Предотвращение массового присваивания и проверка доступа — разные обязанности, поэтому присутствуют обе. HTTP-обработчику изменения также нужны аутентификация и подходящая защита от CSRF; CLI-пример их не заявляет. Ограничение title задано в байтах, поскольку strlen считает байты. Для пользовательского ограничения в символах выберите mb_strlen и явную кодировку, а затем отдельно ограничьте объём хранения.

Полная самостоятельная программа и архив
Изменить только разрешённое поле без массового присваиванияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
mq128Insert($manager,$namespace,[['_id'=>1,'tenant_id'=>'shop-a','title'=>'Черновик','role'=>'reader'],['_id'=>2,'tenant_id'=>'shop-b','title'=>'Чужой','role'=>'reader']]);
    $cases=[['title'=>'Новый заголовок'],['title'=>'Подмена','role'=>'admin'],['title'=>['$ne'=>null]]];$rows=[];$updates=0;$rejected=0;
    foreach($cases as $i=>$input){try{
        if(array_diff(array_keys($input),['title'])!==[]){throw new InvalidArgumentException('unknown_fields');}
        $title=mq128Text($input['title']??null,1,80);$bulk=new MongoDB\Driver\BulkWrite;
        $bulk->update(['_id'=>1,'tenant_id'=>'shop-a'],['$set'=>['title'=>$title]],['multi'=>false,'upsert'=>false]);
        $result=$manager->executeBulkWrite($namespace,$bulk,['writeConcern'=>new MongoDB\Driver\WriteConcern(1,3000)]);
        if($result->getMatchedCount()!==1){throw new RuntimeException('Expected owned document');}$updates++;$rows[]=[$i+1,'Сохранено'];
    }catch(InvalidArgumentException){$rejected++;$rows[]=[$i+1,'Отказ до записи'];}}
    $saved=mq128Find($manager,$namespace,[],['_id'=>1,'title'=>1,'role'=>1]);
    $checks=['one_update'=>$updates===1,'two_rejections'=>$rejected===2,'title_updated'=>$saved[0]['title']==='Новый заголовок','role_unchanged'=>$saved[0]['role']==='reader','foreign_unchanged'=>$saved[1]['title']==='Чужой'];
    $details=['stored_documents'=>$saved];$rows[]=['Итог',$saved[0]['title'].' / '.$saved[0]['role']];return $rows;
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-allowed-update',['Случай', 'Результат'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-allowed-update: OK
';
Ожидаемый результат
mongodb128-safe-allowed-update: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Разрешите поле description как вторую строку с собственным пределом длины. Явно добавьте только его в $set; вход role должен по-прежнему отвергаться. Проверьте, что отсутствие description не стирает старое значение, если договор формы этого не требует.

Вопрос из практики: MongoDB injection in PHP and HTML. Вопрос о лишних POST-полях и массиве вместо ожидаемой строки показывает, почему форма не является серверным договором о данных. Это источник постановки проблемы; приведённый здесь код и решение написаны заново по актуальным официальным контрактам.

Разрешить выбор сортировки и вернуть только нужную страницу

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

Входные данные и понятия

Четыре товара: три активных по цене 400, 100 и 250, один архивный за 50. Вход sort=price_asc и limit="2". Карта сортировок содержит только price_asc и price_desc; проекция отдаёт title/price, скрывая _id и внутренние поля. Число строк ограничено диапазоном 1..20.

Путь данных в этом примере
  1. Имя price_asc → разрешённый sort [price:1, _id:1]
  2. Строка limit=2 → проверенное целое 2
  3. MongoDB: active=true → sort → projection → limit
  4. PHP получает только Лоток 100 и Коробка 250

Минимальная запись

$sorts = ['price_asc' => ['price' => 1, '_id' => 1]];
$sort = $sorts[$input['sort']]; // До обращения проверены строковый тип и разрешённый ключ.
$options = ['sort' => $sort, 'projection' => ['_id'=>0, 'title'=>1, 'price'=>1], 'limit' => 2];

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

Результат

ТоварЦена, коп.
Лоток100
Коробка250

Почему получился такой результат

В options sort выбирается через карту, а не принимается целым документом от клиента. Второй ключ _id делает порядок устойчивым при одинаковой цене. limit ограничивает число возвращённых документов; batchSize управляет размером порций курсора и не заменяет limit. _id=0 — допустимое исключение из включающей проекции. Отдельно отвергнуты 0, 21, массив и строка 2x; $natural не является разрешённым именем сортировки.

Границы и типичные ошибки

Маленький limit не гарантирует маленькую стоимость поиска: для большой коллекции нужны подходящий индекс и план запроса. maxTimeMS ограничивает серверное выполнение, но не подменяет индексы или политику нагрузки. Здесь денежные целые — учебные значения без конвертации валют. Следующая страница требует явного правила пагинации; порядок _id сам по себе не является курсором.

Полная самостоятельная программа и архив
Разрешить выбор сортировки и вернуть только нужную страницуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
mq128Insert($manager,$namespace,[['_id'=>1,'title'=>'Ящик','price'=>400,'active'=>true,'internal'=>'x'],['_id'=>2,'title'=>'Лоток','price'=>100,'active'=>true,'internal'=>'y'],['_id'=>3,'title'=>'Коробка','price'=>250,'active'=>true,'internal'=>'z'],['_id'=>4,'title'=>'Архив','price'=>50,'active'=>false,'internal'=>'q']]);
    $sorts=['price_asc'=>['price'=>1,'_id'=>1],'price_desc'=>['price'=>-1,'_id'=>1]];
    $input=['sort'=>'price_asc','limit'=>'2'];$sortKey=mq128Text($input['sort']??null,1,20);
    if(!array_key_exists($sortKey,$sorts)){throw new InvalidArgumentException('sort');}
    $limit=mq128Limit($input['limit']??null);
    $cursor=$manager->executeQuery($namespace,new MongoDB\Driver\Query(['active'=>true],['sort'=>$sorts[$sortKey],'projection'=>['_id'=>0,'title'=>1,'price'=>1],'limit'=>$limit,'batchSize'=>$limit+1,'maxTimeMS'=>1500]));
    $cursor->setTypeMap(['root'=>'array','document'=>'array','array'=>'array']);$found=$cursor->toArray();
    $badLimits=0;foreach(['0','21',['$gt'=>0],'2x'] as $bad){try{mq128Limit($bad);}catch(InvalidArgumentException){$badLimits++;}}
    $checks=['only_two_rows'=>count($found)===2,'database_sort'=>array_column($found,'price')===[100,250],'private_field_absent'=>!array_key_exists('internal',$found[0]),'invalid_limits_rejected'=>$badLimits===4,'sort_key_whitelist'=>!array_key_exists('$natural',$sorts)];
    $details=['approved_sort'=>$sorts[$sortKey],'projection'=>['title','price'],'limit'=>$limit];return array_map(fn($d)=>[$d['title'],$d['price']],$found);
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-bounded-catalog',['Товар', 'Цена, коп.'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-bounded-catalog: OK
';
Ожидаемый результат
mongodb128-safe-bounded-catalog: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Добавьте разрешённую сортировку title_asc с _id как дополнительным ключом. Не позволяйте клиенту выбрать произвольное поле. Для реального большого каталога сопоставьте фильтр и сортировку с составным индексом из главы об индексах.

Сохранить знак доллара как текст внутри агрегации

Экран редактора должен показать подпись, которую ввёл пользователь. Подпись "$cost" — обычный текст. Но внутри выражения aggregation строка с начальным $ имеет специальный смысл: она обращается к полю документа. Сравним оба результата рядом.

Входные данные и понятия

В документе title="Этикетка", cost=700. Пользовательская строка равна "$cost". В стадии $project поле field_value намеренно читает "$cost", а literal_text получает ту же строку через оператор $literal.

Путь данных в этом примере
  1. Документ cost=700 + строковый ввод $cost
  2. Выражение $cost → прочитать поле → 700
  3. Выражение {$literal: $cost} → сохранить текст → $cost
  4. Проекция одного документа → таблица Этикетка / 700 / $cost

Минимальная запись

$project = ['field_value' => '$cost', 'literal_text' => ['$literal' => $input]];
// В выражении '$cost' — путь к полю; $literal сохраняет именно текст '$cost'.

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

Результат

ТоварСсылка на поле $costТекст пользователя
Этикетка700$cost

Почему получился такой результат

Здесь два языка обозначений: PHP $input — переменная программы, а строка '$cost' внутри pipeline — ссылка MongoDB на поле. Одинарные кавычки PHP не подставляют переменную; MongoDB интерпретирует полученную строку позже в контексте выражения. $literal возвращает значение без такого вычисления. Имена стадий, полей проекции и весь pipeline всё равно формирует программа.

Границы и типичные ошибки

$literal решает конкретную задачу контекста aggregation-expression. Это не универсальная очистка запроса, не HTML-экранирование и не замена проверки входного типа. В обычном equality-фильтре строковое значение "$cost" уже сравнивается как строка. Не удаляйте знак доллара из пользовательских данных: можно изменить нужную человеку подпись.

Полная самостоятельная программа и архив
Сохранить знак доллара как текст внутри агрегацииСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
mq128Insert($manager,$namespace,[['_id'=>1,'title'=>'Этикетка','cost'=>700]]);
    $input='$cost';$literal=mq128Text($input,1,32);
    $pipeline=[['$match'=>['_id'=>1]],['$project'=>['_id'=>0,'title'=>1,'field_value'=>'$cost','literal_text'=>['$literal'=>$literal]]],['$limit'=>1]];
    $found=mq128Command($manager,$db,['aggregate'=>$collection,'pipeline'=>$pipeline,'cursor'=>['batchSize'=>2],'maxTimeMS'=>1500]);$d=$found[0];
    $checks=['one_result'=>count($found)===1,'field_reference'=> $d['field_value']===700,'literal_not_evaluated'=>$d['literal_text']==='$cost'];
    $details=['pipeline'=>$pipeline];return [[$d['title'],$d['field_value'],$d['literal_text']]];
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-literal-expression',['Товар', 'Ссылка на поле $cost', 'Текст пользователя'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-literal-expression: OK
';
Ожидаемый результат
mongodb128-safe-literal-expression: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Замените подпись на "$unknown". Часть literal_text должна показать ровно этот текст. Отдельно обсудите, почему путь к отсутствующему полю в выражении не равен строке с его названием.

Рассчитать строки заказа обычными операторами базы

В заказе нужно показать стоимость каждой позиции: цена × количество. Для этого не требуется хранить или выполнять JavaScript на сервере базы. MongoDB умеет вычислить произведение и вернуть только позиции выбранного заказа стандартными операторами.

Входные данные и понятия

У заказа A две позиции: BOX по 2500 копеек × 2 и TRAY по 1200 копеек × 3. Третья позиция относится к B. $match выбирает A, $project сохраняет sku и вычисляет line_total через $multiply. В конце заданы сортировка и limit.

Путь данных в этом примере
  1. Заказ A → $match исключает позицию заказа B
  2. Каждый документ: $price × $qty, где price задан в копейках
  3. Проекция sku/line_total → сортировка по _id → limit10
  4. Две строки BOX 5000 и TRAY 3600; вместе 8600 копеек

Минимальная запись

$pipeline = [
    ['$match' => ['order' => 'A']],
    ['$project' => ['_id'=>0, 'sku'=>1, 'line_total'=>['$multiply'=>['$price', '$qty']]]],
    ['$limit' => 10]
]; // Числа хранятся в копейках, расчёт выполняет MongoDB.

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

Результат

Позиция заказа AСтоимость строки, коп.
BOX5000
TRAY3600

Почему получился такой результат

Строки '$price' и '$qty' означают чтение числовых полей цены в копейках и количества. Массив аргументов $multiply содержит два выражения; их произведение становится новым полем результата. $project не изменяет исходные документы. В этой задаче PHP получает уже нужные строки, а не все заказы для последующей фильтрации. Проверка сверяет обе суммы по строкам; отдельный серверный общий итог делают через $group/$sum.

Границы и типичные ошибки

Числа здесь малы и целые; пример не задаёт универсальную политику округления и переполнения денег. Для больших диапазонов и дробных сумм нужна явная модель Int64/Decimal128. Оператор $expr позволяет использовать aggregation-выражения в фильтре и сам по себе не означает выполнение JavaScript. Запрет $where не делает безопасным произвольный pipeline от клиента: структуру всё равно выбирает сервер приложения.

Полная самостоятельная программа и архив
Рассчитать строки заказа обычными операторами базыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
mq128Insert($manager,$namespace,[['_id'=>1,'order'=>'A','sku'=>'BOX','price'=>2500,'qty'=>2],['_id'=>2,'order'=>'A','sku'=>'TRAY','price'=>1200,'qty'=>3],['_id'=>3,'order'=>'B','sku'=>'BOX','price'=>2500,'qty'=>5]]);
    $pipeline=[['$match'=>['order'=>'A']],['$project'=>['_id'=>1,'sku'=>1,'line_total'=>['$multiply'=>['$price','$qty']]]],['$sort'=>['_id'=>1]],['$limit'=>10]];
    $found=mq128Command($manager,$db,['aggregate'=>$collection,'pipeline'=>$pipeline,'cursor'=>['batchSize'=>11],'maxTimeMS'=>1500]);
    $fieldNames=array_keys($found[0]);sort($fieldNames);
    $checks=['only_order_a'=>count($found)===2,'server_totals'=>array_column($found,'line_total')===[5000,3600],'only_output_fields'=>$fieldNames===['_id','line_total','sku']];
    $details=['pipeline'=>$pipeline,'unit'=>'kopecks','server_side_javascript_executed'=>false];return array_map(fn($d)=>[$d['sku'],$d['line_total']],$found);
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-native-calculation',['Позиция заказа A', 'Стоимость строки, коп.'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-native-calculation: OK
';
Ожидаемый результат
mongodb128-safe-native-calculation: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Добавьте третью позицию заказа A и посчитайте её итог в той же стадии. Затем получите общий итог через $group с $sum; не загружайте ради общей суммы многотысячный список в PHP.

Вопрос из практики: MongoDB Data API injection attacks. Автор спрашивает, защищает ли API автоматически от опасной структуры, переданной пользователем. Название старого API не делает произвольный фильтр безопасным. Это источник постановки проблемы; приведённый здесь код и решение написаны заново по актуальным официальным контрактам.

Прочитать BSON Javascript как данные, не выполняя код

При переносе старой коллекции обнаружилось поле BSON Javascript. Администратору нужно понять, какой текст там лежит и есть ли связанные значения. Получим просмотрщик: сохранение и чтение такого поля не означает запуск функции из этого текста.

Входные данные и понятия

Две записи содержат одинаковый текст function () { return factor; }. У первой нет scope, у второй scope хранит factor=3. Scope — отдельный документ имён и значений, исторически связанный с кодом. В результате показаны текст кода, PHP-тип scope и JSON его содержимого.

Путь данных в этом примере
  1. Строка кода + необязательный scope → new Javascript
  2. Сохранить типизированное поле в своей коллекции
  3. Прочитать поле → getCode/getScope/__toString
  4. Таблица показывает текст и {factor:3}; return внутри строки не выполняется

Минимальная запись

$js = new MongoDB\BSON\Javascript('function () { return factor; }', ['factor'=>3]);
$text = $js->getCode();
$scope = $js->getScope(); // stdClass с factor=3; это чтение данных, не запуск функции.

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

Результат

IDИсходный код как строкаТип scopeСодержимое scope
1function () { return factor; }nullnull
2function () { return factor; }stdClass{"factor":3}

Почему получился такой результат

new MongoDB\BSON\Javascript создаёт PHP-объект для BSON-значения. Само слово return находится внутри строки и не возвращает 3 из нашей PHP-программы. getCode возвращает эту строку; getScope — null либо объект stdClass; (string)$rule даёт тот же текст, что getCode. Курсор настроен на PHP-массивы для документов, но scope этого BSON-объекта остаётся объектом.

Границы и типичные ошибки

BSON JavaScript code with scope (type15) устарел. В MongoDB 8.0 также устарело server-side выполнение через $where/$function/$accumulator. Это отдельные ограничения: чтение сохранённого поля показано, запуск не предлагается. Не вставляйте полученный текст в eval, HTML script, $where или $function. Просмотрщик экранирует HTML и выводит только текст.

Полная самостоятельная программа и архив
Прочитать BSON Javascript как данные, не выполняя кодСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
$code='function () { return factor; }';
    mq128Insert($manager,$namespace,[['_id'=>1,'rule'=>new MongoDB\BSON\Javascript($code)],['_id'=>2,'rule'=>new MongoDB\BSON\Javascript($code,['factor'=>3])]]);
    $found=mq128Find($manager,$namespace,[],['_id'=>1,'rule'=>1]);$rows=[];
    foreach($found as $d){$js=$d['rule'];$rows[]=[$d['_id'],$js->getCode(),get_debug_type($js->getScope()),mq128Json($js->getScope())];}
    $checks=['two_values'=>count($found)===2,'plain_scope_null'=>$found[0]['rule']->getScope()===null,'scope_object'=>$found[1]['rule']->getScope() instanceof stdClass,'factor_preserved'=>$found[1]['rule']->getScope()->factor===3,'string_is_code'=>(string)$found[1]['rule']===$code];
    $details=['executed_code'=>false,'same_text_different_scope'=>true];return $rows;
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-inspect-javascript',['ID', 'Исходный код как строка', 'Тип scope', 'Содержимое scope'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-inspect-javascript: OK
';
Ожидаемый результат
mongodb128-safe-inspect-javascript: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Измените scope factor на 4. В таблице должно измениться только содержимое scope; никакого вычисленного результата 4 автоматически не появится. Для новой бизнес-модели храните явный вид операции и параметры, а не код.

Различить отсутствующий scope, пустой документ и ошибку NUL

При экспорте старых правил нельзя смешивать «scope не задан» и «scope задан пустым документом». Одновременно покажем одну конкретную ошибку constructor: нулевой байт в строке кода.

Входные данные и понятия

Три допустимых значения имеют код function () { return 7; }, но constructor получает null, [] и (object)[]. Четвёртая попытка содержит NUL внутри строки. [] — PHP-массив, (object)[] — пустой объект; оба разрешены как вход scope.

Путь данных в этом примере
  1. null / [] / пустой object → constructor Javascript
  2. Сохранить три допустимых значения и прочитать их
  3. getScope: null / stdClass{} / stdClass{}
  4. jsonSerialize: без $scope / со $scope:{}; NUL даёт отказ до записи

Минимальная запись

$withoutScope = new MongoDB\BSON\Javascript('function () { return 7; }', null);
$emptyScope = new MongoDB\BSON\Javascript('function () { return 7; }', []);
// getScope(): null и пустой stdClass соответственно. Нулевой байт в code запрещён.

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

Результат

СлучайgetScope / действиеJSON-представление
1Нет scopeБез $scope
2{}Есть $scope
3{}Есть $scope
NULОтклонёнНе записан

Почему получился такой результат

Отсутствующий scope сериализуется без ключа $scope, а пустой заданный документ сохраняет этот ключ с {}. Поэтому проверка array_key_exists на JSON-представлении отличается от простой проверки «scope пустой». PHP get_debug_type показывает класс, не число элементов. Исключение NUL поймано точечно как MongoDB\Driver\Exception\InvalidArgumentException; прочие ошибки базы не маскируются успешной таблицей.

Границы и типичные ошибки

Эта проверка не является валидатором синтаксиса или безопасного поведения JavaScript. Запрет NUL — ограничение значения constructor, а не анализ содержания программы. Страница PHP Manual исторически предлагала scope для отделения значений от текста; современный $where больше не принимает code-with-scope. Пустой scope не обходит это ограничение.

Полная самостоятельная программа и архив
Различить отсутствующий scope, пустой документ и ошибку NULСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
$code='function () { return 7; }';$docs=[['_id'=>1,'rule'=>new MongoDB\BSON\Javascript($code,null)],['_id'=>2,'rule'=>new MongoDB\BSON\Javascript($code,[])],['_id'=>3,'rule'=>new MongoDB\BSON\Javascript($code,(object)[])]];
    $nul=false;try{new MongoDB\BSON\Javascript("return\0 7;");}catch(MongoDB\Driver\Exception\InvalidArgumentException){$nul=true;}
    mq128Insert($manager,$namespace,$docs);$found=mq128Find($manager,$namespace,[],['_id'=>1,'rule'=>1]);$rows=[];
    foreach($found as $d){$scope=$d['rule']->getScope();$json=json_decode(mq128Json($d['rule']->jsonSerialize()),true,512,JSON_THROW_ON_ERROR);$rows[]=[$d['_id'],$scope===null?'Нет scope':mq128Json($scope),array_key_exists('$scope',$json)?'Есть $scope':'Без $scope'];}
    $checks=['three_stored'=>count($found)===3,'no_scope'=>$found[0]['rule']->getScope()===null,'empty_scope_is_object'=>$found[1]['rule']->getScope() instanceof stdClass && get_object_vars($found[1]['rule']->getScope())===[],'object_scope_is_object'=>$found[2]['rule']->getScope() instanceof stdClass,'nul_rejected'=>$nul];
    $details=['nul_exception'=>'MongoDB\\Driver\\Exception\\InvalidArgumentException','code_executed'=>false];$rows[]=['NUL','Отклонён','Не записан'];return $rows;
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-scope-boundaries',['Случай', 'getScope / действие', 'JSON-представление'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-scope-boundaries: OK
';
Ожидаемый результат
mongodb128-safe-scope-boundaries: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Добавьте непустой scope с ключом enabled=false. Убедитесь, что false сохранён как значение, а не принят за отсутствие scope. Сравнивайте наличие и тип, а не только truthy/falsy.

Перенести код и scope через типизированный JSON

Просмотрщик должен выгрузить старое правило в файл и затем восстановить его тип и scope. Если записать только (string)$rule, останется код, а параметры потеряются. Если просто вызвать json_decode, получится обычный массив, а не BSON Javascript.

Входные данные и понятия

Правило содержит factor=3 и label="<rule>". Пример показывает legacy-представление jsonSerialize для диагностики и отдельно Canonical Extended JSON целого Document для типизированного обмена. Восстановленное значение действительно записывается второй записью и читается обратно.

Путь данных в этом примере
  1. Прочитанный BSON Javascript → Document::fromPHP
  2. toCanonicalExtendedJSON → JSON с $code/$scope
  3. fromJSON → типизированный Javascript; json_decode → обычный array
  4. Повторная запись/чтение подтверждают code и оба значения scope

Минимальная запись

$json = MongoDB\BSON\Document::fromPHP(['rule'=>$rule])->toCanonicalExtendedJSON();
$restored = MongoDB\BSON\Document::fromJSON($json)->get('rule');
// (string)$rule хранит только code и потерял бы scope. JSON-декодер PHP не создаёт BSON-объект.

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

Результат

ШагРезультат
json_decodearray
Document::fromJSONMongoDB\BSON\Javascript
Сохранённый codefunction () { return factor; }
Сохранённый scope{"factor":3,"label":"<rule>"}

Почему получился такой результат

jsonSerialize возвращает данные, которые может превратить в строку json_encode; это не обязательно PHP-массив по контракту mixed. Document::fromJSON понимает Extended JSON и восстанавливает BSON-значение. get("rule") достаёт поле. Контроль сравнивает и код, и scope, поскольку одинаковый текст при разных параметрах — разные данные. Подпись <rule> видна в HTML как текст благодаря отдельному HTML-экранированию.

Границы и типичные ошибки

jsonSerialize описан драйвером как legacy Extended JSON; не объявляйте его автоматически современным canonical-режимом для любых BSON-типов. Для обмена целым документом выберите формат явно. JSON из внешнего источника всё равно требует проверки схемы и ограничений объёма; восстановленный Javascript не становится доверенным или разрешённым к исполнению.

Полная самостоятельная программа и архив
Перенести код и scope через типизированный JSONСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
$original=new MongoDB\BSON\Javascript('function () { return factor; }',['factor'=>3,'label'=>'<rule>']);
    mq128Insert($manager,$namespace,[['_id'=>1,'rule'=>$original]]);
    $loaded=mq128Find($manager,$namespace,['_id'=>1],['_id'=>0,'rule'=>1])[0]['rule'];
    $legacy=mq128Json($loaded->jsonSerialize());$canonical=MongoDB\BSON\Document::fromPHP(['rule'=>$loaded])->toCanonicalExtendedJSON();
    $plain=json_decode($canonical,true,512,JSON_THROW_ON_ERROR);$restored=MongoDB\BSON\Document::fromJSON($canonical)->get('rule');
    mq128Insert($manager,$namespace,[['_id'=>2,'rule'=>$restored]]);$saved=mq128Find($manager,$namespace,['_id'=>2],['_id'=>0,'rule'=>1])[0]['rule'];
    $checks=['plain_decode_is_array'=>is_array($plain['rule']),'restored_bson'=>$restored instanceof MongoDB\BSON\Javascript,'code_preserved'=>$saved->getCode()===$loaded->getCode(),'scope_preserved'=>mq128Json($saved->getScope())===mq128Json($loaded->getScope()),'legacy_contains_code'=>json_decode($legacy,true,512,JSON_THROW_ON_ERROR)['$code']===$loaded->getCode()];
    $details=['legacy_json'=>$legacy,'canonical_json'=>$canonical,'restored_scope'=>$saved->getScope()];
    return [['json_decode','array'],['Document::fromJSON',get_debug_type($restored)],['Сохранённый code',$saved->getCode()],['Сохранённый scope',mq128Json($saved->getScope())]];
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-javascript-json',['Шаг', 'Результат'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-javascript-json: OK
';
Ожидаемый результат
mongodb128-safe-javascript-json: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Поменяйте factor в экспортированном JSON и восстановите данные только в учебной коллекции. Сравнение scope должно заметить изменение. Не используйте совпадение одной строки кода как доказательство идентичности всего правила.

Сделать типизированный просмотрщик старых правил

Код просмотра хочется использовать и после чтения коллекции, и после восстановления Extended JSON. Общий интерфейс описывает три нужные операции, чтобы одна функция могла читать code, scope и строковое представление без знания места получения значения.

Входные данные и понятия

В коллекции два настоящих BSON Javascript: с factor=3 и без scope. JavascriptInterface обозначен как тип параметра mq128Describe, свойства Mq128RuleView::$rule и результата value(). Интерфейс содержит getCode, getScope и __toString.

Путь данных в этом примере
  1. Прочитанное BSON-значение → типизированное свойство rule
  2. value(): JavascriptInterface → тот же объект
  3. mq128Describe читает code, scope, строковый alias
  4. Две строки отчёта; ни интерфейс, ни type hint не исполняют код

Минимальная запись

function describe(MongoDB\BSON\JavascriptInterface $rule): array {
    return ['code'=>$rule->getCode(), 'scope'=>$rule->getScope()];
}
// Тип подходит параметру, свойству и результату метода. Он не является разрешением выполнить код.

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

Результат

IDgetCode()getScope() → JSONСтрока равна code
1function () { return factor; }{"factor":3}true
2function () { return 7; }nulltrue

Почему получился такой результат

Тип параметра сообщает PHP и читателю, какие методы здесь ожидаются. Конструктор Mq128RuleView с public-параметром одновременно объявляет и заполняет свойство — это constructor promotion. Реальная логика просмотра остаётся простой: два getter и преобразование в строку. Интерфейс не имеет своего constructor и самостоятельно ничего не хранит. В карточках ниже раскрыт контракт каждого метода.

Границы и типичные ошибки

В примере используются настоящие объекты расширения. Произвольная пользовательская реализация интерфейса не получает обещания автоматического BSON-кодирования и может содержать свой PHP-код в методах. С ext-mongodb2.0 типы возврата обязательны. Для новых правил разумнее явная структура kind=multiply,factor=3 и разрешённый обработчик; details только показывает предложение такой модели, а не автоматический перевод старого JS.

Полная самостоятельная программа и архив
Сделать типизированный просмотрщик старых правилСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/mq128-helper.php';
[$uri,$db]=mq128Config();
$manager=new MongoDB\Driver\Manager($uri,['connectTimeoutMS'=>1000,'serverSelectionTimeoutMS'=>1500,'socketTimeoutMS'=>2000,'retryReads'=>false]);
$runtime=['php'=>PHP_VERSION,'extension'=>phpversion('mongodb'),'server'=>mq128Command($manager,$db,['buildInfo'=>1])[0]['version'],'execution'=>'database'];
$checks=[];$details=[];
$rows=mq128WithCollection($manager,$db,function(string $namespace,string $collection) use($manager,$db,&$checks,&$details): array {
mq128Insert($manager,$namespace,[['_id'=>1,'rule'=>new MongoDB\BSON\Javascript('function () { return factor; }',['factor'=>3])],['_id'=>2,'rule'=>new MongoDB\BSON\Javascript('function () { return 7; }')]]);
    $found=mq128Find($manager,$namespace,[],['_id'=>1,'rule'=>1]);$rows=[];$allInterfaces=true;
    foreach($found as $d){$view=new Mq128RuleView($d['rule']);$v=mq128Describe($view->value());$allInterfaces=$allInterfaces && $view->rule instanceof MongoDB\BSON\JavascriptInterface;$rows[]=[$d['_id'],$v['code'],$v['scope'],$v['same_text']];}
    $checks=['two_contract_values'=>$allInterfaces && count($rows)===2,'first_scope'=>$rows[0][2]==='{"factor":3}','second_scope'=>$rows[1][2]==='null','alias_matches'=>$rows[0][3] && $rows[1][3]];
    $details=['parameter_property_return'=>'MongoDB\\BSON\\JavascriptInterface','new_rule_model'=>['kind'=>'multiply','factor'=>3],'automatic_code_translation'=>false];return $rows;
});
$checks['owned_collection_removed']=true;
mq128Report('mongodb128-safe-interface-summary',['ID', 'getCode()', 'getScope() → JSON', 'Строка равна code'],$rows,$checks,$runtime,$details);
echo 'mongodb128-safe-interface-summary: OK
';
Ожидаемый результат
mongodb128-safe-interface-summary: OK

Настоящие ограниченные запросы к собственной учебной коллекции. Значения Javascript только сохраняются и читаются; HTML/JSON показывают фактические результаты. Helper и инструкция находятся в ZIP.

Открыть фактическую таблицу и промежуточные значения · Данные результата JSON

Упражнение с направлением решения. Дополните просмотрщик отдельным полем has_scope, вычисленным через getScope()!==null. Сохраните различие отсутствующего и пустого scope. Перенос старого правила в новую явную модель проверяйте отдельно по смыслу, а не поиском строки "return".

Javascript: класс, интерфейс и все методы

Справочник ниже охватывает класс Javascript, JavascriptInterface и их восемь методов. Это описание типа данных и операций просмотра; оно не предлагает исполнять полученные строки. Каждый контракт связан с двумя применениями в полных программах этой главы.

Класс или методДля чего используется
MongoDB\BSON\JavascriptПредставить сохранённый BSON-код и его необязательный scope как данные для просмотра и переноса.
MongoDB\BSON\JavascriptInterfaceОписать зависимость функции просмотра от операций чтения кода и scope.
MongoDB\BSON\Javascript::__constructСоздать значение из текста старого правила и явно переданных параметров.
MongoDB\BSON\Javascript::getCodeПрочитать текст BSON-правила для экрана или сравнения.
MongoDB\BSON\Javascript::getScopeПрочитать связанные с кодом имена и значения.
MongoDB\BSON\Javascript::jsonSerializeПолучить данные для JSON-представления BSON Javascript.
MongoDB\BSON\Javascript::__toStringПолучить текст кода обычным преобразованием в string.
MongoDB\BSON\JavascriptInterface::getCodeПрочитать текст кода через общий контракт JavascriptInterface.
MongoDB\BSON\JavascriptInterface::getScopeПрочитать scope через общий контракт JavascriptInterface.
MongoDB\BSON\JavascriptInterface::__toStringПрочитать текст кода через общий контракт JavascriptInterface.

От понятного входа к нужному результату

Для формы сначала определите допустимые имена, типы и диапазоны. Затем самостоятельно соберите фильтр, проекцию, сортировку и предел выдачи; ограничение доступа включите в запрос. Для расчёта используйте разрешённые выражения, а старые BSON-правила показывайте как текст и параметры. Результат должен быть именно тем, что сейчас нужно экрану или следующему шагу сервиса.

Дальше: буквальная строка и регулярное выражение, индексы и ограниченная выдача, отчёты aggregation, числа и деньги, специальные BSON-значения. Список API расположен в дереве справочника.

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

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

MongoDB\BSON\JavascriptПредставить сохранённый BSON-код и его необязательный scope как данные для просмотра и переноса.

Класс с ext-mongodb1.0.0. В 1.2.0 добавлены JsonSerializable и старый Serializable; в 1.3.0 — JavascriptInterface; в 1.12.0 — Stringable для PHP8+. В 2.0.0 Serializable удалён из реализуемых интерфейсов. Эти версии расширения не совпадают с версиями сервера MongoDB. · mongodb

final class MongoDB\BSON\Javascript implements JavascriptInterface, Type, JsonSerializable, Stringable

Параметры и вызов

__construct(string $code, array|object|null $scope = null). $code — текст; $scope — необязательный документ имён и значений. Остальные методы без аргументов: getCode(): string, getScope(): ?object, jsonSerialize(): mixed, __toString(): string. Историческое назначение класса — BSON-параметр команд, принимавших JS, например mapReduce; это не рекомендация выбирать такие команды сейчас.

Возвращаемое значение

PHP-объект, кодируемый как BSON Javascript; при заданном scope — code-with-scope. Constructor не записывает объект в коллекцию. Getter возвращает части значения, строковый cast — только код, JsonSerializable — данные для JSON.

Ошибки и границы

Constructor отвергает NUL в коде; InvalidArgumentException также указан для разбора его аргументов. Хранение/чтение может иметь самостоятельные ошибки MongoDB. Класс не объявляет проверку безопасности содержимого строки.

BSON code-with-scope (type15) устарел; современный $where его не принимает. Server-side JavaScript через $where/$function/$accumulator устарел в MongoDB8.0. Здесь показаны чтение и сохранение значения для просмотра/переноса, без исполнения. Для новых задач используйте явные данные и обычные операторы.

Версии

Класс с ext-mongodb1.0.0. В 1.2.0 добавлены JsonSerializable и старый Serializable; в 1.3.0 — JavascriptInterface; в 1.12.0 — Stringable для PHP8+. В 2.0.0 Serializable удалён из реализуемых интерфейсов. Эти версии расширения не совпадают с версиями сервера MongoDB.

Инвентаризация старых правил

$rule = new MongoDB\BSON\Javascript('function () { return factor; }', ['factor'=>3]);

Запись читается из собственной коллекции, затем показываются отдельно code и scope. Содержимое return не выполняется.

Полная программа, данные и результат

Перенос с сохранением типа и scope

$json = MongoDB\BSON\Document::fromPHP(['rule'=>$rule])->toCanonicalExtendedJSON();

Восстановление через Document::fromJSON и повторное чтение подтверждают обе части; обычный json_decode оставляет массив.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\JavascriptInterfaceОписать зависимость функции просмотра от операций чтения кода и scope.

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны. · mongodb

interface MongoDB\BSON\JavascriptInterface { public function getCode(): string; public function getScope(): ?object; public function __toString(): string; }

Параметры и вызов

Интерфейс применяется как тип параметра, свойства и возвращаемого значения. Его реализует Javascript. Все три метода без аргументов: getCode, getScope, __toString. Собственного constructor и состояния у интерфейса нет.

Возвращаемое значение

Контракт строки кода, объекта scope либо null и строкового представления кода. Тип результата ?object означает object|null. Type hint не создаёт объект и не гарантирует BSON-кодирование произвольного пользовательского класса.

Ошибки и границы

Страница интерфейсного метода не задаёт собственных исключений. Реализация обязана соблюдать тип результата; ошибка пользовательской реализации относится к её коду. Сам интерфейс не запускает запрос.

Тип подтверждает доступность методов, а не доверие к источнику значения и не право исполнения JS. Примеры используют настоящий класс расширения. BSON code-with-scope (type15) устарел; современный $where его не принимает. Server-side JavaScript через $where/$function/$accumulator устарел в MongoDB8.0. Здесь показаны чтение и сохранение значения для просмотра/переноса, без исполнения. Для новых задач используйте явные данные и обычные операторы.

Версии

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны.

Общий просмотрщик для разных источников

function describe(MongoDB\BSON\JavascriptInterface $rule): array {
    return ['code'=>$rule->getCode(), 'scope'=>$rule->getScope()];
}

Полный пример использует этот же контракт для параметра, свойства DTO и результата метода.

Полная программа, данные и результат

Просмотр после восстановления BSON

$view = mq128Describe($restored);

Восстановленный настоящий Javascript подходит тому же просмотру. В основном примере отдельно проверены тип и содержимое сохранённого значения.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\Javascript::__constructСоздать значение из текста старого правила и явно переданных параметров.

С ext-mongodb1.0.0. С 1.2.0 NUL приводит к исключению; раньше текст обрезался на первом нулевом байте. · mongodb

final public function MongoDB\BSON\Javascript::__construct(string $code, array|object|null $scope = null)

Параметры и вызов

$code — обязательная строка. $scope — массив, объект или null; по умолчанию null. Документ scope связывает имена с их значениями. Отсутствующий scope, пустой массив и пустой объект сравниваются отдельным примером.

Возвращаемое значение

Constructor инициализирует новый Javascript; выражение new даёт объект. Оно не возвращает результат return, написанного внутри строки, и не обращается к MongoDB.

Ошибки и границы

MongoDB\Driver\Exception\InvalidArgumentException при разборе аргументов и при NUL в $code. NUL реально проверяется отдельным отрицательным случаем; произвольная неправильная JS-программа этим не считается проверенной.

BSON code-with-scope (type15) устарел; современный $where его не принимает. Server-side JavaScript через $where/$function/$accumulator устарел в MongoDB8.0. Здесь показаны чтение и сохранение значения для просмотра/переноса, без исполнения. Для новых задач используйте явные данные и обычные операторы.

Версии

С ext-mongodb1.0.0. С 1.2.0 NUL приводит к исключению; раньше текст обрезался на первом нулевом байте.

Сохранить код вместе с factor

$rule = new MongoDB\BSON\Javascript('function () { return factor; }', ['factor'=>3]);

После чтения factor=3 остаётся в scope, а код — отдельной строкой.

Полная программа, данные и результат

Проверить форму scope и отказ NUL

$none = new MongoDB\BSON\Javascript($code);
$empty = new MongoDB\BSON\Javascript($code, []);

Один результат не имеет scope, другой хранит пустой документ; код с NUL отвергается до записи.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\Javascript::getCodeПрочитать текст BSON-правила для экрана или сравнения.

С ext-mongodb1.2.0. · mongodb

final public function MongoDB\BSON\Javascript::getCode(): string

Параметры и вызов

Аргументов нет. Метод вызывается на уже существующем значении. Чтение code/scope не выполняет запрос к базе и не запускает JavaScript.

Возвращаемое значение

Строка с сохранённым кодом. Scope не включён. Получение строки не вызывает содержащуюся в ней функцию.

Ошибки и границы

Официальный контракт указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сетевые ошибки относятся к предшествующему чтению документа, а ошибки JSON-кодирования — к последующему json_encode.

Одинаковый code может сопровождаться разным scope. Для сравнения/экспорта целого значения учитывайте обе части. HTML-экран выводит строку через htmlspecialchars, а не как тег script.

Версии

С ext-mongodb1.2.0.

Показать текст найденного старого правила

$text = $document["rule"]->getCode();

В полной программе две записи имеют одинаковый code и разный scope; таблица делает эту разницу видимой.

Полная программа, данные и результат

Сверить код после типизированного обмена

$sameCode = $saved->getCode() === $loaded->getCode();

После Extended JSON правило снова записано и прочитано; отдельно проверен scope, чтобы не потерять параметры.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\Javascript::getScopeПрочитать связанные с кодом имена и значения.

С ext-mongodb1.2.0. · mongodb

final public function MongoDB\BSON\Javascript::getScope(): ?object

Параметры и вызов

Аргументов нет. Метод вызывается на уже существующем значении. Чтение code/scope не выполняет запрос к базе и не запускает JavaScript.

Возвращаемое значение

Объект scope, либо null, если scope не задан. В опытах настоящего Javascript это stdClass даже при array type map курсора. Массив на входе constructor не означает массив результата getter.

Ошибки и границы

Официальный контракт указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сетевые ошибки относятся к предшествующему чтению документа, а ошибки JSON-кодирования — к последующему json_encode.

Проверяйте !==null до доступа к свойствам; пустой объект и отсутствие отличаются. Getter не проверяет полноту параметров для смысла старого кода и не запускает его. BSON code-with-scope (type15) устарел; современный $where его не принимает. Server-side JavaScript через $where/$function/$accumulator устарел в MongoDB8.0. Здесь показаны чтение и сохранение значения для просмотра/переноса, без исполнения. Для новых задач используйте явные данные и обычные операторы.

Версии

С ext-mongodb1.2.0.

Показать параметры рядом с текстом

$scope = $document["rule"]->getScope();

Первая строка отчёта показывает null; вторая — stdClass с factor=3. Getter возвращает документ, а не результат функции.

Полная программа, данные и результат

Не смешать отсутствие с пустым документом

$hasScope = $rule->getScope() !== null;

Null отличается от пустого stdClass. В JSON отсутствие даёт пропущенный ключ, пустой scope даёт $scope:{}.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\Javascript::jsonSerializeПолучить данные для JSON-представления BSON Javascript.

С ext-mongodb1.2.0. Документация описывает legacy Extended JSON, специфичный для драйвера. Режим canonical для целого документа явно выбирается отдельным Document API. · mongodb

final public function MongoDB\BSON\Javascript::jsonSerialize(): mixed

Параметры и вызов

Аргументов нет. Метод вызывается на уже существующем значении. Чтение code/scope не выполняет запрос к базе и не запускает JavaScript.

Возвращаемое значение

Значение, которое json_encode переводит в JSON. В текущих опытах есть $code и, если scope задан, $scope. Контракт mixed нельзя заменять обещанием массива во всех версиях. Метод не возвращает готовую JSON-строку.

Ошибки и границы

Официальный контракт указывает MongoDB\Driver\Exception\InvalidArgumentException при ошибке разбора аргументов. Сетевые ошибки относятся к предшествующему чтению документа, а ошибки JSON-кодирования — к последующему json_encode.

json_decode не восстанавливает BSON-классы. JsonSerializable даёт представление данных, а не разрешение интерпретировать поле $code как программу. Отдельно учитывайте ошибки кодирования JSON и HTML-контекст отображения.

Версии

С ext-mongodb1.2.0. Документация описывает legacy Extended JSON, специфичный для драйвера. Режим canonical для целого документа явно выбирается отдельным Document API.

Диагностика представления рядом с canonical

$legacy = json_encode($rule->jsonSerialize(), JSON_THROW_ON_ERROR);

Программа сохраняет диагностический legacy JSON и отдельно восстанавливает тип из Canonical Extended JSON документа.

Полная программа, данные и результат

Проверить наличие ключа scope

$view = json_decode(json_encode($rule->jsonSerialize(), JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR);
$present = array_key_exists('$scope', $view);

Это намеренный разбор для просмотра ключей, а не восстановление объекта: null не даёт ключа, пустой документ даёт {}.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\Javascript::__toStringПолучить текст кода обычным преобразованием в string.

Метод с ext-mongodb1.2.0. Javascript реализует Stringable на PHP8+ с ext-mongodb1.12.0. · mongodb

final public function MongoDB\BSON\Javascript::__toString(): string

Параметры и вызов

Аргументов нет. Метод вызывается на уже существующем значении. Чтение code/scope не выполняет запрос к базе и не запускает JavaScript.

Возвращаемое значение

Строка кода; метод является alias getCode. Scope и сведения о BSON-типе в строку не включаются.

Ошибки и границы

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

Строка подходит подписи или просмотру, но недостаточна для переноса значения со scope. Не запускайте строку и не вставляйте её в браузер как исполняемый сценарий.

Версии

Метод с ext-mongodb1.2.0. Javascript реализует Stringable на PHP8+ с ext-mongodb1.12.0.

Подпись в отчёте просмотра

$label = (string) $rule;

В таблице виден исходный текст; проверка подтверждает равенство getCode без исполнения return.

Полная программа, данные и результат

Объяснить потерю данных при строковом экспорте

$codeOnly = (string) $rule;
$whole = MongoDB\BSON\Document::fromPHP(['rule'=>$rule])->toCanonicalExtendedJSON();

Для настоящего переноса полный пример выбирает whole: в codeOnly нет factor и label из scope.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\JavascriptInterface::getCodeПрочитать текст кода через общий контракт JavascriptInterface.

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны. · mongodb

abstract public function MongoDB\BSON\JavascriptInterface::getCode(): string

Параметры и вызов

Аргументов нет. Метод вызывается на уже существующем значении. Чтение code/scope не выполняет запрос к базе и не запускает JavaScript.

Возвращаемое значение

Строка кода. Для Javascript это та же строка, что даёт __toString; связанные значения scope читаются отдельным методом.

Ошибки и границы

Страница интерфейсного метода не задаёт собственных исключений. Реализация обязана соблюдать тип результата; ошибка пользовательской реализации относится к её коду. Сам интерфейс не запускает запрос.

Общий тип позволяет вызывать операцию, но не подтверждает источник данных и не обещает BSON-кодирование любого собственного класса. Для интерфейсного примера использован настоящий Javascript. Строка остаётся данными; исполнение требует отдельного решения и здесь не применяется.

Версии

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны.

Показать текст найденного старого правила

$text = $document["rule"]->getCode();

В полной программе две записи имеют одинаковый code и разный scope; таблица делает эту разницу видимой.

Полная программа, данные и результат

Сверить код после типизированного обмена

$sameCode = $saved->getCode() === $loaded->getCode();

После Extended JSON правило снова записано и прочитано; отдельно проверен scope, чтобы не потерять параметры.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\JavascriptInterface::getScopeПрочитать scope через общий контракт JavascriptInterface.

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны. · mongodb

abstract public function MongoDB\BSON\JavascriptInterface::getScope(): ?object

Параметры и вызов

Аргументов нет. Метод вызывается на уже существующем значении. Чтение code/scope не выполняет запрос к базе и не запускает JavaScript.

Возвращаемое значение

Объект scope либо null, если scope не задан. Контракт не разрешает объявить array вместо ?object. Для настоящего Javascript пустой scope — объект, не null.

Ошибки и границы

Страница интерфейсного метода не задаёт собственных исключений. Реализация обязана соблюдать тип результата; ошибка пользовательской реализации относится к её коду. Сам интерфейс не запускает запрос.

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

Версии

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны.

Показать параметры рядом с текстом

$scope = $document["rule"]->getScope();

Первая строка отчёта показывает null; вторая — stdClass с factor=3. Getter возвращает документ, а не результат функции.

Полная программа, данные и результат

Не смешать отсутствие с пустым документом

$hasScope = $rule->getScope() !== null;

Null отличается от пустого stdClass. В JSON отсутствие даёт пропущенный ключ, пустой scope даёт $scope:{}.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

MongoDB\BSON\JavascriptInterface::__toStringПрочитать текст кода через общий контракт JavascriptInterface.

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны. · mongodb

abstract public function MongoDB\BSON\JavascriptInterface::__toString(): string

Параметры и вызов

Аргументов нет. Метод вызывается на уже существующем значении. Чтение code/scope не выполняет запрос к базе и не запускает JavaScript.

Возвращаемое значение

Строка кода; alias getCode. Это представление не содержит scope и не является результатом выполнения JS.

Ошибки и границы

Страница интерфейсного метода не задаёт собственных исключений. Реализация обязана соблюдать тип результата; ошибка пользовательской реализации относится к её коду. Сам интерфейс не запускает запрос.

Общий тип позволяет вызывать операцию, но не подтверждает источник данных и не обещает BSON-кодирование любого собственного класса. Для интерфейсного примера использован настоящий Javascript. Строка остаётся данными; исполнение требует отдельного решения и здесь не применяется.

Версии

JavascriptInterface появился в ext-mongodb1.3.0. С 1.15.0 на PHP8+ возвращаемые типы были tentative: несовпадение давало deprecation, временно подавляемый ReturnTypeWillChange. С 2.0.0 возвращаемые типы обязательны.

Проверить единый строковый контракт просмотрщика

$same = (string) $rule === $rule->getCode();

Две строки отчёта подтверждают alias на настоящих значениях с scope и без него.

Полная программа, данные и результат

Показать восстановленное значение без выполнения

$label = (string) $restored;

После BSON-декодера можно снова показать строку; полный пример дополнительно сравнивает scope, чтобы доказать сохранение целого значения.

Полная программа, данные и результат

Связи: Document и Extended JSON, интерфейсы и type hint, вывод текста через htmlspecialchars.

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

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

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

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

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