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

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

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

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

Глава 129 · После первой коллекции и базовых операций изменения

MySQL X DevAPI: patch, замена полей и ограниченные порции документов

От названия одной карточки до вложенного Merge Patch, различия null и удаления, замены существующих полей, устойчивых порций и осмысленных CRUD-интерфейсов.

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

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

ПолеДоПосле изменения названия
_idLEDLED
nameСветильникЛампа
skuLED-01LED-01
stock44

Сначала представим карточку обычным ассоциативным массивом PHP. Ключ — имя поля, значение — его содержимое. Здесь вложенности пока нет: одна запись состоит из четырёх простых полей. Массив $changes содержит только изменение, а не копию всей карточки.

$card = ['_id'=>'LED', 'name'=>'Светильник', 'sku'=>'LED-01', 'stock'=>4];
$changes = ['name'=>'Лампа'];
echo $card['stock']; // 4
echo $changes['name']; // Лампа

Пока мы работаем только с переменными: база не изменилась. Чтобы передать документ изменений в patch, получим JSON-строку. json_encode сохраняет структуру массива и правильно записывает кавычки внутри пользовательского текста; вручную склеивать JSON не нужно. JSON_THROW_ON_ERROR просит PHP сообщить об ошибке преобразования, а JSON_UNESCAPED_UNICODE оставляет русский текст читаемым.

