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

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

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

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

Глава 133 · После первого подключения и простого ассоциативного массива

MySQL X DevAPI: от объекта коллекции к сохранённым карточкам

Четыре самостоятельных сценария каталога: сохранение с настоящими ID, выбранные поля витрины, проверка изменения и ограниченное удаление. Семь закрытых конструкторов объяснены через правильные публичные фабрики.

От карточки товара до сохранения и ответа витрине

Нужен небольшой каталог: сохранить кабель и светильник, показать на витрине только доступные товары, утвердить новую цену одной карточки и убрать старые черновики. В этой главе результатом будут настоящие сохранённые документы, выбранные карточки и проверенный остаток после удаления. Одновременно станет понятно, почему для Schema, Collection, CollectionAdd, CollectionFind, CollectionModify, CollectionRemove и DocResult не пишут обычный new.

Одна карточка проходит через разные инструменты
  1. Session → Schema: выбираем подготовленную базу
  2. Schema → Collection: получаем конкретный каталог
  3. Collection → CollectionAdd: готовим CABLE с ценой 12000 копеек
  4. CollectionAdd → execute → Result: сохраняем и получаем созданный _id
  5. Collection → CollectionFind → execute → DocResult: выбираем товары для витрины
  6. DocResult → fetchAll → PHP-массив: выводим артикул, название и цену

Удобная модель — магазин и рабочие поручения: Schema указывает на отделение базы, Collection — на выбранный каталог, объект команды — на подготовленное поручение, Result — на квитанцию, DocResult — на ответ поиска. Это схема обязанностей, а не изображение физического устройства диска и не обещание отдельного сетевого соединения на каждый PHP-объект.

Начнём с самой карточки

Документ описывает один товар. Пока база не нужна: знакомый ассоциативный массив PHP связывает имена полей с их значениями. Ключ sku хранит артикул, name — название, price_kopecks — цену в целых копейках. Запись => соединяет ключ и значение внутри массива, а обычный знак = сохраняет весь массив в переменную $product.

$product = [
    'sku' => 'CABLE',
    'name' => 'Кабель',
    'price_kopecks' => 12000,
];
echo $product['name']; // Кабель

12000 копеек — это 120 рублей. Целые копейки выбраны, чтобы не вводить дробные денежные вычисления в урок о фабриках. Сам массив сейчас живёт только в PHP: завершение процесса не сохраняет его в базе. Поле _id появится при добавлении на нашем MySQL 8, если его не задать самостоятельно. Артикул и _id — разные поля; приложение не получает уникальность артикула только от его названия. Подробное начало — ассоциативные массивы; вложенные документы рассматриваются после простых в изменении вложенных настроек.

Почему объект получают через другой объект

У ряда классов расширения конструктор закрыт словом private. Прикладной код не может просто создать new Collection и ожидать готовую связь с сервером. Вместо этого уже настроенная Session знает соединение, её getSchema выбирает базу, а Schema выдаёт Collection для конкретного имени. Метод, который создаёт и возвращает пригодный объект, здесь называем фабрикой. Это не отдельный синтаксис PHP: это обычный вызов метода со стрелкой ->.

$schema = $session->getSchema($schemaName);
$collection = $schema->getCollection($collectionName);

В этом фрагменте Session уже открыта, schemaName — имя заранее подготовленной учебной базы, collectionName — имя существующего каталога. Круглые скобки содержат параметры метода; знак = сохраняет возвращённый объект. getSchema не создаёт базу, getCollection не создаёт коллекцию. Если нужен новый каталог, отдельный createCollection действительно создаёт его. Первая полная программа проверяет оба состояния: адрес получен, коллекции ещё нет; затем она создана, но документов пока ноль.

Для начала работы нужны расширение mysql_xdevapi и X Protocol, а не подключение MySQLi к обычному MySQL-порту. Пошаговая настройка дана в первом подключении. Устройство объекта и метода и проверки существования хранилища помогают различать PHP-объект и настоящий объект базы.

Подготовили действие — ещё не выполнили

Самое короткое сохранение уже подготовленной карточки состоит из двух шагов. add получает данные и возвращает CollectionAdd — объект команды. execute выполняет команду и возвращает Result — квитанцию. Разные имена переменных помогают видеть разницу: $command ещё может ждать выполнения, а $receipt относится к конкретному уже полученному ответу.

$command = $collection->add($product);
$receipt = $command->execute();
$ids = $receipt->getGeneratedIds();
$saved = $collection->getOne($ids[0]);
echo $saved['name']; // Кабель

getGeneratedIds даёт массив строковых идентификаторов документов этой операции. Первый ID берём по индексу 0, затем getOne читает сохранённый документ. Здесь нет заранее угаданного ID и нет вывода значения только из исходного $product: слово «Кабель» получено обратно из базы. Для нескольких документов полный пример проверяет все возвращённые ID. Если указали собственный _id, не следует ожидать его в списке автоматически созданных ID.

Нужное действиеФабрика и объект командыПосле execute
Добавить товарCollection::add → CollectionAddResult: квитанция, affected и generated IDs
Выбрать карточки для витриныCollection::find → CollectionFindDocResult: документы читаются через fetchAll
Изменить цену или статусCollection::modify → CollectionModifyResult; карточка перечитывается отдельно
Удалить старый черновикCollection::remove → CollectionRemoveResult; оставшиеся документы проверяются поиском

Настройки command можно писать цепочкой: каждый -> вызывает метод у возвращённого объекта. Но execute завершает подготовку и возвращает уже другой вид объекта. Поэтому $collection, $command, $receipt и $cards не взаимозаменяемы. Не стоит сериализовать любой из них в HTTP-ответ в надежде получить товар: данные ответа формируются явно.

Покажем нужные товары, а не весь каталог

Теперь задача чтения: нужны опубликованные товары. Строка условия описывает правило, :state обозначает место отдельного значения, bind передаёт published. fields оставляет только поля ответа. Следующий fetchAll превращает ответ DocResult в массив, пригодный для foreach или кодирования JSON.

$query = $collection->find('state = :state')
    ->fields(['sku','name'])->bind(['state'=>'published']);
$result = $query->execute();
$cards = $result->fetchAll();

Во второй полной программе добавлены остаток, порядок цены и limit(2). Среди пяти учебных документов сервер вернёт только CABLE и LED. Изменение фильтра до stock > 100 даёт []; для посетителя это сообщение «Подходящих товаров нет». Сортировать весь большой каталог после fetchAll в PHP для такого экрана не требуется. Для большого объёма отдельно проектируют индекс под запрос и проверяют его план; ограничение ответа не означает автоматически дешёвую обработку сервером.

Самостоятельные архивы и границы опыта

Каждый из четырёх архивов запускается отдельно и сам создаёт свои исходные документы: предыдущие примеры запускать перед ним не нужно. Уроки используют PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13. Это профиль проверки, а не утверждение о самых новых версиях. Подключение берётся из PHPMAN_MYSQLX_URI, имя подготовленной базы — из PHPMAN_MYSQLX_SCHEMA; реальных паролей в архиве нет.

lab.php проверяет окружение и ведёт список собственных объектов, а finally удаляет только случайно названную коллекцию этого запуска и закрывает Session. В первой программе получение адреса и createCollection показаны непосредственно; остальные используют открытую служебную функцию x145OwnCollection с теми же проверками. На настоящем сайте коллекцию создают при подготовке системы, а не для каждого запроса. Удаление в учебной программе относится только к её изолированным данным.

