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

Дерево руководства
Глава 129 · После первой коллекции и базовых операций изменения
MySQL X DevAPI: patch, замена полей и ограниченные порции документов
От названия одной карточки до вложенного Merge Patch, различия null и удаления, замены существующих полей, устойчивых порций и осмысленных CRUD-интерфейсов.
Сохранить часть карточки и увидеть результат
В карточке светильника хранятся название, артикул и остаток. Редактор меняет только название. Нужно сохранить новый текст, оставить остальные данные и убедиться, что соседний товар не затронут. Затем та же потребность вырастет до вложенных настроек, удаления устаревшего поля и публикации ограниченной порции карточек.
| Поле | До | После изменения названия |
|---|---|---|
| _id | LED | LED |
| name | Светильник | Лампа |
| sku | LED-01 | LED-01 |
| stock | 4 | 4 |
Сначала представим карточку обычным ассоциативным массивом 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- name = Лампа попадает в массив изменений
- json_encode создаёт JSON-объект только с name
- modify + bind выбирают документ LED
- patch настраивает изменение выбранного документа
- execute выполняет запись и возвращает квитанцию Result
- 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; соседняя карточка кабеля останется прежней.
- LED: name Светильник, status draft, stock = 4
- patch содержит только name → Лампа
- Два поля формы → JSON-объект name/status
- Условие _id=:id + bind LED выбирает один документ
- 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 одного поля | Лампа |
| Статус после первого patch | draft |
| Название после формы | Светильник PRO |
| Статус после формы | published |
| Артикул сохранён | LED-01 |
| Остаток сохранён | 4 |
| Другой товар не изменён | да |
| Изменено вторым patch | 1 |
Самостоятельный файл и архив для запуска
<?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, чтобы увидеть отличие от отсутствующего ключа.
- display: color white, width = 200 → patch color blue
- Рекурсивное слияние сохраняет width = 200
- contact.phone:null в patch → ключ удалён, email сохранён
- tags:[featured] → старые sale/new не добавляются
- 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 | нет |
| Метки после patch | featured |
| Добавлена гарантия, месяцев | 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 здесь ещё не доказывает, что новое значение появилось.
- LED имеет price_cents = 1000 → replace(1200) меняет поле
- CABLE не имеет price_cents → replace(900) не создаёт его
- Квитанция отсутствующего пути даёт 1, чтение показывает прежний документ
- set(900) добавляет поле у CABLE
- Везде сохранены _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. Выбор и ограничение выполняет сервер: приложение не загружает весь каталог для сортировки массива.
- Фильтр оставляет pending: A,B,C,D,E
- priority ASC ставит A/B первыми
- _id ASC различает карточки с одинаковым priority = 1
- limit(2) выбирает A и B
- 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 |
| Опубликованные ID | A, 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 порциями, не увеличивая смещение у уменьшающегося набора.
- A,B,C,D,E → пропустить 1, взять 2 означает B,C
- CollectionModify::skip(1) в этом профиле → ошибка 5012, записи прежние
- CollectionFind::offset(1) / limit(2) возвращает B,C
- modify с привязкой ID отмечает именно B/C
- 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.skip | 5012 |
| После отказа все документы прежние | да |
| Выбранное окно | B, C |
| Реально отмеченные ID | B, C |
| Порции без смещения | 2, 2, 1 |
| Осталось pending | 0 |
| Итоговые статусы | done, done, done, done, done |
Самостоятельный файл и архив для запуска
<?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.
- modify+patch готовят объект операции без записи
- bindDocument143 получает тот же объект и ID LED
- bind/sort/limit возвращают CollectionModify; статус ещё draft
- executeOperation143 вызывает execute → сервер сохраняет published
- возвращается 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. Это удобно, когда общий шаг нужен нескольким конкретным операциям; для одного короткого вызова дополнительная функция не обязательна.
Что получится
| Наблюдение | Результат |
|---|---|
| Статус до 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
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Сохранить только переданные настройки карточки, не потеряв остальные поля.
- LED: name Светильник, stock = 4
- patch {name:Лампа} меняет только name
- stock = 4 сохраняется, пока не включён в patch
- execute → getOne → новое название
Запись, вход и результат
CollectionModify::patch(string $document): mysql_xdevapi\CollectionModify| Параметр / часть контракта | Значение |
|---|---|
| document | JSON-строка с объектом изменений. Один 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Заменить значение по существующему пути внутри документа.
- LED.price_cents = 1000 → replace(1200) → 1200
- CABLE без price_cents → replace(900) → поля по-прежнему нет
- Result: affected = 1 не доказывает изменение значения
- 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Выбрать, какие совпадения попадут в ограниченную порцию изменений.
- pending A/B имеют одинаковый priority = 1
- priority ASC делает их первыми
- _id ASC различает A и B
- 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Ограничить число документов одного изменения.
- Выбраны 5 документов pending
- sort задаёт порядок
- limit(2) ограничивает первое изменение
- после 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Разобраться со смещением и ограничением конкретного сервера.
- Желаемое окно A/B/C/D/E: skip(1) + limit(2) → B/C
- Метод skip существует в PHP
- execute отклоняет offset кодом 5012
- Рабочий 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Общий код знает, как привязать значения условия, не зная остальных методов операции.
Запись, вход и результат
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Общий код знает, как привязать значения условия, не зная остальных методов операции.
Запись, вход и результат
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Передать ограничение порции в общий код, который не отвечает за саму выборку или изменение.
- Порция содержит не более 2 документов
- limit не выполняет запись
- объект операции сохраняет ограничение
- 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Передать ограничение порции в общий код, который не отвечает за саму выборку или изменение.
- Порция содержит не более 2 документов
- limit не выполняет запись
- объект операции сохраняет ограничение
- 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Явно отделить наличие метода пропуска от возможности выполнить операцию на сервере.
- CollectionModify реализует Skippable
- skip(1) возвращает объект операции
- execute может дать 5012
- прикладной код выбирает рабочую стратегию окна
Запись, вход и результат
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Явно отделить наличие метода пропуска от возможности выполнить операцию на сервере.
- CollectionModify реализует Skippable
- skip(1) возвращает объект операции
- execute может дать 5012
- прикладной код выбирает рабочую стратегию окна
Запись, вход и результат
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.
- priority ASC определяет срочность
- _id ASC различает документы с равным priority
- порядок применён на сервере
- 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.
- priority ASC определяет срочность
- _id ASC различает документы с равным priority
- порядок применён на сервере
- 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Выполнить уже настроенную операцию в понятном месте программы и получить её фактический результат.
- Настройки собраны, документ остаётся draft
- execute отправляет операцию серверу
- сервер меняет status на published
- возвращена квитанция 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Выполнить уже настроенную операцию в понятном месте программы и получить её фактический результат.
- Настройки собраны, документ остаётся draft
- execute отправляет операцию серверу
- сервер меняет status на published
- возвращена квитанция 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 записана в результатах.
Источники и обсуждения
- PHP Manual — CollectionModify::patch
- PHP Manual — CollectionModify::replace
- PHP Manual — CollectionModify::sort
- PHP Manual — CollectionModify::limit
- PHP Manual — CollectionModify::skip
- PHP Manual — CrudOperationBindable
- PHP Manual — CrudOperationBindable::bind
- PHP Manual — CrudOperationLimitable
- PHP Manual — CrudOperationLimitable::limit
- PHP Manual — CrudOperationSkippable
- PHP Manual — CrudOperationSkippable::skip
- PHP Manual — CrudOperationSortable
- PHP Manual — CrudOperationSortable::sort
- PHP Manual — Executable
- PHP Manual — Executable::execute
- MySQL X DevAPI: Collection.modify и правила patch
- RFC 7396: JSON Merge Patch
- MySQL 8.4: функции изменения JSON
- MySQL worklog 10797: отображение MERGE_PATCH в X Protocol
- PECL mysql_xdevapi 8.0.30: выбранная реализация PHP API
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