$patchJson = json_encode($changes, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
echo $patchJson; // {"name":"Лампа"}

Первое изменение в коллекции

Пусть $collection уже указывает на учебную коллекцию, в которой есть документ LED. modify задаёт условие, :id обозначает место отдельного значения, bind передаёт LED. patch описывает поля изменения. execute отправляет подготовленную операцию серверу. getOne после этого читает сохранённую карточку — так мы видим результат записи.

$operation = $collection->modify('_id = :id');
$operation->patch($patchJson);
$operation->bind(['id'=>'LED']);
$receipt = $operation->execute();
$saved = $collection->getOne('LED');
echo $saved['name']; // Лампа
echo $saved['stock']; // 4
От частичного ввода к сохранённой карточке
  1. name = Лампа попадает в массив изменений
  2. json_encode создаёт JSON-объект только с name
  3. modify + bind выбирают документ LED
  4. patch настраивает изменение выбранного документа
  5. execute выполняет запись и возвращает квитанцию Result
  6. getOne читает name = Лампа и сохранённый stock = 4

Эту цепочку можно представить как бланк задания: условие отвечает «какую карточку», patch — «что в ней исправить», execute — «выполнить», а Result — квитанция. Аналогия не обещает блокировок или прав доступа: эти правила нужно задать в приложении. Сам объект операции ещё не является сохранённым документом.

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

Какой способ изменения нужен

ПотребностьОперацияЧто произойдёт
Пришли несколько разрешённых полей формыpatchНеуказанные поля сохранятся; объекты объединятся рекурсивно
Нужно заменить только существующее полеreplaceОтсутствующий путь не будет создан
Нужно присвоить поле, даже если его ещё нетsetНовое поле добавится; JSON null можно сохранить как значение
Нужно заменить документ целикомreplaceOneНужно подготовить весь новый документ по правилам этого метода
Нужно удалить отдельное полеunset / patch с nullПоля не будет в документе
Нужно добавить элемент в массивarrayAppendСписок расширится; patch всего массива заменил бы его целиком
Нужно обработать ограниченную порциюsort + limitСервер выберет первые совпадения в указанном порядке

Особенно важны два различия. В Merge Patch null удаляет ключ; это не способ сохранить JSON null. А массив tags заменяется целиком: Merge Patch не ищет внутри него товар с нужным ID. Во второй программе эти правила показаны на одном документе до и после каждого действия. Формат JSON Patch с операциями op/path/value — другой формат, здесь он не используется.

Число getAffectedItemsCount не заменяет проверку бизнес-результата. В проверенной версии replace отсутствующего пути вернул 1, но поле не появилось. Поэтому пример сохраняет квитанцию и отдельно перечитывает документ. Для большого набора проверку проектируют под задачу: здесь используются только собственные маленькие учебные коллекции, а не выгрузка рабочего каталога целиком.

Порция, окно и общий контракт операции

sort и limit позволяют выбрать порцию на стороне базы. Мы не читаем сотни тысяч документов ради сортировки PHP-массива. При равном приоритете добавляем уникальный _id, чтобы порядок первой порции был однозначным. Если обработанные документы выходят из условия pending, следующую порцию снова берём с начала оставшихся совпадений.

Метод skip входит в официальный справочник и реализованный PHP-интерфейс. Однако проверенная связка PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 отклоняет ненулевой offset именно при изменении. Пятая программа показывает точный отказ 5012, проверяет неизменность данных и выполняет рабочий вариант: выбирает ограниченный список ID через find с offset, затем изменяет эти документы. Это окно чтения и записи не становится автоматически атомарным; конкурентная обработка разобрана в главе о двух сеансах и согласованности.

Последняя программа вводит интерфейс через конкретную потребность: функция привязки должна уметь вызвать bind, а функция выполнения — execute. Сначала читатель видит обычную операцию и её результат, затем типы параметров этих двух функций. Контракт Executable сообщает доступный метод, но не делает все объекты результата одинаковыми. Подробности каждого интерфейса сохранены в справочнике этой главы.

Запуск и файлы результата

Нужны PHP 8.3.35 с PECL mysql_xdevapi 8.0.30, MySQL 8.4.13 с X Protocol и отдельная учебная схема. Это зафиксированный профиль примеров, а не утверждение о последних версиях. Параметры берутся из PHPMAN_MYSQLX_URI и PHPMAN_MYSQLX_SCHEMA. Пароль не включается в файлы. README описывает подготовку, права и CLI-запуск; lab.php проверяет окружение и создаёт только собственные коллекции с префиксом mysql143_… .

После выполнения result.html показывает реальные наблюдения, result.json сохраняет исходные документы, квитанции и перечитанные состояния, metrics.json фиксирует проверки и удаление собственных объектов. Шестой архив дополнительно содержит contracts.php с двумя обычными PHP-функциями. Форма первой программы моделируется массивом значений, а не выдаётся за готовый HTTP-обработчик; полноценная проверка ввода и доступа добавляется на уровне приложения.

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

Редактор меняет название и статус светильника. У карточки уже есть артикул и остаток, которых в форме нет: их нельзя потерять при сохранении частичного ввода. Сначала изменим только name, затем передадим две проверенные настройки одним patch; соседняя карточка кабеля останется прежней.

Путь данных этой программы
  1. LED: name Светильник, status draft, stock = 4
  2. patch содержит только name → Лампа
  3. Два поля формы → JSON-объект name/status
  4. Условие _id=:id + bind LED выбирает один документ
  5. execute сохраняет изменения; getOne читает фактическую карточку

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

$collection->modify('_id = :id')
    ->patch('{"name":"Лампа"}')
    ->bind(['id'=>'LED'])->execute();
echo $collection->getOne('LED')['name']; // Лампа

modify задаёт условие выбора, :id — место для отдельного значения, bind передаёт строку LED. patch принимает JSON-строку: внешние одинарные кавычки относятся к PHP, внутренние двойные — к именам и тексту JSON. Стрелки связывают настройки одного объекта операции. До execute мы только готовим изменение. Поля, которых нет в patch, сохраняются. В развитом примере PHP-массив кодируется через json_encode; вручную склеивать пользовательский текст с кавычками не требуется.

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

НаблюдениеРезультат
Название доСветильник
После patch одного поляЛампа
Статус после первого patchdraft
Название после формыСветильник PRO
Статус после формыpublished
Артикул сохранёнLED-01
Остаток сохранён4
Другой товар не изменёнда
Изменено вторым patch1
Самостоятельный файл и архив для запуска
Изменить название и статус, сохранив остальные данныеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x143Config();
$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 = x143OwnCollection($schema,$owned,'product_card');
    $collection = $schema->getCollection($name);
    $documents = [
        ['_id'=>'LED','sku'=>'LED-01','name'=>'Светильник','status'=>'draft','stock'=>4],
        ['_id'=>'CABLE','sku'=>'CABLE-02','name'=>'Кабель','status'=>'draft','stock'=>10],
    ];
    foreach ($documents as $document) { $collection->add($document)->execute(); }
    $before = $collection->getOne('LED');
    $otherBefore = $collection->getOne('CABLE');
    $first = $collection->modify('_id = :id')->patch('{"name":"Лампа"}')->bind(['id'=>'LED'])->execute();
    $afterOne = $collection->getOne('LED');
    $formValues = ['name'=>'Светильник PRO','status'=>'published'];
    $allowed = ['name'=>true,'status'=>true];
    if (array_diff_key($formValues,$allowed) !== []) {
        throw new InvalidArgumentException('Форма передала недоступное поле');
    }
    $patchJson = json_encode($formValues,JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    $command = $collection->modify('_id = :id')->patch($patchJson)->bind(['id'=>'LED']);
    $receipt = $command->execute();
    $after = $collection->getOne('LED');
    $otherAfter = $collection->getOne('CABLE');
    $rows = [['Название до',$before['name']],['После patch одного поля',$afterOne['name']],
        ['Статус после первого patch',$afterOne['status']],['Название после формы',$after['name']],
        ['Статус после формы',$after['status']],['Артикул сохранён',$after['sku']],['Остаток сохранён',$after['stock']],
        ['Другой товар не изменён',$otherAfter === $otherBefore],['Изменено вторым patch',$receipt->getAffectedItemsCount()]];
    $checks = ['first_field'=>$afterOne['name'] === 'Лампа' && $afterOne['status'] === 'draft',
        'form_fields'=>$after['name'] === 'Светильник PRO' && $after['status'] === 'published',
        'identity_preserved'=>$after['_id'] === 'LED' && $after['sku'] === 'LED-01',
        'stock_preserved'=>$after['stock'] === 4,'other_untouched'=>$otherAfter === $otherBefore,
        'affected_counts'=>$first->getAffectedItemsCount() === 1 && $receipt->getAffectedItemsCount() === 1,
        'receipt_class'=>$receipt instanceof mysql_xdevapi\Result];
    $observations = ['before'=>$before,'after_one_field'=>$afterOne,'form_values'=>$formValues,'patch_json'=>$patchJson,
        'after'=>$after,'other_before'=>$otherBefore,'other_after'=>$otherAfter,
        'command_class'=>get_debug_type($command),'receipt_class'=>get_debug_type($receipt)];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x143Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x143Report('mysql143-x-patch-product-card', 'Изменить название и статус, сохранив остальные данные', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Название до — Светильник
После patch одного поля — Лампа
Статус после первого patch — draft
Название после формы — Светильник PRO
Статус после формы — published
Артикул сохранён — LED-01
Остаток сохранён — 4
Другой товар не изменён — да
Изменено вторым patch — 1

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт только собственную коллекцию, программа записывает и перечитывает документы, затем удаляет свою коллекцию. Откройте result.html и result.json; metrics.json содержит проверки и очистку.

Таблица сохранённого запуска · Документы до и после, квитанции и типы.

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

Пример формы здесь моделируется фиксированным массивом, это не готовый HTTP-обработчик. На настоящем сайте нужны проверка пользователя, права на конкретную карточку, допустимые поля и значения, CSRF-защита и обработка ошибок. Allowlist полей не проверяет длину name или допустимость status сам по себе. _id, sku и stock нельзя разрешать редактировать только потому, что они пришли в JSON. patch не блокирует чужие одновременные изменения: если важна версия карточки, добавьте её в условие.

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

Добавьте в formValues ключ stock=>0. Должен ли измениться остаток?

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

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

Изменить вложенные настройки, удалить поле и заменить список

В настройках карточки надо сменить цвет, сохранить ширину, убрать устаревший телефон и оставить только метку featured. Это разные действия: объект display сливается по ключам, null удаляет поле, а массив tags заменяется целиком. Затем отдельно сохраним настоящий JSON null, чтобы увидеть отличие от отсутствующего ключа.

Путь данных этой программы
  1. display: color white, width = 200 → patch color blue
  2. Рекурсивное слияние сохраняет width = 200
  3. contact.phone:null в patch → ключ удалён, email сохранён
  4. tags:[featured] → старые sale/new не добавляются
  5. set(contact.phone,null) → ключ снова есть, значение JSON null

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

$collection->modify('_id = :id')
    ->patch('{"display":{"color":"blue"}}')
    ->bind(['id'=>'CARD'])->execute();
$card = $collection->getOne('CARD');
echo $card['display']['width']; // 200: не затронута

Вложенный JSON-объект — набор именованных полей внутри другого поля; display.color обозначает цвет внутри display. Merge Patch проходит по таким именам рекурсивно. Пропущенный ключ означает «не менять», переданный null — «удалить». Массив представляет список целиком, поэтому новый список tags заменяет старый. array_key_exists проверяет само наличие ключа; isset возвращает false и для отсутствующего ключа, и для существующего null, поэтому для этой разницы не подходит.

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

НаблюдениеРезультат
Цвет доwhite
Цвет послеblue
Ширина сохранена200
Email сохранёнshop@example.test
Телефон присутствует после patchнет
Метки после patchfeatured
Добавлена гарантия, месяцев24
Телефон присутствует после set(null)да
Значение телефона после set(null)null
Самостоятельный файл и архив для запуска
Изменить вложенные настройки, удалить поле и заменить списокСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x143Config();
$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 = x143OwnCollection($schema,$owned,'nested_merge');
    $collection = $schema->getCollection($name);
    $document = ['_id'=>'CARD','title'=>'Панель товара',
        'display'=>['color'=>'white','width'=>200],
        'contact'=>['phone'=>'+7 000 000-00-00','email'=>'shop@example.test'],
        'tags'=>['sale','new']];
    $collection->add($document)->execute();
    $before = $collection->getOne('CARD');
    $patch = ['display'=>['color'=>'blue'],'contact'=>['phone'=>null],'tags'=>['featured'],'warranty_months'=>24];
    $patchJson = json_encode($patch,JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    $receipt = $collection->modify('_id = :id')->patch($patchJson)->bind(['id'=>'CARD'])->execute();
    $afterPatch = $collection->getOne('CARD');
    $phoneAfterPatch = array_key_exists('phone',$afterPatch['contact']);
    $collection->modify('_id = :id')->set('contact.phone',null)->bind(['id'=>'CARD'])->execute();
    $afterNull = $collection->getOne('CARD');
    $rows = [['Цвет до',$before['display']['color']],['Цвет после',$afterPatch['display']['color']],
        ['Ширина сохранена',$afterPatch['display']['width']],['Email сохранён',$afterPatch['contact']['email']],
        ['Телефон присутствует после patch',$phoneAfterPatch],['Метки после patch',implode(', ',$afterPatch['tags'])],
        ['Добавлена гарантия, месяцев',$afterPatch['warranty_months']],
        ['Телефон присутствует после set(null)',array_key_exists('phone',$afterNull['contact'])],
        ['Значение телефона после set(null)',get_debug_type($afterNull['contact']['phone'])]];
    $checks = ['nested_merge'=>$afterPatch['display'] == ['color'=>'blue','width'=>200],
        'phone_removed'=>!$phoneAfterPatch,'email_retained'=>$afterPatch['contact']['email'] === 'shop@example.test',
        'array_replaced'=>$afterPatch['tags'] === ['featured'],'new_field'=>$afterPatch['warranty_months'] === 24,
        'unmentioned_retained'=>$afterPatch['title'] === $before['title'],
        'actual_null'=>array_key_exists('phone',$afterNull['contact']) && $afterNull['contact']['phone'] === null,
        'id_unchanged'=>$afterNull['_id'] === 'CARD','patch_affected'=>$receipt->getAffectedItemsCount() === 1];
    $observations = ['before'=>$before,'patch'=>$patch,'patch_json'=>$patchJson,'after_patch'=>$afterPatch,'after_set_null'=>$afterNull,
        'presence_after_patch'=>$phoneAfterPatch,'presence_after_set'=>array_key_exists('phone',$afterNull['contact'])];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x143Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x143Report('mysql143-x-patch-nested-merge', 'Изменить вложенные настройки, удалить поле и заменить список', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Цвет до — white
Цвет после — blue
Ширина сохранена — 200
Email сохранён — shop@example.test
Телефон присутствует после patch — нет
Метки после patch — featured
Добавлена гарантия, месяцев — 24
Телефон присутствует после set(null) — да
Значение телефона после set(null) — null

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт только собственную коллекцию, программа записывает и перечитывает документы, затем удаляет свою коллекцию. Откройте result.html и result.json; metrics.json содержит проверки и очистку.

Таблица сохранённого запуска · Документы до и после, квитанции и типы.

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

patch не является JSON Patch со списком операций op/path/value. Для добавления одного элемента массива используйте подходящий arrayAppend/arrayInsert, а не отправку неполного списка как будто он автоматически объединится. Пустой PHP-массив json_encode превратит в [], а пустой JSON-объект задают отдельно, например (object)[]. Сохранённый JSON null получают явным set; строка "null" — обычный текст. Не допускайте в patch неизвестные поля и пути, даже если JSON синтаксически корректен.

При частичном изменении вложенного списка разработчик ожидает объединения его элементов и сохранения старых полей каждого элемента. Реальная задача на Stack Overflow. В нашей карточке отдельно показаны рекурсивно сливаемый объект display и целиком заменяемый массив tags. Формат Merge Patch не адресует отдельный элемент массива; соответствующий метод выбирают явно.

Нужно сохранить JSON null, но MERGE_PATCH удаляет поле; строка "null" тоже не даёт нужный тип. Реальная задача на Stack Overflow. На одном документе сравниваем удаление phone через patch и сохранение JSON null через set. array_key_exists отличает присутствующее пустое значение от отсутствующего ключа; чужие JavaScript-рецепты не задают контракт PHP.

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

Уберите contact из patch целиком, оставив изменение display и tags. Что произойдёт с телефоном?

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

Он сохранит исходную строку: отсутствие ключа в patch не удаляет его. Если нужен именно null как значение, выполняйте явный set; если удаление — используйте предусмотренный null в patch либо unset по пути.

Исправить существующую цену и заметить отсутствующее поле

Корректировка цены должна менять уже существующий price_cents и сохранять остальную карточку. Но у второго товара поле цены ещё не заполнено. Покажем оба случая рядом, а затем явно выберем set, когда нужно создать отсутствующее поле. Результат квитанции тоже важен: affected=1 здесь ещё не доказывает, что новое значение появилось.

Путь данных этой программы
  1. LED имеет price_cents = 1000 → replace(1200) меняет поле
  2. CABLE не имеет price_cents → replace(900) не создаёт его
  3. Квитанция отсутствующего пути даёт 1, чтение показывает прежний документ
  4. set(900) добавляет поле у CABLE
  5. Везде сохранены _id и sku; цена задана целыми копейками

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

$collection->modify('_id = :id')
    ->replace('price_cents',1200)
    ->bind(['id'=>'LED'])->execute();
echo $collection->getOne('LED')['price_cents']; // 1200 копеек

Первый аргумент replace — путь поля, второй — новое значение. Это замена поля внутри документа, не замена карточки целиком. В краткой сигнатуре Manual второй аргумент указан string, но реализация PECL 8.0.30 принимает значение PHP; целое число в этой программе остаётся JSON-числом и читается как int. У отсутствующего пути replace не делает вставку. Для вставки либо изменения поля применяется set; для нескольких связанных полей удобно patch.

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

НаблюдениеРезультат
LED: цена до, коп.1000
LED: цена после replace, коп.1200
LED: артикул сохранёнLED-01
CABLE: поле добавлено через replaceнет
CABLE: документ после replace прежнийда
Квитанция replace отсутствующего поля1
CABLE: цена после set, коп.900
Тип PHP новой ценыint
Самостоятельный файл и архив для запуска
Исправить существующую цену и заметить отсутствующее полеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x143Config();
$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 = x143OwnCollection($schema,$owned,'replace_field');
    $collection = $schema->getCollection($name);
    $withPrice = ['_id'=>'LED','sku'=>'LED-01','price_cents'=>1000];
    $withoutPrice = ['_id'=>'CABLE','sku'=>'CABLE-02'];
    $collection->add($withPrice)->execute();
    $collection->add($withoutPrice)->execute();
    $beforeMissing = $collection->getOne('CABLE');
    $existingResult = $collection->modify('_id = :id')->replace('price_cents',1200)->bind(['id'=>'LED'])->execute();
    $afterExisting = $collection->getOne('LED');
    $missingResult = $collection->modify('_id = :id')->replace('price_cents',900)->bind(['id'=>'CABLE'])->execute();
    $afterMissing = $collection->getOne('CABLE');
    $missingWasAdded = array_key_exists('price_cents',$afterMissing);
    $collection->modify('_id = :id')->set('price_cents',900)->bind(['id'=>'CABLE'])->execute();
    $afterSet = $collection->getOne('CABLE');
    $rows = [['LED: цена до, коп.',1000],['LED: цена после replace, коп.',$afterExisting['price_cents']],
        ['LED: артикул сохранён',$afterExisting['sku']],['CABLE: поле добавлено через replace',$missingWasAdded],
        ['CABLE: документ после replace прежний',$afterMissing === $beforeMissing],
        ['Квитанция replace отсутствующего поля',$missingResult->getAffectedItemsCount()],
        ['CABLE: цена после set, коп.',$afterSet['price_cents']],['Тип PHP новой цены',get_debug_type($afterSet['price_cents'])]];
    $checks = ['existing_replaced'=>$afterExisting['price_cents'] === 1200,
        'existing_identity'=>$afterExisting['_id'] === 'LED' && $afterExisting['sku'] === 'LED-01',
        'missing_not_added'=>!$missingWasAdded,'missing_unchanged'=>$afterMissing === $beforeMissing,
        'matched_receipt'=>$missingResult->getAffectedItemsCount() === 1,
        'set_added'=>$afterSet['price_cents'] === 900 && $afterSet['_id'] === 'CABLE' && $afterSet['sku'] === 'CABLE-02',
        'integer_literal'=>is_int($afterExisting['price_cents']) && is_int($afterSet['price_cents'])];
    $observations = ['with_price_before'=>$withPrice,'existing_after_replace'=>$afterExisting,
        'missing_before'=>$beforeMissing,'missing_after_replace'=>$afterMissing,'missing_after_set'=>$afterSet,
        'existing_affected'=>$existingResult->getAffectedItemsCount(),'missing_affected'=>$missingResult->getAffectedItemsCount()];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x143Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x143Report('mysql143-x-replace-existing-field', 'Исправить существующую цену и заметить отсутствующее поле', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
LED: цена до, коп. — 1000
LED: цена после replace, коп. — 1200
LED: артикул сохранён — LED-01
CABLE: поле добавлено через replace — нет
CABLE: документ после replace прежний — да
Квитанция replace отсутствующего поля — 1
CABLE: цена после set, коп. — 900
Тип PHP новой цены — int

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт только собственную коллекцию, программа записывает и перечитывает документы, затем удаляет свою коллекцию. Откройте result.html и result.json; metrics.json содержит проверки и очистку.

Таблица сохранённого запуска · Документы до и после, квитанции и типы.

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

Нужно различать отсутствие документа, отсутствие поля и наличие поля с null. Здесь CABLE существует, отсутствует только price_cents. В проверенной связке отсутствующий путь не меняет документ, хотя getAffectedItemsCount возвращает 1. Проверяйте нужное постусловие чтением или явной моделью состояния, а не делайте из квитанции утверждение «цена сохранена». Путь — доверенная часть кода; bind предназначен для значений условия, не для выбора произвольного редактируемого пути. Число 1200 обозначает копейки; единица задаётся приложением.

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

Пусть форма разрешает изменить только цену уже оценённых товаров. Нужно ли после неудачного replace автоматически вызывать set?

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

Нет: отсутствие цены может быть отдельной стадией товара. Проверьте предметное условие и сообщите, что цена ещё не назначена. Автоматическое создание поля через set допустимо только если такое действие предусмотрено задачей.

Опубликовать только две первые карточки очереди

В очереди пять готовых карточек. За один проход нужно опубликовать только две с наивысшим приоритетом; в нашей модели меньшее число означает более раннюю очередь. При равенстве priority порядок задаёт уникальный _id. Выбор и ограничение выполняет сервер: приложение не загружает весь каталог для сортировки массива.

Путь данных этой программы
  1. Фильтр оставляет pending: A,B,C,D,E
  2. priority ASC ставит A/B первыми
  3. _id ASC различает карточки с одинаковым priority = 1
  4. limit(2) выбирает A и B
  5. patch меняет status у A/B; C/D/E остаются pending

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

$collection->modify('status = :status')->bind(['status'=>'pending'])
    ->sort('priority ASC','_id ASC')->limit(2)
    ->patch('{"status":"published"}')->execute();

sort получает выражения по порядку важности. ASC означает возрастание, DESC — убывание. Сначала сравнивается priority, а при равенстве — _id. Два аргумента через запятую поддерживаются реализацией и показаны в Manual, хотя краткая сигнатура содержит один string. limit(2) ограничивает число документов операции, а не массив PHP после чтения. execute отправляет настроенное изменение; затем отдельный find читает фактические состояния для учебной ведомости.

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

НаблюдениеРезультат
A: статусpending → published
B: статусpending → published
C: статусpending → pending
D: статусpending → pending
E: статусpending → pending
Опубликованные IDA, B
Квитанция2
Самостоятельный файл и архив для запуска
Опубликовать только две первые карточки очередиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x143Config();
$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 = x143OwnCollection($schema,$owned,'sorted_batch');
    $collection = $schema->getCollection($name);
    $documents = [
        ['_id'=>'A','priority'=>1,'status'=>'pending'],['_id'=>'B','priority'=>1,'status'=>'pending'],
        ['_id'=>'C','priority'=>2,'status'=>'pending'],['_id'=>'D','priority'=>3,'status'=>'pending'],
        ['_id'=>'E','priority'=>4,'status'=>'pending'],
    ];
    foreach ($documents as $document) { $collection->add($document)->execute(); }
    $before = $collection->find()->sort('_id ASC')->execute()->fetchAll();
    $command = $collection->modify('status = :status')->patch('{"status":"published"}')
        ->bind(['status'=>'pending'])->sort('priority ASC','_id ASC')->limit(2);
    $receipt = $command->execute();
    $after = $collection->find()->sort('_id ASC')->execute()->fetchAll();
    $publishedRows = $collection->find('status = :status')->bind(['status'=>'published'])
        ->fields('_id')->sort('_id ASC')->execute()->fetchAll();
    $publishedIds = array_column($publishedRows,'_id');
    $rows = [];
    foreach ($after as $index=>$document) {
        $rows[] = [$document['_id'].': статус',$before[$index]['status'].' → '.$document['status']];
    }
    $rows[] = ['Опубликованные ID',implode(', ',$publishedIds)];
    $rows[] = ['Квитанция',$receipt->getAffectedItemsCount()];
    $checks = ['selected_ids'=>$publishedIds === ['A','B'],
        'states'=>array_column($after,'status') === ['published','published','pending','pending','pending'],
        'ids_unchanged'=>array_column($after,'_id') === ['A','B','C','D','E'],
        'priorities_unchanged'=>array_column($after,'priority') === [1,1,2,3,4],
        'bounded_receipt'=>$receipt->getAffectedItemsCount() === 2,
        'fluent_command'=>$command instanceof mysql_xdevapi\CollectionModify];
    $observations = ['before'=>$before,'after'=>$after,'published_ids'=>$publishedIds,
        'sort'=>['priority ASC','_id ASC'],'limit'=>2,'affected'=>$receipt->getAffectedItemsCount()];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x143Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x143Report('mysql143-x-sorted-limited-patch', 'Опубликовать только две первые карточки очереди', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
A: статус — pending → published
B: статус — pending → published
C: статус — pending → pending
D: статус — pending → pending
E: статус — pending → pending
Опубликованные ID — A, B
Квитанция — 2

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт только собственную коллекцию, программа записывает и перечитывает документы, затем удаляет свою коллекцию. Откройте result.html и result.json; metrics.json содержит проверки и очистку.

Таблица сохранённого запуска · Документы до и после, квитанции и типы.

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

LIMIT не гарантирует дешёвый запрос: фильтр, сортировка и индексы определяют работу сервера. Для показа результата небольшой учебной коллекции мы читаем пять строк; в реальном сервисе запрашивают только необходимые поля и порцию. Без уникального последнего ключа одинаковые приоритеты не дают устойчивой границы. Это один ограниченный проход, не готовая конкурентная очередь: два работника, повторная доставка и внешние действия требуют отдельного протокола владения задачей.

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

Сделайте priority у E равным 0. Кто должен попасть в порцию из двух?

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

E будет первым, затем A: у A/B одинаковый priority = 1 и _id ASC выбирает A. Изменённый тест должен ожидать именно E/A, а не полагаться на физический порядок хранения документов.

Выбрать окно по ID и не пропускать уменьшающуюся очередь

Нужно отметить вторую и третью карточки устойчивого списка A–E. Кажется естественным написать skip(1).limit(2), но наличие PHP-метода ещё не обещает поддержку обновления сервером. Покажем фактический отказ, рабочее чтение окна B/C и изменение по их ID. Затем обработаем все pending порциями, не увеличивая смещение у уменьшающегося набора.

Путь данных этой программы
  1. A,B,C,D,E → пропустить 1, взять 2 означает B,C
  2. CollectionModify::skip(1) в этом профиле → ошибка 5012, записи прежние
  3. CollectionFind::offset(1) / limit(2) возвращает B,C
  4. modify с привязкой ID отмечает именно B/C
  5. pending уменьшается: пять → три → один → ноль, limit(2) без offset

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

$window = $collection->find('status = :status')->bind(['status'=>'pending'])
    ->fields('_id')->sort('_id ASC')->limit(2)->offset(1)->execute()->fetchAll();
foreach ($window as $row) {
    $collection->modify('_id = :id AND status = :status')->set('window_selected',true)
        ->bind(['id'=>$row['_id'],'status'=>'pending'])->execute();
}

skip задаёт число пропускаемых элементов; это не ID документа. В связке PECL mysql_xdevapi 8.0.30/MySQL 8.4.13 выполнение CollectionModify со skip(1) отклоняется кодом 5012: offset не разрешён для операции. Неподдерживаемый вызов показан в полном файле внутри узкого catch, который пропускает только ожидаемый код; другие ошибки не скрываются. Рабочий путь использует offset у find, сохраняет полученные ID и проверяет статус при записи. Во второй части for ограничен тремя проходами для пяти известных документов; каждый проход снова берёт первые два оставшихся pending.

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

НаблюдениеРезультат
Код отказа modify.skip5012
После отказа все документы прежниеда
Выбранное окноB, C
Реально отмеченные IDB, C
Порции без смещения2, 2, 1
Осталось pending0
Итоговые статусыdone, done, done, done, done
Самостоятельный файл и архив для запуска
Выбрать окно по ID и не пропускать уменьшающуюся очередьСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x143Config();
$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 = x143OwnCollection($schema,$owned,'skip_window');
    $collection = $schema->getCollection($name);
    foreach (['A','B','C','D','E'] as $id) {
        $document = ['_id'=>$id,'status'=>'pending','window_selected'=>false];
        $collection->add($document)->execute();
    }
    $before = $collection->find()->sort('_id ASC')->execute()->fetchAll();
    $skipFailure = null;
    try {
        $collection->modify('status = :status')->bind(['status'=>'pending'])->set('window_selected',true)
            ->sort('_id ASC')->limit(2)->skip(1)->execute();
    } catch (mysql_xdevapi\Exception $error) {
        if ($error->getCode() !== 5012) { throw $error; }
        $skipFailure = ['class'=>get_debug_type($error),'code'=>$error->getCode(),'message'=>$error->getMessage()];
    }
    $afterRejected = $collection->find()->sort('_id ASC')->execute()->fetchAll();
    $window = $collection->find('status = :status')->bind(['status'=>'pending'])->fields('_id')
        ->sort('_id ASC')->limit(2)->offset(1)->execute()->fetchAll();
    $windowIds = array_column($window,'_id');
    foreach ($windowIds as $id) {
        $collection->modify('_id = :id AND status = :status')->set('window_selected',true)
            ->bind(['id'=>$id,'status'=>'pending'])->execute();
    }
    $afterWindow = $collection->find()->sort('_id ASC')->execute()->fetchAll();
    $selected = $collection->find('window_selected = true')->fields('_id')->sort('_id ASC')->execute()->fetchAll();
    $selectedIds = array_column($selected,'_id');
    $batchCounts = [];
    // Для этих пяти исходных документов достаточно не более трёх проходов.
    for ($pass=0; $pass<3; $pass++) {
        $receipt = $collection->modify('status = :status')->bind(['status'=>'pending'])
            ->sort('_id ASC')->limit(2)->patch('{"status":"done"}')->execute();
        $batchCounts[] = $receipt->getAffectedItemsCount();
    }
    $afterBatches = $collection->find()->sort('_id ASC')->execute()->fetchAll();
    $pending = $collection->find('status = :status')->bind(['status'=>'pending'])->fields('_id')->execute()->fetchAll();
    $rows = [['Код отказа modify.skip',$skipFailure['code'] ?? 'Отказ не получен'],
        ['После отказа все документы прежние',$afterRejected === $before],['Выбранное окно',implode(', ',$windowIds)],
        ['Реально отмеченные ID',implode(', ',$selectedIds)],['Порции без смещения',implode(', ',$batchCounts)],
        ['Осталось pending',count($pending)],['Итоговые статусы',implode(', ',array_column($afterBatches,'status'))]];
    $checks = ['expected_skip_failure'=>$skipFailure !== null && $skipFailure['code'] === 5012,
        'rejected_unchanged'=>$afterRejected === $before,'window_ids'=>$windowIds === ['B','C'],
        'window_applied'=>$selectedIds === ['B','C'],
        'others_unmarked'=>array_column($afterWindow,'window_selected') === [false,true,true,false,false],
        'batch_counts'=>$batchCounts === [2,2,1],'queue_drained'=>$pending === [],
        'all_done'=>array_column($afterBatches,'status') === ['done','done','done','done','done'],
        'window_preserved'=>array_column($afterBatches,'window_selected') === [false,true,true,false,false]];
    $observations = ['before'=>$before,'skip_failure'=>$skipFailure,'after_rejected'=>$afterRejected,'selected_window'=>$window,
        'after_window'=>$afterWindow,'selected_ids'=>$selectedIds,'batch_counts'=>$batchCounts,'after_batches'=>$afterBatches,
        'remaining_pending'=>$pending];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x143Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x143Report('mysql143-x-modify-skip-window', 'Выбрать окно по ID и не пропускать уменьшающуюся очередь', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Код отказа modify.skip — 5012
После отказа все документы прежние — да
Выбранное окно — B, C
Реально отмеченные ID — B, C
Порции без смещения — 2, 2, 1
Осталось pending — 0
Итоговые статусы — done, done, done, done, done

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт только собственную коллекцию, программа записывает и перечитывает документы, затем удаляет свою коллекцию. Откройте result.html и result.json; metrics.json содержит проверки и очистку.

Таблица сохранённого запуска · Документы до и после, квитанции и типы.

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

Если после обработки A/B исключить их из pending, следующий pending-набор начинается C/D/E. Растущее смещение 2 пропустит C/D и начнёт с E. Поэтому одинаковое окно чтения и уменьшающаяся очередь — разные задачи. Наши fixtures не изменяются конкурентно; чтение ID и последующие изменения не образуют автоматически одну атомарную операцию. При других писателях нужны транзакция/блокировки либо проверка версии и повторное подтверждение состояния. Это демонстрация контракта и ограниченного алгоритма, не бесконечный фоновый worker.

Автор хочет обновить первые совпадения и следующую порцию, но перенос LIMIT/OFFSET из SELECT в UPDATE не работает. Реальная задача на Stack Overflow. Разделяем выбор фиксированных ID и изменение, проверяем фактическую поддержку CollectionModify::skip, а уменьшающуюся pending-очередь обрабатываем ограниченными проходами без растущего смещения. SQL из ответа не копируется в PHP API.

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

После первого прохода очередь стала C/D/E. Кого вернёт offset(2).limit(2) и почему это не продолжение без пропусков?

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

Только E: C и D сейчас занимают первые два места нового набора и будут пропущены. Для исчерпания pending выбирайте очередную первую порцию или используйте заранее зафиксированные ID/согласованный курсор и предметные правила повторов.

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

В приложении появился небольшой общий код: одна функция подставляет ID, другая выполняет уже настроенную операцию. Им не нужны все возможности CollectionModify. Укажем требуемые интерфейсы в параметрах и проверим реальную запись. Читатель увидит, что подготовленная операция ещё оставляет draft, а опубликованный статус появляется после execute.

Путь данных этой программы
  1. modify+patch готовят объект операции без записи
  2. bindDocument143 получает тот же объект и ID LED
  3. bind/sort/limit возвращают CollectionModify; статус ещё draft
  4. executeOperation143 вызывает execute → сервер сохраняет published
  5. возвращается Result-квитанция, а не новый объект операции

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

$operation = $collection->modify('_id = :id')->patch('{"status":"published"}');
$operation->bind(['id'=>'LED']);
echo $collection->getOne('LED')['status']; // draft
$receipt = $operation->execute();
echo $collection->getOne('LED')['status']; // published

Интерфейс — обещание, какие методы доступны у переданного объекта. Это не новый сервер, не соединение и не результат. Тип параметра CrudOperationBindable разрешает функции вызвать bind; параметр Executable разрешает вызвать execute. Функции в contracts.php принимают уже созданный объект. return передаёт вызывающему коду возвращённую операцию либо квитанцию. Проверка instanceof показывает фактические контракты CollectionModify. Это удобно, когда общий шаг нужен нескольким конкретным операциям; для одного короткого вызова дополнительная функция не обязательна.

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

НаблюдениеРезультат
Статус до executedraft
Статус после executepublished
Возвращён из bindmysql_xdevapi\CollectionModify
Возвращён из sortmysql_xdevapi\CollectionModify
Возвращён из limitmysql_xdevapi\CollectionModify
Возвращён из executemysql_xdevapi\Result
Настройки относятся к одному объектуда
Контракт bindда
Контракт limitда
Контракт skipда
Контракт sortда
Контракт executeда
Самостоятельный файл и архив для запуска
Передать настройку и выполнение через ясные контрактыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x143Config();
$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']];
    require __DIR__.'/contracts.php';
    $name = x143OwnCollection($schema,$owned,'contracts');
    $collection = $schema->getCollection($name);
    $document = ['_id'=>'LED','name'=>'Светильник','status'=>'draft'];
    $collection->add($document)->execute();
    $operation = $collection->modify('_id = :id')->patch('{"status":"published"}');
    $bound = bindDocument143($operation,'LED');
    $sorted = $operation->sort('_id ASC');
    $limited = $operation->limit(1);
    $beforeExecute = $collection->getOne('LED');
    $contracts = [
        'bind'=>$operation instanceof mysql_xdevapi\CrudOperationBindable,
        'limit'=>$operation instanceof mysql_xdevapi\CrudOperationLimitable,
        'skip'=>$operation instanceof mysql_xdevapi\CrudOperationSkippable,
        'sort'=>$operation instanceof mysql_xdevapi\CrudOperationSortable,
        'execute'=>$operation instanceof mysql_xdevapi\Executable,
    ];
    $receipt = executeOperation143($operation);
    $afterExecute = $collection->getOne('LED');
    $rows = [['Статус до execute',$beforeExecute['status']],['Статус после execute',$afterExecute['status']],
        ['Возвращён из bind',get_debug_type($bound)],['Возвращён из sort',get_debug_type($sorted)],
        ['Возвращён из limit',get_debug_type($limited)],['Возвращён из execute',get_debug_type($receipt)],
        ['Настройки относятся к одному объекту',$bound === $operation && $sorted === $operation && $limited === $operation]];
    foreach ($contracts as $method=>$implemented) { $rows[] = ['Контракт '.$method,$implemented]; }
    $checks = ['deferred_execution'=>$beforeExecute['status'] === 'draft','real_write'=>$afterExecute['status'] === 'published',
        'other_fields'=>$afterExecute['_id'] === 'LED' && $afterExecute['name'] === 'Светильник',
        'fluent_identity'=>$bound === $operation && $sorted === $operation && $limited === $operation,
        'all_interfaces'=>!in_array(false,$contracts,true),'actual_receipt'=>$receipt instanceof mysql_xdevapi\Result,
        'affected'=>$receipt->getAffectedItemsCount() === 1];
    $observations = ['before_execute'=>$beforeExecute,'after_execute'=>$afterExecute,'contracts'=>$contracts,
        'return_types'=>['bind'=>get_debug_type($bound),'sort'=>get_debug_type($sorted),'limit'=>get_debug_type($limited),'execute'=>get_debug_type($receipt)],
        'affected'=>$receipt->getAffectedItemsCount()];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x143Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x143Report('mysql143-x-crud-command-contracts', 'Передать настройку и выполнение через ясные контракты', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Статус до execute — draft
Статус после execute — published
Возвращён из bind — mysql_xdevapi\CollectionModify
Возвращён из sort — mysql_xdevapi\CollectionModify
Возвращён из limit — mysql_xdevapi\CollectionModify
Возвращён из execute — mysql_xdevapi\Result
Настройки относятся к одному объекту — да
Контракт bind — да
Контракт limit — да
Контракт skip — да
Контракт sort — да
Контракт execute — да

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт только собственную коллекцию, программа записывает и перечитывает документы, затем удаляет свою коллекцию. Откройте result.html и result.json; metrics.json содержит проверки и очистку.

Таблица сохранённого запуска · Документы до и после, квитанции и типы.

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

Способность вызвать skip не доказывает поддержку offset сервером: предыдущая программа показывает 5012 при том же реализованном интерфейсе. Executable не обещает одинаковое содержимое всех результатов: изменение даёт Result, чтение документов — DocResult, SQL имеет SqlStatementResult. Поэтому функция выполнения не выдумывает универсальную схему данных; потребитель знает предметную операцию. Повторное execute не является автоматически безопасным повтором — особенно если изменение увеличивает счётчик или добавляет элемент.

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

Перенесите вызов executeOperation143 ниже чтения beforeExecute. Появится ли published раньше выполнения?

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

Нет: bind, sort, limit и patch лишь настраивают объект. При работающем соединении наблюдаемый документ остаётся draft до execute. Если нужно прочитать будущий вариант до сохранения, это отдельная модель предпросмотра, а не побочный эффект создания объекта операции.

Найти действие и его контракт

APIДля чего
CollectionModify::patchСохранить только переданные настройки карточки, не потеряв остальные поля.
CollectionModify::replaceЗаменить значение по существующему пути внутри документа.
CollectionModify::sortВыбрать, какие совпадения попадут в ограниченную порцию изменений.
CollectionModify::limitОграничить число документов одного изменения.
CollectionModify::skipРазобраться со смещением и ограничением конкретного сервера.
CrudOperationBindableОбщий код знает, как привязать значения условия, не зная остальных методов операции.
CrudOperationBindable::bindОбщий код знает, как привязать значения условия, не зная остальных методов операции.
CrudOperationLimitableПередать ограничение порции в общий код, который не отвечает за саму выборку или изменение.
CrudOperationLimitable::limitПередать ограничение порции в общий код, который не отвечает за саму выборку или изменение.
CrudOperationSkippableЯвно отделить наличие метода пропуска от возможности выполнить операцию на сервере.
CrudOperationSkippable::skipЯвно отделить наличие метода пропуска от возможности выполнить операцию на сервере.
CrudOperationSortableПередать известное правило порядка в общий код, не загружая весь набор в массив PHP.
CrudOperationSortable::sortПередать известное правило порядка в общий код, не загружая весь набор в массив PHP.
ExecutableВыполнить уже настроенную операцию в понятном месте программы и получить её фактический результат.
Executable::executeВыполнить уже настроенную операцию в понятном месте программы и получить её фактический результат.

От изменения документа к навигации по объектам базы

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

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

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

mysql_xdevapi\CollectionModify::patchСохранить только переданные настройки карточки, не потеряв остальные поля.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

CollectionModify::patch(string $document): mysql_xdevapi\CollectionModify

Сохранить только переданные настройки карточки, не потеряв остальные поля.

Как применяется вызов
  1. LED: name Светильник, stock = 4
  2. patch {name:Лампа} меняет только name
  3. stock = 4 сохраняется, пока не включён в patch
  4. execute → getOne → новое название

Запись, вход и результат

CollectionModify::patch(string $document): mysql_xdevapi\CollectionModify
Параметр / часть контрактаЗначение
documentJSON-строка с объектом изменений. Один patch применяется ко всем документам, выбранным modify; это не ID и не полный старый документ.

Возвращаемое значение. Возвращает объект CollectionModify для дальнейшей настройки. Сохранение выполняет execute; его отдельный Result — квитанция операции.

Два применения

Сохранить два разрешённых поля формы одним документом изменений.

$patch = json_encode(['name'=>'Светильник PRO','status'=>'published'], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$collection->modify('_id = :id')->patch($patch)->bind(['id'=>'LED'])->execute();

Исходные данные, полный запуск и результат.

Изменить вложенную настройку и удалить устаревший телефон, сохранив соседние поля.

$collection->modify('_id = :id')->patch('{"display":{"color":"blue"},"contact":{"phone":null}}')
    ->bind(['id'=>'CARD'])->execute();

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Merge Patch рекурсивно сливает объекты; отсутствие ключа сохраняет значение, null удаляет ключ, новый ненулевой ключ добавляется. Массив заменяется целиком, не объединяется по индексам. PHP-вариант patch принимает строку: сериализуйте проверенные данные через json_encode, не копируйте объектный синтаксис JavaScript-коннектора. Не путайте Merge Patch с JSON Patch op/path/value. Условия доступа, allowlist полей и проверка версии принадлежат приложению; изменение выбранных полей не защищает автоматически от конкурентного перезаписывания тех же полей.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

Контекст: объект изменения, значения условия и момент выполнения.

mysql_xdevapi\CollectionModify::replaceЗаменить значение по существующему пути внутри документа.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

CollectionModify::replace(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify

Заменить значение по существующему пути внутри документа.

Как применяется вызов
  1. LED.price_cents = 1000 → replace(1200) → 1200
  2. CABLE без price_cents → replace(900) → поля по-прежнему нет
  3. Result: affected = 1 не доказывает изменение значения
  4. set(900) выбирается отдельно, если нужна вставка

Запись, вход и результат

CollectionModify::replace(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify
Параметр / часть контрактаЗначение
collection_fieldПуть целевого поля, например price_cents или display.color; это доверенная часть программы.
expression_or_literalНовое значение. Manual пишет string, но исходник этой версии принимает PHP zval; в программе целое 1200 сохраняется числом.

Возвращаемое значение. CollectionModify для продолжения цепочки. В существующем поле новое значение заменяет старое; отсутствующий путь в данном профиле ничего не добавляет.

Два применения

Исправить существующую цену, не меняя артикул и идентификатор.

$collection->modify('_id = :id')->replace('price_cents',1200)->bind(['id'=>'LED'])->execute();

Исходные данные, полный запуск и результат.

Обнаружить отсутствие поля после квитанции; не выдавать matched/affected за доказательство сохранённой цены.

$receipt = $collection->modify('_id = :id')->replace('price_cents',900)->bind(['id'=>'CABLE'])->execute();
$card = $collection->getOne('CABLE');
$hasPrice = array_key_exists('price_cents',$card);

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

replace изменяет поле, а Collection::replaceOne заменяет документ целиком — это разные операции. set подходит для вставки отсутствующего поля. Строка "price_cents + 100" не превращается автоматически в вычисление: выражение передают соответствующим Expression, уже разобранным отдельно. Здесь используется буквальное целое значение; денежная единица задана явно. При отсутствии документа и при отсутствии пути постусловия различаются, проверяйте нужную бизнес-ситуацию.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

Контекст: объект изменения, значения условия и момент выполнения.

mysql_xdevapi\CollectionModify::sortВыбрать, какие совпадения попадут в ограниченную порцию изменений.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

CollectionModify::sort(string $sort_expr): mysql_xdevapi\CollectionModify

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

Как применяется вызов
  1. pending A/B имеют одинаковый priority = 1
  2. priority ASC делает их первыми
  3. _id ASC различает A и B
  4. limit(2) задаёт границу порции

Запись, вход и результат

CollectionModify::sort(string $sort_expr): mysql_xdevapi\CollectionModify
Параметр / часть контрактаЗначение
sort_exprВыражение сортировки с ASC или DESC. Несколько выражений оцениваются слева направо; реализация 8.0.30 и пример Manual поддерживают несколько аргументов через запятую.

Возвращаемое значение. CollectionModify, который можно дополнить limit, patch и выполнить. Порядок соответствует смыслу ORDER BY; сортируется серверное множество выбранных документов.

Два применения

Опубликовать первые две карточки по приоритету и устойчивому ключу.

$collection->modify('status = :status')->bind(['status'=>'pending'])
    ->sort('priority ASC','_id ASC')->limit(2)->patch('{"status":"published"}')->execute();

Исходные данные, полный запуск и результат.

Брать очередную первую порцию уменьшающейся pending-очереди.

$collection->modify('status = :status')->bind(['status'=>'pending'])
    ->sort('_id ASC')->limit(2)->patch('{"status":"done"}')->execute();

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

sort не является usort над уже загруженными документами. Одно поле с повторяющимися значениями не даёт устойчивой границы, поэтому добавлен уникальный _id. Имена полей и направления не подставляют из запроса пользователя без разрешённого словаря. Индекс и план влияют на стоимость; limit не гарантирует, что сервер просмотрит только две записи. При конкурентном изменении набора нужна отдельная стратегия согласованности.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

Контекст: объект изменения, значения условия и момент выполнения.

mysql_xdevapi\CollectionModify::limitОграничить число документов одного изменения.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

CollectionModify::limit(int $rows): mysql_xdevapi\CollectionModify

Ограничить число документов одного изменения.

Как применяется вызов
  1. Выбраны 5 документов pending
  2. sort задаёт порядок
  3. limit(2) ограничивает первое изменение
  4. после execute остаются 3 документа pending

Запись, вход и результат

CollectionModify::limit(int $rows): mysql_xdevapi\CollectionModify
Параметр / часть контрактаЗначение
rowsМаксимальное число документов этой операции, например 2. Это количество, не последний ID и не число полей.

Возвращаемое значение. CollectionModify для дальнейшей настройки. Фактический результат зависит от числа совпадений; limit(2) не требует, чтобы совпадений было ровно два.

Два применения

Ограничить одну публикацию двумя карточками.

$operation = $collection->modify('status = :status')->bind(['status'=>'pending']);
$operation->sort('priority ASC','_id ASC')->limit(2)->patch('{"status":"published"}')->execute();

Исходные данные, полный запуск и результат.

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

for ($pass=0; $pass<3; $pass++) {
    $collection->modify('status = :status')->bind(['status'=>'pending'])
        ->sort('_id ASC')->limit(2)->patch('{"status":"done"}')->execute();
}

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Manual предлагает сочетать limit со skip, но проверенный сервер отклоняет ненулевой offset для CollectionModify кодом 5012; это разобрано в следующей карточке. Для размера порции приложение должно задать допустимый положительный диапазон; не передавайте отрицательные значения и не рассчитывайте на неограниченную обработку. Квитанция не заменяет чтение состояния. Отдельные проходы не составляют автоматически одну транзакцию.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

Контекст: объект изменения, значения условия и момент выполнения.

mysql_xdevapi\CollectionModify::skipРазобраться со смещением и ограничением конкретного сервера.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

CollectionModify::skip(int $position): mysql_xdevapi\CollectionModify

Разобраться со смещением и ограничением конкретного сервера.

Как применяется вызов
  1. Желаемое окно A/B/C/D/E: skip(1) + limit(2) → B/C
  2. Метод skip существует в PHP
  3. execute отклоняет offset кодом 5012
  4. Рабочий find с offset выбирает ID, затем modify меняет их

Запись, вход и результат

CollectionModify::skip(int $position): mysql_xdevapi\CollectionModify
Параметр / часть контрактаЗначение
positionЧисло первых элементов для пропуска. Manual описывает это языком find: при пропуске за концом результата find пуст. Это описание нельзя автоматически переносить в успешно выполняемый UPDATE.

Возвращаемое значение. На этапе настройки — CollectionModify. При фактическом выполнении проверенного ненулевого смещения возникает серверное исключение 5012.

Два применения

Показать реальный отказ, не принять настройку объекта за успешное изменение.

try {
    $collection->modify('status = :status')->bind(['status'=>'pending'])
        ->set('window_selected',true)->sort('_id ASC')->limit(2)->skip(1)->execute();
} catch (mysql_xdevapi\Exception $error) {
    if ($error->getCode() !== 5012) { throw $error; }
    echo 'Этот сервер не разрешает offset для изменения';
}

Исходные данные, полный запуск и результат.

Выбрать те же ID для отдельного ограниченного изменения; полный файл проверяет отмеченные и нетронутые карточки.

$window = $collection->find('status = :status')->bind(['status'=>'pending'])
    ->fields('_id')->sort('_id ASC')->limit(2)->offset(1)->execute()->fetchAll();

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

В проверенной связке PECL mysql_xdevapi 8.0.30/MySQL 8.4.13 skip(1) создаёт допустимую PHP-цепочку, но execute отклоняется сервером: mysql_xdevapi\Exception, код 5012, offset value is not allowed for this operation. После отказа документы остаются прежними. Это зафиксированное ограничение профиля, а не повод скрыть метод из полного справочника. В исходнике отрицательная position отклоняется до отправки операции. При меняющемся фильтре растущий offset пропускает ещё не обработанные документы; используйте ограниченную первую порцию или согласованные ID. Чтение окна и изменение не являются одной атомарной командой: нужны предметные проверки состояния и, при необходимости, транзакции.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

Контекст: объект изменения, значения условия и момент выполнения.

mysql_xdevapi\CrudOperationBindableОбщий код знает, как привязать значения условия, не зная остальных методов операции.

Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11. · mysql_xdevapi

interface mysql_xdevapi\CrudOperationBindable

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

Как применяется вызов
  1. Функция получает готовый CollectionModify
  2. bind([id=>LED]) подставляет значение условия
  3. возвращён объект операции
  4. execute вызывается отдельно

Запись, вход и результат

interface mysql_xdevapi\CrudOperationBindable
Параметр / часть контрактаЗначение
НазначениеКонтракт одного метода bind; не отдельный объект подключения или результата.

Возвращаемое значение. Возвращает объект, поддерживающий CrudOperationBindable. В конкретном CollectionModify это тот же объект операции, а не результат SQL.

Два применения

Повторно использовать один понятный шаг привязки ID.

function bindDocument(mysql_xdevapi\CrudOperationBindable $operation, string $id): mysql_xdevapi\CrudOperationBindable {
    return $operation->bind(['id'=>$id]);
}

Исходные данные, полный запуск и результат.

Передать текущий статус отдельно от синтаксиса условия.

$operation = $collection->modify('status = :status');
$operation->bind(['status'=>'pending']);

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Интерфейс не предоставляет modify/patch и не создаётся через new. Значения не являются именами коллекций, путями полей или направлениями сортировки. Это контракт CRUD-привязки именованного массива; не переносите его в SqlStatement::bind, где в этом PHP-драйвере используются последовательные скалярные аргументы для ?. Не превращайте отсутствие обязательного placeholder в пустую строку.

Ниже полный краткий synopsis официальной страницы. В нём написано class, однако исходник PECL 8.0.30 этой версии регистрирует именно interface. Его нельзя создать через new; объект конкретной операции реализует объявленный метод.

class mysql_xdevapi\CrudOperationBindable {
    abstract public function bind(array $placeholder_values): mysql_xdevapi\CrudOperationBindable;
}

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

Версии. Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11.

mysql_xdevapi\CrudOperationBindable::bindОбщий код знает, как привязать значения условия, не зная остальных методов операции.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

abstract public CrudOperationBindable::bind(array $placeholder_values): mysql_xdevapi\CrudOperationBindable

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

Как применяется вызов
  1. Функция получает готовый CollectionModify
  2. bind([id=>LED]) подставляет значение условия
  3. возвращён объект операции
  4. execute вызывается отдельно

Запись, вход и результат

abstract public CrudOperationBindable::bind(array $placeholder_values): mysql_xdevapi\CrudOperationBindable
Параметр / часть контрактаЗначение
placeholder_valuesМассив имя→значение, например ["id"=>"LED"]. Ключ не содержит двоеточие; :id находится в выражении.

Возвращаемое значение. Возвращает объект, поддерживающий CrudOperationBindable. В конкретном CollectionModify это тот же объект операции, а не результат SQL.

Два применения

Повторно использовать один понятный шаг привязки ID.

function bindDocument(mysql_xdevapi\CrudOperationBindable $operation, string $id): mysql_xdevapi\CrudOperationBindable {
    return $operation->bind(['id'=>$id]);
}

Исходные данные, полный запуск и результат.

Передать текущий статус отдельно от синтаксиса условия.

$operation = $collection->modify('status = :status');
$operation->bind(['status'=>'pending']);

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Интерфейс не предоставляет modify/patch и не создаётся через new. Значения не являются именами коллекций, путями полей или направлениями сортировки. Это контракт CRUD-привязки именованного массива; не переносите его в SqlStatement::bind, где в этом PHP-драйвере используются последовательные скалярные аргументы для ?. Не превращайте отсутствие обязательного placeholder в пустую строку.

Контекст контракта: CrudOperationBindable.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

mysql_xdevapi\CrudOperationLimitableПередать ограничение порции в общий код, который не отвечает за саму выборку или изменение.

Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11. · mysql_xdevapi

interface mysql_xdevapi\CrudOperationLimitable

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

Как применяется вызов
  1. Порция содержит не более 2 документов
  2. limit не выполняет запись
  3. объект операции сохраняет ограничение
  4. execute применяет ограниченную операцию

Запись, вход и результат

interface mysql_xdevapi\CrudOperationLimitable
Параметр / часть контрактаЗначение
НазначениеКонтракт одного метода limit; не отдельный объект подключения или результата.

Возвращаемое значение. Объект с контрактом CrudOperationLimitable. Конкретный класс определяет смысл выполнения; у CollectionModify метод возвращает CollectionModify.

Два применения

В одном месте проверить положительный размер порции и настроить операцию.

function limitBatch(mysql_xdevapi\CrudOperationLimitable $operation, int $size): mysql_xdevapi\CrudOperationLimitable {
    if ($size < 1 || $size > 100) { throw new InvalidArgumentException('Недопустимый размер порции'); }
    return $operation->limit($size);
}

Исходные данные, полный запуск и результат.

Выполнить первую порцию по заранее определённому порядку.

$operation->sort('priority ASC','_id ASC')->limit(2);
$receipt = $operation->execute();

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Интерфейс не выбирает порядок и не обещает ровно rows совпадений. Две операции с limit(2) могут читать строки или менять документы — общий максимум не делает их эквивалентными. Оценивать стоимость и конкурентное выполнение нужно по конкретному запросу. Наличие этого контракта ничего не говорит о поддержке offset сервером.

Ниже полный краткий synopsis официальной страницы. В нём написано class, однако исходник PECL 8.0.30 этой версии регистрирует именно interface. Его нельзя создать через new; объект конкретной операции реализует объявленный метод.

class mysql_xdevapi\CrudOperationLimitable {
    abstract public function limit(int $rows): mysql_xdevapi\CrudOperationLimitable;
}

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

Версии. Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11.

mysql_xdevapi\CrudOperationLimitable::limitПередать ограничение порции в общий код, который не отвечает за саму выборку или изменение.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

abstract public CrudOperationLimitable::limit(int $rows): mysql_xdevapi\CrudOperationLimitable

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

Как применяется вызов
  1. Порция содержит не более 2 документов
  2. limit не выполняет запись
  3. объект операции сохраняет ограничение
  4. execute применяет ограниченную операцию

Запись, вход и результат

abstract public CrudOperationLimitable::limit(int $rows): mysql_xdevapi\CrudOperationLimitable
Параметр / часть контрактаЗначение
rowsМаксимум записей/документов конкретной операции; для чтения ограничивает выдачу, для CollectionModify — количество изменяемых документов.

Возвращаемое значение. Объект с контрактом CrudOperationLimitable. Конкретный класс определяет смысл выполнения; у CollectionModify метод возвращает CollectionModify.

Два применения

В одном месте проверить положительный размер порции и настроить операцию.

function limitBatch(mysql_xdevapi\CrudOperationLimitable $operation, int $size): mysql_xdevapi\CrudOperationLimitable {
    if ($size < 1 || $size > 100) { throw new InvalidArgumentException('Недопустимый размер порции'); }
    return $operation->limit($size);
}

Исходные данные, полный запуск и результат.

Выполнить первую порцию по заранее определённому порядку.

$operation->sort('priority ASC','_id ASC')->limit(2);
$receipt = $operation->execute();

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Интерфейс не выбирает порядок и не обещает ровно rows совпадений. Две операции с limit(2) могут читать строки или менять документы — общий максимум не делает их эквивалентными. Оценивать стоимость и конкурентное выполнение нужно по конкретному запросу. Наличие этого контракта ничего не говорит о поддержке offset сервером.

Контекст контракта: CrudOperationLimitable.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

mysql_xdevapi\CrudOperationSkippableЯвно отделить наличие метода пропуска от возможности выполнить операцию на сервере.

Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11. · mysql_xdevapi

interface mysql_xdevapi\CrudOperationSkippable

Явно отделить наличие метода пропуска от возможности выполнить операцию на сервере.

Как применяется вызов
  1. CollectionModify реализует Skippable
  2. skip(1) возвращает объект операции
  3. execute может дать 5012
  4. прикладной код выбирает рабочую стратегию окна

Запись, вход и результат

interface mysql_xdevapi\CrudOperationSkippable
Параметр / часть контрактаЗначение
НазначениеКонтракт одного метода skip; не отдельный объект подключения или результата.

Возвращаемое значение. Объект с контрактом CrudOperationSkippable. Этот возврат относится к настройке, не подтверждает успешный серверный результат.

Два применения

Отобразить доступный PHP-контракт в таблице возможностей конкретного объекта.

$supportsMethod = $operation instanceof mysql_xdevapi\CrudOperationSkippable;

Исходные данные, полный запуск и результат.

Отделить общий шаг настройки от обработки отказа при выполнении; полный урок даёт рабочий обход через выбранные ID.

function configureOffset(mysql_xdevapi\CrudOperationSkippable $operation, int $skip): mysql_xdevapi\CrudOperationSkippable {
    return $operation->skip($skip);
}
// Это только настройка; execute CollectionModify со skip(1) здесь даст 5012.

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

В проверенной связке PECL mysql_xdevapi 8.0.30/MySQL 8.4.13 skip(1) создаёт допустимую PHP-цепочку, но execute отклоняется сервером: mysql_xdevapi\Exception, код 5012, offset value is not allowed for this operation. После отказа документы остаются прежними. Это зафиксированное ограничение профиля, а не повод скрыть метод из полного справочника. Краткий пример Manual использует find()->skip(), но PHP CollectionFind в нашем рабочем обходе применяет offset(). Не угадывайте одинаковое имя метода по аналогии с другим классом или коннектором. Интерфейс не гарантирует устойчивый порядок; сначала определяют сортировку и согласованность набора.

Ниже полный краткий synopsis официальной страницы. В нём написано class, однако исходник PECL 8.0.30 этой версии регистрирует именно interface. Его нельзя создать через new; объект конкретной операции реализует объявленный метод.

class mysql_xdevapi\CrudOperationSkippable {
    abstract public function skip(int $skip): mysql_xdevapi\CrudOperationSkippable;
}

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

Версии. Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11.

mysql_xdevapi\CrudOperationSkippable::skipЯвно отделить наличие метода пропуска от возможности выполнить операцию на сервере.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

abstract public CrudOperationSkippable::skip(int $skip): mysql_xdevapi\CrudOperationSkippable

Явно отделить наличие метода пропуска от возможности выполнить операцию на сервере.

Как применяется вызов
  1. CollectionModify реализует Skippable
  2. skip(1) возвращает объект операции
  3. execute может дать 5012
  4. прикладной код выбирает рабочую стратегию окна

Запись, вход и результат

abstract public CrudOperationSkippable::skip(int $skip): mysql_xdevapi\CrudOperationSkippable
Параметр / часть контрактаЗначение
skipКоличество пропускаемых элементов; смысл и серверная допустимость определяются конкретной операцией.

Возвращаемое значение. Объект с контрактом CrudOperationSkippable. Этот возврат относится к настройке, не подтверждает успешный серверный результат.

Два применения

Отобразить доступный PHP-контракт в таблице возможностей конкретного объекта.

$supportsMethod = $operation instanceof mysql_xdevapi\CrudOperationSkippable;

Исходные данные, полный запуск и результат.

Отделить общий шаг настройки от обработки отказа при выполнении; полный урок даёт рабочий обход через выбранные ID.

function configureOffset(mysql_xdevapi\CrudOperationSkippable $operation, int $skip): mysql_xdevapi\CrudOperationSkippable {
    return $operation->skip($skip);
}
// Это только настройка; execute CollectionModify со skip(1) здесь даст 5012.

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

В проверенной связке PECL mysql_xdevapi 8.0.30/MySQL 8.4.13 skip(1) создаёт допустимую PHP-цепочку, но execute отклоняется сервером: mysql_xdevapi\Exception, код 5012, offset value is not allowed for this operation. После отказа документы остаются прежними. Это зафиксированное ограничение профиля, а не повод скрыть метод из полного справочника. Краткий пример Manual использует find()->skip(), но PHP CollectionFind в нашем рабочем обходе применяет offset(). Не угадывайте одинаковое имя метода по аналогии с другим классом или коннектором. Интерфейс не гарантирует устойчивый порядок; сначала определяют сортировку и согласованность набора.

Контекст контракта: CrudOperationSkippable.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

mysql_xdevapi\CrudOperationSortableПередать известное правило порядка в общий код, не загружая весь набор в массив PHP.

Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11. · mysql_xdevapi

interface mysql_xdevapi\CrudOperationSortable

Передать известное правило порядка в общий код, не загружая весь набор в массив PHP.

Как применяется вызов
  1. priority ASC определяет срочность
  2. _id ASC различает документы с равным priority
  3. порядок применён на сервере
  4. limit делает первую порцию предсказуемой

Запись, вход и результат

interface mysql_xdevapi\CrudOperationSortable
Параметр / часть контрактаЗначение
НазначениеКонтракт одного метода sort; не отдельный объект подключения или результата.

Возвращаемое значение. Объект с контрактом CrudOperationSortable для продолжения настройки. У конкретного CollectionModify фактический класс остаётся CollectionModify.

Два применения

Применять одно явное правило порядка к подходящим операциям.

function byPublicationOrder(mysql_xdevapi\CrudOperationSortable $operation): mysql_xdevapi\CrudOperationSortable {
    return $operation->sort('priority ASC','_id ASC');
}

Исходные данные, полный запуск и результат.

Задать стабильную следующую порцию известной очереди.

$operation->sort('_id ASC')->limit(2);

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Порядок соответствует смыслу ORDER BY; интерфейс не разрешает произвольную SQL-строку от пользователя. Входные значения направления/поля выбираются из словаря приложения, а не экранируются как данные через bind. Без уникального последнего ключа равные значения оставляют неоднозначность. Объекты с orderby вместо sort не получают этот контракт только потому, что тоже умеют сортировать.

Ниже полный краткий synopsis официальной страницы. В нём написано class, однако исходник PECL 8.0.30 этой версии регистрирует именно interface. Его нельзя создать через new; объект конкретной операции реализует объявленный метод.

class mysql_xdevapi\CrudOperationSortable {
    abstract public function sort(string $sort_expr): mysql_xdevapi\CrudOperationSortable;
}

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

Версии. Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11.

mysql_xdevapi\CrudOperationSortable::sortПередать известное правило порядка в общий код, не загружая весь набор в массив PHP.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

abstract public CrudOperationSortable::sort(string $sort_expr): mysql_xdevapi\CrudOperationSortable

Передать известное правило порядка в общий код, не загружая весь набор в массив PHP.

Как применяется вызов
  1. priority ASC определяет срочность
  2. _id ASC различает документы с равным priority
  3. порядок применён на сервере
  4. limit делает первую порцию предсказуемой

Запись, вход и результат

abstract public CrudOperationSortable::sort(string $sort_expr): mysql_xdevapi\CrudOperationSortable
Параметр / часть контрактаЗначение
sort_exprОдно либо несколько выражений сортировки; ASC/DESC, приоритет слева направо. Реальная декларация интерфейса 8.0.30 вариативная, как и приведённый официальный пример.

Возвращаемое значение. Объект с контрактом CrudOperationSortable для продолжения настройки. У конкретного CollectionModify фактический класс остаётся CollectionModify.

Два применения

Применять одно явное правило порядка к подходящим операциям.

function byPublicationOrder(mysql_xdevapi\CrudOperationSortable $operation): mysql_xdevapi\CrudOperationSortable {
    return $operation->sort('priority ASC','_id ASC');
}

Исходные данные, полный запуск и результат.

Задать стабильную следующую порцию известной очереди.

$operation->sort('_id ASC')->limit(2);

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

Порядок соответствует смыслу ORDER BY; интерфейс не разрешает произвольную SQL-строку от пользователя. Входные значения направления/поля выбираются из словаря приложения, а не экранируются как данные через bind. Без уникального последнего ключа равные значения оставляют неоднозначность. Объекты с orderby вместо sort не получают этот контракт только потому, что тоже умеют сортировать.

Контекст контракта: CrudOperationSortable.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

mysql_xdevapi\ExecutableВыполнить уже настроенную операцию в понятном месте программы и получить её фактический результат.

Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11. · mysql_xdevapi

interface mysql_xdevapi\Executable

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

Как применяется вызов
  1. Настройки собраны, документ остаётся draft
  2. execute отправляет операцию серверу
  3. сервер меняет status на published
  4. возвращена квитанция Result; новый getOne подтверждает запись

Запись, вход и результат

interface mysql_xdevapi\Executable
Параметр / часть контрактаЗначение
НазначениеКонтракт одного метода execute; не отдельный объект подключения или результата.

Возвращаемое значение. Один из объектов результата. Manual показывает Result в сигнатуре, но поясняет Result/SqlStatementResult; конкретные операции чтения также возвращают DocResult/RowResult. В этой программе CollectionModify возвращает Result.

Два применения

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

function runOperation(mysql_xdevapi\Executable $operation) {
    return $operation->execute();
}
$receipt = runOperation($operation);

Исходные данные, полный запуск и результат.

Получить квитанцию ограниченного изменения и перечитать реально опубликованные ID.

$receipt = $operation->execute();
$affected = $receipt->getAffectedItemsCount();
$published = $collection->find('status = :status')->bind(['status'=>'published'])->fields('_id')->execute()->fetchAll();

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

execute не означает commit ранее открытой транзакции: её завершение управляется отдельно. До execute вызовы bind/limit/sort лишь настраивают операцию. Ошибки сервера и соединения нужно обрабатывать по задаче, не заменяя их пустым результатом. Повторный вызов может повторно применить изменение; интерфейс не обещает идемпотентность, откат либо автоматические повторы.

Ниже полный краткий synopsis официальной страницы. В нём написано class, однако исходник PECL 8.0.30 этой версии регистрирует именно interface. Его нельзя создать через new; объект конкретной операции реализует объявленный метод.

class mysql_xdevapi\Executable {
    abstract public function execute(): mysql_xdevapi\Result;
}

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

Версии. Интерфейс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11.

mysql_xdevapi\Executable::executeВыполнить уже настроенную операцию в понятном месте программы и получить её фактический результат.

Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах. · mysql_xdevapi

abstract public Executable::execute(): mysql_xdevapi\Result

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

Как применяется вызов
  1. Настройки собраны, документ остаётся draft
  2. execute отправляет операцию серверу
  3. сервер меняет status на published
  4. возвращена квитанция Result; новый getOne подтверждает запись

Запись, вход и результат

abstract public Executable::execute(): mysql_xdevapi\Result
Параметр / часть контрактаЗначение
ПараметрыВ официальном контракте параметров нет. Конкретный метод класса может иметь собственные возможности, их проверяют отдельно.

Возвращаемое значение. Один из объектов результата. Manual показывает Result в сигнатуре, но поясняет Result/SqlStatementResult; конкретные операции чтения также возвращают DocResult/RowResult. В этой программе CollectionModify возвращает Result.

Два применения

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

function runOperation(mysql_xdevapi\Executable $operation) {
    return $operation->execute();
}
$receipt = runOperation($operation);

Исходные данные, полный запуск и результат.

Получить квитанцию ограниченного изменения и перечитать реально опубликованные ID.

$receipt = $operation->execute();
$affected = $receipt->getAffectedItemsCount();
$published = $collection->find('status = :status')->bind(['status'=>'published'])->fields('_id')->execute()->fetchAll();

Исходные данные, полный запуск и результат.

Условия, ошибки и альтернативы

execute не означает commit ранее открытой транзакции: её завершение управляется отдельно. До execute вызовы bind/limit/sort лишь настраивают операцию. Ошибки сервера и соединения нужно обрабатывать по задаче, не заменяя их пустым результатом. Повторный вызов может повторно применить изменение; интерфейс не обещает идемпотентность, откат либо автоматические повторы.

Контекст контракта: Executable.

Версии. Страница метода не сообщает первую версию. Примеры относятся к PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13; версия PHP записана в результатах.

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

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

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

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

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