После выполнения result.html показывает конкретный результат, result.json содержит прочитанные документы, реальные ID, классы объектов и наблюдения. metrics.json проверяет значения и удаление собственного объекта. Если сервер не доступен или операция неожиданно не удалась, программа не печатает вместо ответа заранее подготовленные товары. Предусмотренные примерами пустая выдача и отсутствие совпадения показаны отдельно от ошибок сервера. Диагностику подключения нельзя публиковать с паролем или полным URI; общий helper очищает такую информацию перед записью предусмотренных сообщений.

Создать каталог и сохранить две карточки с настоящими ID

Редактор подготовил кабель за 120 рублей и светильник за 890 рублей. Их нужно сохранить в отдельном каталоге, а затем получить адрес каждой карточки для дальнейшего редактирования. Имена объектов PHP сами по себе не сохраняют товары: покажем отсутствие данных до execute и настоящее чтение после него.

Что происходит с данными
  1. Session выбирает заранее созданную Schema
  2. getCollection получает адрес ещё отсутствующей коллекции
  3. createCollection создаёт коллекцию; в ней пока 0 карточек
  4. add подготавливает CABLE и LAMP
  5. execute отправляет добавление и возвращает Result
  6. Result даёт реальные _id; getOne по ним возвращает сохранённые товары

Сначала короткое действие

$document = ['sku'=>'CABLE', 'price_kopecks'=>12000];
$command = $collection->add($document);
$receipt = $command->execute();
$ids = $receipt->getGeneratedIds();
echo $collection->getOne($ids[0])['sku']; // CABLE

Этот короткий фрагмент требует живую пустую коллекцию. Ключ sku обозначает артикул, price_kopecks — цену в целых копейках. add получает документ как PHP-массив и возвращает объект команды. Следующий execute выполняет команду. Квитанция Result сообщает созданные идентификаторы, но не является массивом товаров. $ids[0] — первый созданный ID именно этого добавления; getOne использует его для настоящего чтения. Полный пример сначала проверяет существование коллекции, затем создаёт её и добавляет две карточки одной командой.

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

НаблюдениеРезультат
Коллекция существовала до createCollectionнет
Карточек до execute0
Сохранено карточек2
Первый товарКабель
Цена первого товара, коп.12000
Второй товарСветильник
Цена второго товара, коп.89000
Получено созданных ID2
ID совпали с сохранёнными документамида
Самостоятельный файл и архив для запуска
Создать каталог и сохранить две карточки с настоящими IDСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x145Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x145Name('catalog_publish');
    x145Qualified($schema, $name);
    $handle = $schema->getCollection($name);
    $existsBefore = $handle->existsInDatabase();
    if ($existsBefore) { throw new RuntimeException('Случайное имя коллекции уже занято.'); }
    $collection = $schema->createCollection($name);
    $owned[] = ['type'=>'COLLECTION', 'name'=>$name];
    $documents = [
        ['sku'=>'CABLE', 'name'=>'Кабель', 'price_kopecks'=>12000, 'state'=>'published'],
        ['sku'=>'LAMP', 'name'=>'Светильник', 'price_kopecks'=>89000, 'state'=>'published'],
    ];
    $command = $collection->add($documents[0])->add($documents[1]);
    $beforeExecute = $collection->find('true')->fields(['sku','name','price_kopecks','state'])->sort('sku ASC')->execute()->fetchAll();
    $receipt = $command->execute();
    $ids = $receipt->getGeneratedIds();
    $readById = [];
    foreach ($ids as $id) { $readById[] = $collection->getOne($id); }
    $saved = $collection->find('true')->fields(['_id','sku','name','price_kopecks','state'])->sort('sku ASC')->execute()->fetchAll();
    $idsSorted = $ids;
    $savedIds = array_column($saved, '_id');
    sort($idsSorted, SORT_STRING);
    sort($savedIds, SORT_STRING);
    $byIdSkus = array_column($readById, 'sku');
    sort($byIdSkus, SORT_STRING);
    $publicDocuments = [];
    foreach ($saved as $document) {
        $publicDocuments[] = ['sku'=>$document['sku'], 'name'=>$document['name'],
            'price_kopecks'=>$document['price_kopecks'], 'state'=>$document['state']];
    }
    $rows = [['Коллекция существовала до createCollection', $existsBefore],
        ['Карточек до execute', count($beforeExecute)], ['Сохранено карточек', count($saved)],
        ['Первый товар', $saved[0]['name']], ['Цена первого товара, коп.', $saved[0]['price_kopecks']],
        ['Второй товар', $saved[1]['name']], ['Цена второго товара, коп.', $saved[1]['price_kopecks']],
        ['Получено созданных ID', count($ids)], ['ID совпали с сохранёнными документами', $idsSorted === $savedIds]];
    $checks = ['handle_not_creation'=>$existsBefore === false, 'deferred_add'=>$beforeExecute === [],
        'saved_documents'=>$publicDocuments === $documents, 'generated_count'=>count($ids) === 2,
        'generated_nonempty'=>count(array_filter($ids, static fn($id)=>is_string($id) && $id !== '')) === 2,
        'generated_unique'=>count(array_unique($ids)) === 2, 'ids_match'=>$idsSorted === $savedIds,
        'read_by_id'=>$byIdSkus === ['CABLE','LAMP'], 'affected'=>$receipt->getAffectedItemsCount() === 2,
        'factory_types'=>$schema instanceof mysql_xdevapi\Schema && $collection instanceof mysql_xdevapi\Collection
            && $command instanceof mysql_xdevapi\CollectionAdd && $receipt instanceof mysql_xdevapi\Result];
    $observations = ['schema_class'=>get_debug_type($schema), 'handle_class'=>get_debug_type($handle),
        'collection_class'=>get_debug_type($collection), 'command_class'=>get_debug_type($command),
        'receipt_class'=>get_debug_type($receipt), 'existed_before'=>$existsBefore, 'input_documents'=>$documents,
        'before_execute'=>$beforeExecute, 'generated_ids'=>$ids, 'read_by_id'=>$readById,
        'saved_documents'=>$saved, 'public_documents'=>$publicDocuments, 'affected'=>$receipt->getAffectedItemsCount()];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x145Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x145Report('mysql145-x-factory-catalog-publish', 'Создать каталог и сохранить две карточки с настоящими ID', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Коллекция существовала до createCollection — нет
Карточек до execute — 0
Сохранено карточек — 2
Первый товар — Кабель
Цена первого товара, коп. — 12000
Второй товар — Светильник
Цена второго товара, коп. — 89000
Получено созданных ID — 2
ID совпали с сохранёнными документами — да

Запускайте отдельным процессом PHP по README. Полный файл создаёт одну собственную коллекцию, выполняет показанные операции, перечитывает фактические документы и в finally удаляет только свой учебный объект. result.html показывает результат, result.json — документы и наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · Прочитанные документы и наблюдения.

Границы и ошибки

ID не равен артикулу и не является порядковым номером товара. Он создаётся сервером и меняется между запусками; в HTML показано совпадение, в JSON сохранены сами ID и прочитанные документы. getGeneratedIds относится к конкретной операции, чей Result сохранён в $receipt, а не ко всем записям коллекции. Последующие запросы не переключают прежнюю квитанцию на новую операцию. MySQL 8.0 и новее допускает генерацию _id; официальный пример отдельно требует явный _id для MySQL 5.7. Здесь проверяется MySQL 8.4.13. Повторный запуск добавления не становится безопасным повтором автоматически: повтор импорта требует отдельного уникального бизнес-ключа и стратегии обработки дубликатов.

После добавления документа разработчик пытается получить его идентификатор через табличный AUTO_INCREMENT и получает неподходящий результат. Задача на Stack Overflow. Коллекция получает строковый _id; читаем getGeneratedIds у квитанции именно выполненного добавления и по каждому фактическому ID перечитываем документ. Контракт PHP взят из PHP Manual и исходника расширения, а не из Python API.

Попробуйте изменить

Добавьте третью карточку BOX за 250 рублей. Какие числа изменятся и можно ли заранее вписать её будущий _id?

Ожидаемое решение

Добавьте документ с price_kopecks = 25000 и ещё один add к команде. После execute будет три документа и три созданных ID; до execute по-прежнему ноль. _id заранее неизвестен: сохраните ответ getGeneratedIds и сопоставьте его с фактически прочитанными карточками.

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

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

Что происходит с данными
  1. 5 документов остаются в коллекции
  2. find исключает state=draft и stock=0 на сервере
  3. sort price_kopecks, sku определяет устойчивый порядок при одинаковой цене
  4. limit 2 оставляет CABLE и LED
  5. fields оставляет sku, name, price_kopecks
  6. execute → DocResult → fetchAll → массив для HTML/JSON

Сначала короткое действие

$query = $collection->find('state = :state');
$query->fields(['sku','name'])->bind(['state'=>'published']);
$result = $query->execute();
$cards = $result->fetchAll();
foreach ($cards as $card) {
    echo $card['sku'], ': ', $card['name'], "\n";
}

В коротком фрагменте :state — место для значения, а bind передаёт published отдельно от текста условия. fields задаёт поля каждого ответа. execute возвращает DocResult, fetchAll — PHP-массив документов, foreach по одному берёт их в $card. В полной версии добавлены условие остатка, сортировка и предел 2. ASC означает возрастание; sku разрешает ничью одинаковых цен. Только после выполнения выборки можно решать, показывать карточки или сообщение «Подходящих товаров нет».

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

НаблюдениеРезультат
Всего карточек в учебном каталоге5
Карточек для витрины2
Первый артикулCABLE
Первое названиеКабель
Первая цена, коп.12000
Второй артикулLED
Второе названиеLED-лампа
Вторая цена, коп.12000
Только три нужных поляда
При остатке больше 100 карточек найдено0
Самостоятельный файл и архив для запуска
Получить две доступные карточки для витрины без выгрузки всего каталогаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x145Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x145OwnCollection($schema, $owned, 'filtered_feed');
    $collection = $schema->getCollection($name);
    $documents = [
        ['_id'=>'CABLE','sku'=>'CABLE','name'=>'Кабель','price_kopecks'=>12000,'stock'=>8,'state'=>'published'],
        ['_id'=>'LED','sku'=>'LED','name'=>'LED-лампа','price_kopecks'=>12000,'stock'=>3,'state'=>'published'],
        ['_id'=>'LAMP','sku'=>'LAMP','name'=>'Светильник','price_kopecks'=>89000,'stock'=>2,'state'=>'published'],
        ['_id'=>'DRAFT','sku'=>'DRAFT','name'=>'Новый товар','price_kopecks'=>9900,'stock'=>4,'state'=>'draft'],
        ['_id'=>'EMPTY','sku'=>'EMPTY','name'=>'Распроданный товар','price_kopecks'=>1000,'stock'=>0,'state'=>'published'],
    ];
    foreach ($documents as $document) { $collection->add($document)->execute(); }
    $command = $collection->find('state = :state AND stock > :minimum')
        ->fields(['sku','name','price_kopecks'])->sort('price_kopecks ASC','sku ASC')->limit(2)
        ->bind(['state'=>'published','minimum'=>0]);
    $result = $command->execute();
    $selected = $result->fetchAll();
    $emptyResult = $collection->find('state = :state AND stock > :minimum')
        ->fields(['sku','name','price_kopecks'])->bind(['state'=>'published','minimum'=>100])->execute();
    $empty = $emptyResult->fetchAll();
    $expected = [
        ['sku'=>'CABLE','name'=>'Кабель','price_kopecks'=>12000],
        ['sku'=>'LED','name'=>'LED-лампа','price_kopecks'=>12000],
    ];
    $fieldSets = [];
    foreach ($selected as $document) { $keys = array_keys($document); sort($keys, SORT_STRING); $fieldSets[] = $keys; }
    $rows = [['Всего карточек в учебном каталоге', $collection->count()], ['Карточек для витрины', count($selected)],
        ['Первый артикул', $selected[0]['sku']], ['Первое название', $selected[0]['name']], ['Первая цена, коп.', $selected[0]['price_kopecks']],
        ['Второй артикул', $selected[1]['sku']], ['Второе название', $selected[1]['name']], ['Вторая цена, коп.', $selected[1]['price_kopecks']],
        ['Только три нужных поля', $fieldSets === [['name','price_kopecks','sku'],['name','price_kopecks','sku']]],
        ['При остатке больше 100 карточек найдено', count($empty)]];
    $checks = ['source_count'=>$collection->count() === 5, 'selected_data'=>$selected == $expected,
        'stable_order'=>array_column($selected, 'sku') === ['CABLE','LED'],
        'projection'=>$fieldSets === [['name','price_kopecks','sku'],['name','price_kopecks','sku']],
        'excluded_rows'=>array_intersect(array_column($selected, 'sku'), ['DRAFT','EMPTY','LAMP']) === [],
        'empty_array'=>$empty === [], 'no_warnings'=>$result->getWarningsCount() === 0,
        'factory_types'=>$command instanceof mysql_xdevapi\CollectionFind && $result instanceof mysql_xdevapi\DocResult];
    $observations = ['input_documents'=>$documents, 'command_class'=>get_debug_type($command),
        'result_class'=>get_debug_type($result), 'selected_documents'=>$selected, 'field_sets'=>$fieldSets,
        'empty_result'=>$empty, 'warning_count'=>$result->getWarningsCount(),
        'selection'=>['state'=>'published','stock_greater_than'=>0,'order'=>['price_kopecks ASC','sku ASC'],'limit'=>2]];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x145Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x145Report('mysql145-x-factory-filtered-feed', 'Получить две доступные карточки для витрины без выгрузки всего каталога', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Всего карточек в учебном каталоге — 5
Карточек для витрины — 2
Первый артикул — CABLE
Первое название — Кабель
Первая цена, коп. — 12000
Второй артикул — LED
Второе название — LED-лампа
Вторая цена, коп. — 12000
Только три нужных поля — да
При остатке больше 100 карточек найдено — 0

Запускайте отдельным процессом PHP по README. Полный файл создаёт одну собственную коллекцию, выполняет показанные операции, перечитывает фактические документы и в finally удаляет только свой учебный объект. result.html показывает результат, result.json — документы и наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · Прочитанные документы и наблюдения.

Границы и ошибки

limit ограничивает число возвращаемых документов, но сам по себе не гарантирует дешёвое чтение сервером: для большого каталога нужен подходящий индекс и проверка плана. Сортируем по числовому полю в копейках, а не по форматированной строке «120 руб.». Данные запроса не подставляются в имена полей: набор fields и допустимые варианты sort выбирает приложение. В этом примере критерий наличия упрощён до stock > 0; он не заменяет резервирование при покупке. DocResult удерживается в переменной до чтения; каждый результат здесь читается одним способом, без смешивания курсорных вызовов.

Запись прошла, но сериализация служебного объекта результата в HTTP-ответ дала пустой объект вместо понятных данных. Задача на Stack Overflow. Отделяем квитанцию записи от выбранных документов. Сначала execute возвращает DocResult, затем fetchAll даёт PHP-массив конкретных полей. Именно данные массива превращаются в HTML или JSON; поведение сериализатора Node.js не объявляется свойством PHP.

Попробуйте изменить

Сделайте витрину из трёх товаров. Должны ли появиться DRAFT или EMPTY? Что изменится при минимальном остатке 100?

Ожидаемое решение

После limit(3) появится LAMP: он опубликован, доступен и дороже двух первых. DRAFT и EMPTY по-прежнему не проходят условия. При stock > 100 получится пустой массив []; это нормальный результат поиска, а не ошибка соединения.

Проверить карточку до и после команды публикации

Редактор утвердил новую цену LED-лампы: 135 рублей вместо 120, карточку можно опубликовать. Нужно изменить только этот товар, сохранив его остаток и соседний кабель. Отдельно посмотрим, что означает подготовленная, но ещё не выполненная команда, и почему исчезнувшая карточка не должна превращаться в сообщение «успешно обновлено».

Что происходит с данными
  1. LED: draft, цена 12000 копеек
  2. modify выбирает _id=LED; set подготавливает цену 13500 и published
  3. Чтение до execute по-прежнему даёт draft и 12000
  4. execute возвращает Result; новое чтение даёт published и 13500
  5. CABLE и stock LED не изменились
  6. MISSING даёт 0 совпадений и не создаёт документ

Сначала короткое действие

$change = $collection->modify('_id = :id')
    ->set('state', 'published')->bind(['id'=>'LED']);
// Пока только подготовили действие.
$receipt = $change->execute();
echo $collection->getOne('LED')['state']; // published

modify получает условие, не сам документ. set задаёт путь поля и новое значение; в примере это строка published. bind связывает :id со строкой LED. Метод execute вызывается у CollectionModify и возвращает отдельную квитанцию Result. Чтобы показать человеку новое состояние, перечитываем карточку через getOne. Полная версия меняет два поля одной командой и отдельно проверяет отсутствие совпадения для MISSING.

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

НаблюдениеРезультат
Цена до подготовки, коп.12000
Цена до execute, коп.12000
Статус до executedraft
Цена после execute, коп.13500
Статус после executepublished
Остаток сохранён3
Соседняя карточка не измениласьда
Найдено для изменения LED1
Найдено для отсутствующего ID0
Самостоятельный файл и архив для запуска
Проверить карточку до и после команды публикацииСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x145Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x145OwnCollection($schema, $owned, 'change_review');
    $collection = $schema->getCollection($name);
    $documents = [
        ['_id'=>'LED','sku'=>'LED','name'=>'LED-лампа','price_kopecks'=>12000,'stock'=>3,'state'=>'draft'],
        ['_id'=>'CABLE','sku'=>'CABLE','name'=>'Кабель','price_kopecks'=>5000,'stock'=>8,'state'=>'published'],
    ];
    foreach ($documents as $document) { $collection->add($document)->execute(); }
    $before = $collection->getOne('LED');
    $neighborBefore = $collection->getOne('CABLE');
    $command = $collection->modify('_id = :id')->set('price_kopecks', 13500)->set('state', 'published')->bind(['id'=>'LED']);
    $prepared = $collection->getOne('LED');
    $receipt = $command->execute();
    $after = $collection->getOne('LED');
    $neighborAfter = $collection->getOne('CABLE');
    $missingReceipt = $collection->modify('_id = :id')->set('price_kopecks', 15000)->bind(['id'=>'MISSING'])->execute();
    $afterMissing = $collection->getOne('LED');
    $rows = [['Цена до подготовки, коп.', $before['price_kopecks']], ['Цена до execute, коп.', $prepared['price_kopecks']],
        ['Статус до execute', $prepared['state']], ['Цена после execute, коп.', $after['price_kopecks']],
        ['Статус после execute', $after['state']], ['Остаток сохранён', $after['stock']],
        ['Соседняя карточка не изменилась', $neighborAfter === $neighborBefore],
        ['Найдено для изменения LED', $receipt->getAffectedItemsCount()],
        ['Найдено для отсутствующего ID', $missingReceipt->getAffectedItemsCount()]];
    $checks = ['not_executed'=>$prepared === $before, 'price_saved'=>$after['price_kopecks'] === 13500,
        'published'=>$after['state'] === 'published', 'retained_fields'=>$after['sku'] === 'LED' && $after['name'] === 'LED-лампа' && $after['stock'] === 3,
        'neighbor_unchanged'=>$neighborBefore === $neighborAfter,
        'affected'=>$receipt->getAffectedItemsCount() === 1, 'missing_no_match'=>$missingReceipt->getAffectedItemsCount() === 0,
        'missing_no_insert'=>$collection->count() === 2 && $afterMissing === $after,
        'factory_types'=>$command instanceof mysql_xdevapi\CollectionModify && $receipt instanceof mysql_xdevapi\Result];
    $observations = ['before'=>$before, 'after_preparation'=>$prepared, 'after_execute'=>$after,
        'neighbor_before'=>$neighborBefore, 'neighbor_after'=>$neighborAfter,
        'command_class'=>get_debug_type($command), 'receipt_class'=>get_debug_type($receipt),
        'affected'=>$receipt->getAffectedItemsCount(), 'missing_affected'=>$missingReceipt->getAffectedItemsCount(), 'after_missing'=>$afterMissing];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x145Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x145Report('mysql145-x-factory-change-review', 'Проверить карточку до и после команды публикации', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Цена до подготовки, коп. — 12000
Цена до execute, коп. — 12000
Статус до execute — draft
Цена после execute, коп. — 13500
Статус после execute — published
Остаток сохранён — 3
Соседняя карточка не изменилась — да
Найдено для изменения LED — 1
Найдено для отсутствующего ID — 0

Запускайте отдельным процессом PHP по README. Полный файл создаёт одну собственную коллекцию, выполняет показанные операции, перечитывает фактические документы и в finally удаляет только свой учебный объект. result.html показывает результат, result.json — документы и наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · Прочитанные документы и наблюдения.

Границы и ошибки

Число affected само по себе не доказывает, что все значения стали другими; итог проверяется чтением. Условие по _id выбирает уникальный документ, но не защищает от параллельного редактирования: для него нужна версия в условии или транзакционная стратегия. Это демонстрация действия редактора, а не готовый HTTP-контроллер: аутентификация, право редактирования, CSRF и валидация цены должны предшествовать записи. set передаёт готовое число; вычисление новой цены по прежней требует отдельного Expression и объяснения конкуренции. Ошибка сервера не равна нормальным нулю совпадений.

Настройка modify и set завершилась без ошибки, но карточка не изменилась: в вопросе не был вызван execute. Задача на Stack Overflow. В нашем PHP-примере подготовленная команда сохраняется в переменную, затем документ читается до и после execute. Поэтому граница между описанием действия и его выполнением видна в данных. Синтаксис Node.js из обсуждения не переносится в PHP.

Попробуйте изменить

После подготовки команды измените её условие на отсутствующий ID. Можно ли выводить новую цену сразу из переменной формы?

Ожидаемое решение

Нет. execute даст ноль совпадений и не создаст карточку. Интерфейс должен сообщить, что документ не найден или условие сохранения больше не выполнено. Значение формы — только намерение; оно не доказывает, что база его сохранила.

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

В учебном каталоге накопились два старых черновика. Администратор хочет убрать именно их, оставив свежий черновик, опубликованный товар и архивную карточку. Сначала покажем кандидатов, потом выполним ограниченную команду удаления и сравним все оставшиеся документы с исходными.

Что происходит с данными
  1. 5 документов: два старых draft, один новый draft, published и archived
  2. find показывает только draft с updated раньше 2026-01-01
  3. remove готовит то же условие, сортировку sku и предел 2
  4. До execute все 5 документов существуют
  5. execute удаляет OLD-A и OLD-B
  6. Чтение подтверждает ARCHIVE, KEEP, NEW и сохранность их полей

Сначала короткое действие

$remove = $collection->remove('_id = :id')->bind(['id'=>'OLD-A']);
$receipt = $remove->execute();
echo $receipt->getAffectedItemsCount(); // 1, если OLD-A существовал

Для первого шага условие по _id удаляет один известный документ. В полной программе AND соединяет два требования: статус draft и дата раньше cutoff. Значения передаются через bind, ASC задаёт порядок артикулов, limit(2) ограничивает порцию. Даты во всех учебных документах записаны одинаково как YYYY-MM-DD: для этих корректных строк лексикографический порядок соответствует календарному. Предпросмотр и удаление — две самостоятельные команды; объект CollectionRemove не является результатом поиска.

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

НаблюдениеРезультат
До удаления карточек5
Предпросмотр кандидатовOLD-A, OLD-B
До execute карточек5
Удалено карточек2
После удаления карточек3
Остались артикулыARCHIVE, KEEP, NEW
Опубликованный товар сохранёнда
Свежий черновик сохранёнда
Оставшиеся документы не измененыда
Самостоятельный файл и архив для запуска
Удалить только устаревшие черновики и показать оставшиеся карточкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x145Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x145OwnCollection($schema, $owned, 'draft_removal');
    $collection = $schema->getCollection($name);
    $documents = [
        ['_id'=>'OLD-A','sku'=>'OLD-A','state'=>'draft','updated'=>'2025-10-01'],
        ['_id'=>'OLD-B','sku'=>'OLD-B','state'=>'draft','updated'=>'2025-12-15'],
        ['_id'=>'NEW','sku'=>'NEW','state'=>'draft','updated'=>'2026-10-01'],
        ['_id'=>'KEEP','sku'=>'KEEP','state'=>'published','updated'=>'2024-01-01'],
        ['_id'=>'ARCHIVE','sku'=>'ARCHIVE','state'=>'archived','updated'=>'2024-01-01'],
    ];
    foreach ($documents as $document) { $collection->add($document)->execute(); }
    $before = $collection->find('true')->sort('sku ASC')->execute()->fetchAll();
    $cutoff = '2026-01-01';
    $candidateResult = $collection->find('state = :state AND updated < :cutoff')
        ->fields(['sku','updated'])->sort('sku ASC')->limit(2)->bind(['state'=>'draft','cutoff'=>$cutoff])->execute();
    $candidates = $candidateResult->fetchAll();
    $command = $collection->remove('state = :state AND updated < :cutoff')
        ->sort('sku ASC')->limit(2)->bind(['state'=>'draft','cutoff'=>$cutoff]);
    $prepared = $collection->find('true')->sort('sku ASC')->execute()->fetchAll();
    $receipt = $command->execute();
    $remaining = $collection->find('true')->sort('sku ASC')->execute()->fetchAll();
    $preserved = [];
    foreach ($before as $document) {
        if (!in_array($document['sku'], ['OLD-A','OLD-B'], true)) { $preserved[] = $document; }
    }
    $rows = [['До удаления карточек', count($before)], ['Предпросмотр кандидатов', implode(', ', array_column($candidates, 'sku'))],
        ['До execute карточек', count($prepared)], ['Удалено карточек', $receipt->getAffectedItemsCount()],
        ['После удаления карточек', count($remaining)], ['Остались артикулы', implode(', ', array_column($remaining, 'sku'))],
        ['Опубликованный товар сохранён', $collection->getOne('KEEP')['state'] === 'published'],
        ['Свежий черновик сохранён', $collection->getOne('NEW')['state'] === 'draft'],
        ['Оставшиеся документы не изменены', $remaining === $preserved]];
    $checks = ['preview_ids'=>array_column($candidates, 'sku') === ['OLD-A','OLD-B'],
        'deferred_remove'=>$prepared === $before, 'affected'=>$receipt->getAffectedItemsCount() === 2,
        'remaining_ids'=>array_column($remaining, 'sku') === ['ARCHIVE','KEEP','NEW'],
        'preserved_documents'=>$remaining === $preserved, 'count_after'=>count($remaining) === 3,
        'factory_types'=>$command instanceof mysql_xdevapi\CollectionRemove && $receipt instanceof mysql_xdevapi\Result];
    $observations = ['cutoff'=>$cutoff, 'before'=>$before, 'candidates'=>$candidates,
        'after_preparation'=>$prepared, 'remaining'=>$remaining, 'command_class'=>get_debug_type($command),
        'receipt_class'=>get_debug_type($receipt), 'affected'=>$receipt->getAffectedItemsCount(),
        'selection'=>['state'=>'draft','updated_before'=>$cutoff,'order'=>'sku ASC','limit'=>2]];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x145Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x145Report('mysql145-x-factory-draft-removal', 'Удалить только устаревшие черновики и показать оставшиеся карточки', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
До удаления карточек — 5
Предпросмотр кандидатов — OLD-A, OLD-B
До execute карточек — 5
Удалено карточек — 2
После удаления карточек — 3
Остались артикулы — ARCHIVE, KEEP, NEW
Опубликованный товар сохранён — да
Свежий черновик сохранён — да
Оставшиеся документы не изменены — да

Запускайте отдельным процессом PHP по README. Полный файл создаёт одну собственную коллекцию, выполняет показанные операции, перечитывает фактические документы и в finally удаляет только свой учебный объект. result.html показывает результат, result.json — документы и наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · Прочитанные документы и наблюдения.

Границы и ошибки

Предпросмотр не блокирует документы и не гарантирует ту же выборку при конкурирующем редакторе. Для удаления именно просмотренных записей сохраняют проверенные ID и при необходимости версии, повторяют ограничения или применяют транзакцию; этот пример запускается без другого писателя в собственной коллекции. Не превращайте пользовательский фильтр в remove("true"). limit ограничивает порцию, но не заменяет точное условие и права. Старый published специально остаётся: возраст записи сам по себе не даёт основания её удалить. На рабочем каталоге может быть уместнее архивирование, а не физическое удаление.

Попробуйте изменить

Добавьте ещё один старый черновик OLD-C, сохранив limit(2). Какие карточки уйдут за один запуск и как безопаснее обработать оставшуюся порцию?

Ожидаемое решение

При сортировке sku ASC уйдут OLD-A и OLD-B, OLD-C останется. Следующий проход снова выбирает первые подходящие записи без растущего offset: удалённые документы уже вышли из выборки. Для промышленного процесса нужны конечный размер пакета, журнал и правила конкуренции; интерфейс предварительного подтверждения должен показывать конкретные ID.

Семь конструкторов и их публичные фабрики

Официальный узелДля чего нужен правильный маршрут
Schema::__constructВыбрать именованную учебную базу, внутри которой находятся коллекции и обычные таблицы.
Collection::__constructПолучить инструмент для сохранения и поиска документов конкретного каталога.
CollectionAdd::__constructПодготовить добавление одного или нескольких товаров и отдельно получить квитанцию записи.
CollectionFind::__constructОписать нужную выдачу каталога: условия, поля, порядок и размер ответа.
CollectionModify::__constructПодготовить изменение выбранной карточки и увидеть, когда оно действительно сохраняется.
CollectionRemove::__constructУдалить определённые документы, сохранив остальные записи каталога.
DocResult::__constructПревратить ответ поиска документов в конкретные данные для витрины или списка проверки.

От команды к надёжному результату

Теперь каталог сохраняет карточки, отдаёт витрине нужные поля и отличает подготовленное действие от выполненного. Следующий вопрос — какие сведения возвращает квитанция и почему отсутствие исключения не всегда означает сохранение ровно того значения, которое ввёл человек. Продолжите в результатах, идентификаторах и предупреждениях операций. Для вложенных изменений используйте set, patch и ограниченные порции, а для обычных таблиц — фабрики табличных команд.

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

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

mysql_xdevapi\Schema::__constructВыбрать именованную учебную базу, внутри которой находятся коллекции и обычные таблицы.

На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. · mysql_xdevapi

private mysql_xdevapi\Schema::__construct()

Выбрать именованную учебную базу, внутри которой находятся коллекции и обычные таблицы.

Путь получения и использования
  1. Session уже подключена к серверу
  2. getSchema получает имя подготовленной учебной базы
  3. Schema обозначает эту базу, а не создаёт её
  4. createCollection создаёт каталог внутри неё; getCollection получает существующий адрес

Закрытый конструктор и параметры

private mysql_xdevapi\Schema::__construct()

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

Публичный маршрутЧто передаём и получаем
$session->getSchema($schemaName)Строка schemaName — имя базы. Фабрика возвращает Schema, связанный с уже открытой Session. Она не выполняет CREATE DATABASE.
$schema->existsInDatabase()Отдельно проверяет существование базы. Сам факт получения PHP-объекта не подтверждает её наличие или права на каждое дальнейшее действие.

Возвращаемое значение. У конструктора нет самостоятельно используемого возвращаемого значения; отдельный раздел Return Values на официальной странице отсутствует. Публичный getSchema возвращает объект Schema. Через него получают таблицы и коллекции, проверяют существование и выполняют поддерживаемые операции со схемой.

Два практических применения

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

$schema = $session->getSchema($schemaName);
if (!$schema->existsInDatabase()) {
    throw new RuntimeException('Сначала подготовьте учебную базу.');
}
$catalog = $schema->getCollection($collectionName);

Исходные данные, самостоятельный архив и результат.

На витрине выбираем ту же настроенную базу, но только читаем нужные карточки. Для каждого HTTP-запроса не нужно заново создавать схему и каталог.

$schema = $session->getSchema($schemaName);
$catalog = $schema->getCollection($collectionName);
$cards = $catalog->find('state = :state')
    ->fields(['sku','name'])->bind(['state'=>'published'])
    ->execute()->fetchAll();

Исходные данные, самостоятельный архив и результат.

Ограничения, ошибки и выбор другого действия

__construct закрыт (private), не принимает параметров и не предназначен для new Schema из прикладного кода. Параметр имени относится к getSchema, а не к этому конструктору. Ошибки подключения, отсутствующая схема и отказ в правах относятся к публичным операциям и серверу; получение handle не доказывает готовность всей цепочки. Не копируйте DROP DATABASE из демонстрационного окружения официальной страницы в свой проект. В нашем запуске база подготовлена отдельно, а удаляется только собственная случайно названная коллекция.

Официальный пример после отдельного создания базы получает Schema, создаёт коллекцию и обходит таблицы и коллекции. Эти виды объектов и их перечисление разобраны в навигации по объектам базы. Правильное закрытие Session — жизненный цикл соединения.

Версии. На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol.

mysql_xdevapi\Collection::__constructПолучить инструмент для сохранения и поиска документов конкретного каталога.

На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. · mysql_xdevapi

private mysql_xdevapi\Collection::__construct()

Получить инструмент для сохранения и поиска документов конкретного каталога.

Путь получения и использования
  1. Schema знает учебную базу
  2. Имя каталога выбирает одну коллекцию
  3. getCollection даёт handle; createCollection отдельно создаёт хранение
  4. add/find/modify/remove готовят действия над документами этой коллекции

Закрытый конструктор и параметры

private mysql_xdevapi\Collection::__construct()

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

Публичный маршрутЧто передаём и получаем
$schema->getCollection($name)name — строковое имя коллекции. Возвращается Collection, связанный со схемой; отсутствие коллекции проверяется отдельно.
$schema->createCollection($name)Создаёт новую коллекцию и возвращает её Collection. Наши примеры используют один обязательный аргумент и отдельное случайное имя; это действие меняет базу.
$collection->getOne($id)После сохранения по настоящему строковому _id читают документ. Это чтение, а не способ создать Collection.

Возвращаемое значение. Конструктор не предоставляет самостоятельного результата. Публичные getCollection и createCollection дают Collection; второй маршрут также создаёт объект в базе. Collection не является массивом всех товаров: он предоставляет методы работы с выбранным набором документов.

Два практических применения

При первоначальной подготовке нового каталога нужно именно создать коллекцию. Здесь newName — проверенное новое учебное имя. Самостоятельная программа регистрирует только созданный ею объект для уборки.

$handle = $schema->getCollection($newName);
if ($handle->existsInDatabase()) {
    throw new RuntimeException('Учебное имя уже занято.');
}
$catalog = $schema->createCollection($newName);

Исходные данные, самостоятельный архив и результат.

Редактор уже существующего каталога получает handle и открывает конкретную карточку. Он не должен выполнять повторное createCollection при каждом редактировании.

$catalog = $schema->getCollection($collectionName);
$card = $catalog->getOne('LED');
echo $card['name']; // LED-лампа в учебных данных

Исходные данные, самостоятельный архив и результат.

Ограничения, ошибки и выбор другого действия

Конструктор private, параметров нет, версия появления на странице не указана. Имена схемы и коллекции относятся к фабрикам, а не к new Collection. Handle не подтверждает существование хранилища; чтение отсутствующего документа и запрос к отсутствующей коллекции — разные ситуации. В официальном примере заголовок упоминает getOne: его смысл — добавить документ, получить созданный ID из Result и перечитать документ. Значение ID в примере является образцом одного запуска, а не константой для вашего каталога.

Проверка родителей, существования и отличия коллекции от таблицы: маршрут Schema и Collection. Показанное здесь создание не вводит произвольные SQL-идентификаторы из формы пользователя.

Версии. На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol.

mysql_xdevapi\CollectionAdd::__constructПодготовить добавление одного или нескольких товаров и отдельно получить квитанцию записи.

На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. · mysql_xdevapi

private mysql_xdevapi\CollectionAdd::__construct()

Подготовить добавление одного или нескольких товаров и отдельно получить квитанцию записи.

Путь получения и использования
  1. PHP-массив CABLE содержит sku и цену 12000 копеек
  2. Collection::add сохраняет намерение добавить этот документ
  3. CollectionAdd::add добавляет LAMP в ту же команду
  4. execute отправляет пакет
  5. Result → generated IDs → getOne → реальные карточки

Закрытый конструктор и параметры

private mysql_xdevapi\CollectionAdd::__construct()

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

Публичный маршрутЧто передаём и получаем
$collection->add($document)document — поддерживаемое представление документа, в наших программах ассоциативный PHP-массив. В официальных примерах также используются JSON-строки.
$collection->add($first, $second)Несколько документов можно передать отдельными аргументами. Это не один JSON-объект, содержащий два безымянных документа.
$command->add($next)Добавляет ещё документ к подготовленной команде CollectionAdd. В полном запуске две карточки подготавливаются до execute.
$command->execute()Выполняет добавление. Возвращаемый Result — отдельный объект с квитанцией, числом затронутых документов и сгенерированными ID.

Возвращаемое значение. Приватный конструктор сам по себе результата не предоставляет. Collection::add возвращает CollectionAdd; execute этой команды возвращает Result. Объект команды не является подтверждением сохранения. getGeneratedIds у полученного Result возвращает ID документов именно соответствующей операции.

Два практических применения

После сохранения одной карточки редактору нужен её адрес. Получаем ID от квитанции и проверяем документ чтением, вместо того чтобы искать табличный AUTO_INCREMENT.

$document = ['sku'=>'CABLE', 'price_kopecks'=>12000];
$receipt = $collection->add($document)->execute();
$ids = $receipt->getGeneratedIds();
$saved = $collection->getOne($ids[0]);

Исходные данные, самостоятельный архив и результат.

Небольшой импорт готовит несколько документов перед выполнением. Полный пример показывает пустой каталог до execute, две сохранённые карточки после него и проверку каждого возвращённого ID.

$first = ['sku'=>'CABLE', 'price_kopecks'=>12000];
$second = ['sku'=>'LAMP', 'price_kopecks'=>89000];
$batch = $collection->add($first);
$batch->add($second);
$receipt = $batch->execute();

Исходные данные, самостоятельный архив и результат.

Ограничения, ошибки и выбор другого действия

__construct private, без параметров; документы передаются в add, а не в конструктор. На странице конструктора нет сведений о версии. Сохранена её отдельная заметка: MySQL 8.0 и новее может создавать уникальный _id, а для MySQL 5.7 пример требует явный _id. Эти уроки не проверяют старый сервер. Некорректный документ, конфликт явного _id или отказ сервера не следует превращать в сообщение об успехе; большой импорт требует порций и собственной политики повторов. Квитанция прежней операции не переключается на последующие запросы. Отдельный вызов add не даёт idempotency повторному импорту.

Официальный пример сначала вставляет документы по одному, затем передаёт две JSON-строки отдельными аргументами и читает ID последней выполненной команды. Комментарий о «единственном JSON-объекте» не соответствует этому вызову; здесь показаны обе осмысленные формы подготовки без копирования этой неточности. Сериализация массива объясняется в главе о JSON, а сохранение частичных изменений — patch и set.

Версии. На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol.

mysql_xdevapi\CollectionFind::__constructОписать нужную выдачу каталога: условия, поля, порядок и размер ответа.

На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. · mysql_xdevapi

private mysql_xdevapi\CollectionFind::__construct()

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

Путь получения и использования
  1. Пять товаров хранятся на сервере
  2. find получает условия published и stock > 0
  3. fields, sort и limit описывают нужный экран
  4. execute возвращает DocResult
  5. fetchAll даёт выбранные CABLE и LED для отображения

Закрытый конструктор и параметры

private mysql_xdevapi\CollectionFind::__construct()

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

Публичный маршрутЧто передаём и получаем
$collection->find($condition)condition — строка выражения поиска; публичная фабрика допускает отсутствие условия. Для прикладных выборок здесь условие указано явно.
->bind(['state'=>'published'])Передаёт значение для :state отдельно от выражения. Это не подстановка имени поля или готового фрагмента языка запросов.
->fields(['sku','name'])->sort('sku ASC')->limit(2)Настройки одной команды: какие поля вернуть, в каком порядке и сколько документов. Имена и выражения задаёт приложение.
$query->execute()Выполняет чтение и возвращает DocResult; документы затем получают методами результата.

Возвращаемое значение. Конструктор не имеет самостоятельного возвращаемого значения. find даёт CollectionFind; execute даёт DocResult; fetchAll даёт PHP-массив документов. Эти три значения выполняют разные обязанности и не взаимозаменяемы.

Два практических применения

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

$query = $collection->find('state = :state AND stock > :minimum')
    ->fields(['sku','name','price_kopecks'])
    ->sort('price_kopecks ASC','sku ASC')->limit(2)
    ->bind(['state'=>'published','minimum'=>0]);
$cards = $query->execute()->fetchAll();

Исходные данные, самостоятельный архив и результат.

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

$query = $collection->find('state = :state AND updated < :cutoff')
    ->fields(['sku','updated'])->sort('sku ASC')->limit(2)
    ->bind(['state'=>'draft','cutoff'=>'2026-01-01']);
$preview = $query->execute()->fetchAll();

Исходные данные, самостоятельный архив и результат.

Ограничения, ошибки и выбор другого действия

__construct private и без параметров. Официальная страница прямо предупреждает о недостаточной документации самого конструктора; это не основание выдумывать public new или дополнительные параметры. Фабрики и выборка раскрыты по отдельным официальным страницам и проверяемому профилю. find без ограничений может вернуть большой набор: не используйте его на витрине, чтобы затем сортировать всё в PHP. Предпросмотр не блокирует данные. Для быстрого запроса нужен подходящий индекс; limit ограничивает ответ, но не гарантирует число просмотренных сервером документов.

Официальный пример сочетает условия job и age, передаёт их через bind и читает результат. Здесь та же механика развивается в витрину и предварительный список. Индексы и группировки: серверные выборки и индексы.

Версии. На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol.

mysql_xdevapi\CollectionModify::__constructПодготовить изменение выбранной карточки и увидеть, когда оно действительно сохраняется.

На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. · mysql_xdevapi

private mysql_xdevapi\CollectionModify::__construct()

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

Путь получения и использования
  1. Редактор выбрал LED
  2. modify получает условие по _id
  3. set подготавливает state и цену
  4. До execute чтение показывает прежние значения
  5. После execute Result сообщает итог, а getOne подтверждает реальные новые поля

Закрытый конструктор и параметры

private mysql_xdevapi\CollectionModify::__construct()

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

Публичный маршрутЧто передаём и получаем
$collection->modify($condition)Обязательная строка condition определяет документы. Возвращается CollectionModify, а не изменённая карточка.
->set('state', 'published')Путь поля и новое значение — параметры операции set; это не параметры приватного конструктора.
->bind(['id'=>'LED'])Передаёт конкретное значение для :id в условии. Для этого урока _id точно выбирает одну карточку.
$change->execute()Выполняет подготовленные изменения и возвращает Result. Фактический документ после записи читается отдельным запросом.

Возвращаемое значение. У конструктора нет самостоятельного результата. modify возвращает CollectionModify, execute возвращает Result. Квитанция описывает выполненную операцию; она не содержит автоматически новую карточку для редактора.

Два практических применения

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

$change = $collection->modify('_id = :id')
    ->set('state', 'published')->bind(['id'=>'LED']);
$receipt = $change->execute();
$saved = $collection->getOne('LED');

Исходные данные, самостоятельный архив и результат.

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

$change = $collection->modify('_id = :id')
    ->set('price_kopecks', 13500)->bind(['id'=>'LED']);
$receipt = $change->execute();
echo $collection->getOne('LED')['price_kopecks']; // 13500

Исходные данные, самостоятельный архив и результат.

Ограничения, ошибки и выбор другого действия

Конструктор private, параметров нет, сведения о версии отсутствуют. Он не заменяет modify и не выполняет запись при создании объекта. Официальный пример дописывает метку в массив hobbies через arrayAppend, а затем читает документ. Это частный вид изменения; set, patch и работа с вложенными массивами имеют разные правила. Ноль совпадений при отсутствующем ID не равен серверной ошибке и не создаёт новый документ. Счётчик affected не заменяет проверку нужных значений; для защиты от потери параллельной правки нужны дополнительные условия или транзакции.

Вложенные поля, массивы, patch и его отличие от set разобраны отдельно: частичное изменение документа. Здесь фокус на получении настоящей команды и границе execute.

Версии. На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol.

mysql_xdevapi\CollectionRemove::__constructУдалить определённые документы, сохранив остальные записи каталога.

На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. · mysql_xdevapi

private mysql_xdevapi\CollectionRemove::__construct()

Удалить определённые документы, сохранив остальные записи каталога.

Путь получения и использования
  1. Предпросмотр выбрал OLD-A и OLD-B как старые draft
  2. remove описал те же условия и предел 2
  3. До execute обе карточки существуют
  4. execute возвращает квитанцию удаления
  5. Чтение показывает неизменные ARCHIVE, KEEP и NEW

Закрытый конструктор и параметры

private mysql_xdevapi\CollectionRemove::__construct()

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

Публичный маршрутЧто передаём и получаем
$collection->remove($condition)Обязательная непустая строка условия определяет кандидатов. Возвращает CollectionRemove, а не уже выполненное удаление.
->bind(['id'=>'OLD-A'])Передаёт значение для placeholder условия; не даёт пользователю права удалять произвольную карточку.
->sort('sku ASC')->limit(2)Задаёт порядок и максимальный размер порции. Применяется вместе с точным условием, не вместо него.
$remove->execute()Отправляет удаление, возвращает Result. getAffectedItemsCount относится к этой операции; остаток коллекции проверяется чтением.

Возвращаемое значение. Приватный конструктор не возвращает используемого значения. remove даёт CollectionRemove, execute даёт Result. Для подтверждения оставшихся данных используют отдельный CollectionFind/DocResult, а не сам объект удаления.

Два практических применения

После подтверждения удаления одного черновика используем его точный ID. Нормальное отсутствие совпадения следует отличать от исключения сервера.

$remove = $collection->remove('_id = :id')->bind(['id'=>'OLD-A']);
$receipt = $remove->execute();
echo $receipt->getAffectedItemsCount(); // 1 либо 0, если уже отсутствует

Исходные данные, самостоятельный архив и результат.

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

$remove = $collection->remove('state = :state AND updated < :cutoff')
    ->sort('sku ASC')->limit(2)
    ->bind(['state'=>'draft','cutoff'=>'2026-01-01']);
$receipt = $remove->execute();

Исходные данные, самостоятельный архив и результат.

Ограничения, ошибки и выбор другого действия

Конструктор private, аргументов нет; версия на странице не указана. Условие передаётся фабрике remove. Официальная страница показывает цепочку удалений; после первых двух операций её исходные два документа уже исчезли, поэтому последняя не демонстрирует обещанный выбор. Кроме того, комментарий «самый молодой» не соответствует age DESC: это сортировка возраста по убыванию. В собственном сценарии достаточно исходных данных и проверяется каждое оставшееся значение. Удаление нельзя заменять remove("true") из-за неудобного фильтра. Для ценных данных рассмотрите архивирование, журнал и восстановление; предварительное чтение не является блокировкой.

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

Версии. На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol.

mysql_xdevapi\DocResult::__constructПревратить ответ поиска документов в конкретные данные для витрины или списка проверки.

На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. · mysql_xdevapi

private mysql_xdevapi\DocResult::__construct()

Превратить ответ поиска документов в конкретные данные для витрины или списка проверки.

Путь получения и использования
  1. CollectionFind содержит фильтр и проекцию
  2. execute получает ответ сервера как DocResult
  3. fetchAll читает документы в PHP-массив
  4. foreach видит sku, name и price_kopecks каждой карточки
  5. Пустой массив даёт экран без подходящих товаров, а не ложное сообщение об ошибке

Закрытый конструктор и параметры

private mysql_xdevapi\DocResult::__construct()

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

Публичный маршрутЧто передаём и получаем
$query->execute()query — подготовленный CollectionFind. В обычном синхронном чтении возвращается DocResult.
$result->fetchAll()Получает документы как PHP-массив; каждый документ представлен своим ассоциативным массивом. Если совпадений нет, получается [].
$result->getWarningsCount() / getWarnings()У результата есть диагностика предупреждений. Отсутствие строк само по себе не означает предупреждение или исключение.

Возвращаемое значение. Конструктор без собственного результата и недоступен извне. Объект DocResult выдаёт выполненный поиск. Он относится к ответу сервера, а массив от fetchAll — уже прикладные данные. Для изменения или удаления обычно нужен другой тип результата: Result, а не DocResult.

Два практических применения

На витрине выводим данные выбранных карточек. В HTML каждое внешнее строковое значение нужно экранировать; общий отчёт полного примера делает это до вывода ячеек.

$result = $collection->find('state = :state')
    ->fields(['sku','name'])->bind(['state'=>'published'])->execute();
$cards = $result->fetchAll();
foreach ($cards as $card) {
    echo $card['sku'], ': ', $card['name'], "\n";
}

Исходные данные, самостоятельный архив и результат.

Фильтр большого остатка может не найти ничего. Это нормальный пустой экран: сообщение строится по массиву данных, а не по наличию объекта DocResult.

$result = $collection->find('stock > :minimum')
    ->fields(['sku','name'])->bind(['minimum'=>100])->execute();
$cards = $result->fetchAll();
if ($cards === []) {
    echo 'Подходящих товаров нет';
}

Исходные данные, самостоятельный архив и результат.

Ограничения, ошибки и выбор другого действия

__construct private, без параметров; информация о версии отсутствует. Официальный пример выбирает butler старше указанного возраста, сортирует по age DESC, ограничивает одной записью и читает её; это получение конкретного ответа, а не ручное создание DocResult. Не сериализуйте служебный объект в надежде автоматически получить товары: сначала вызовите документированный метод чтения. Не называйте любой результат execute «массивом». В проверяемом расширении есть особенности буферного чтения; эта глава удерживает результат в переменной и читает его одним fetchAll, не приписывая ему курсорную семантику PDO.

Обычное чтение полей отличается от метаданных SQL-столбцов. JSON-объект каталога и объект PHP DocResult тоже различаются: первый хранит данные предметной области, второй представляет ответ запроса.

Версии. На официальной странице конструктора информация о версии отсутствует. Примеры предназначены для PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol.

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

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

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

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

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