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

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

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

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

Глава 122 · После первого каталога X DevAPI, массивов, объектов и исключений

MySQL Document Store: изменения документа и проверяемая квитанция

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

Изменить каталог и увидеть сохранённое состояние

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

Одна карточка — разные решения
  1. LED-1: price1200 → set price1350; stock4 остаётся
  2. KIT-1: Основа, Винт → append Шайба → insert Клипса на индекс1
  3. Полная CARD-1 → replaceOne и проверка текста; для8.0.30 рабочая замена корня через set; новая CARD-2 → явный addOrReplaceOne
  4. OLD-2 и OLD-3 → ограниченное удаление; LIVE-1 остаётся
  5. P-OK + EV-OK → общая транзакция → обе записи
  6. HTML-форма → проверка → запись → GET читает новую цену

Документ удобно представить карточкой с подписанными графами. Изменить одну графу — set, выдать полностью новую карточку — replaceOne, убрать всю карточку — removeOne. Это модель выбора действия, а не устройство памяти MySQL. _id адресует документ, путь supplier.city адресует свойство внутри него. Квитанция сообщает число затронутых документов, но не заменяет их содержание.

Перед этой главой пройдите подключение, коллекцию и первое чтение. Здесь уже используются массивы, вызов метода объекта и try/catch/finally. При этом новую операцию разбираем до длинной программы: адрес → изменение → отправка → чтение результата.

ПотребностьВыбор и результат
Поправить часть свойствmodify + set / unset; соседи сохраняются.
Передать весь новый документCollection::replaceOne и обязательное чтение: на выбранном8.0.30 Unicode искажён. Рабочая полная замена корня через modify/set сохраняет текст; неизвестный ключ остаётся отсутствующим.
Разрешить создание или полную заменуCollection::addOrReplaceOne; это явная политика upsert.
Удалить конкретный ключ или очередьCollection::removeOne либо Collection::remove с пределом.
Сохранить две связанные записиSession::startTransaction → действия → Session::commit; при отказе Session::rollback.

В этих программах php8.3.35, расширение mysql_xdevapi8.0.30 и MySQL8.4.13 образуют отдельное воспроизводимое окружение. Это конкретный стенд, а не утверждение о последних версиях. Нужны обе переменные PHPMAN_MYSQLX_URI и PHPMAN_MYSQLX_SCHEMA, отдельная схема и доступный loopback X-порт с TLS. Подготовка описана в установке и README. Полный URI остаётся в окружении и не выводится.

Каждый CLI создаёт собственное имя mysql139_… и регистрирует владение только после успешного создания. В finally удаляются только зарегистрированные коллекции и закрывается сеанс. Пять сценариев используют по одной коллекции, транзакционный — две. HTTP-каталог живёт дольше одного запроса и очищается отдельной командой после проверки формы.

У выбранного расширения mysql_xdevapi8.0.30 есть подтверждённая оговорка: getOne для отсутствующего ключа может оставить неопределённое значение вместо документированного null. Поэтому там, где карточка может отсутствовать, используется $document = $collection->getOne($id) ?? null. Оператор ?? берёт значение справа, если слева нет определённого ненулевого результата. Он не подавляет исключения и не превращает ошибку соединения в «карточка не найдена». Другой проверенный путь — find с условием по _id и fetchOne. На нормализованном результате можно надёжно выбрать HTTP404.

Исправить цену, сохранив остальные свойства

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

Путь данных этой программы
  1. LED-1: price=1200, supplier={city:Тверь, code:S-7}, stock=4
  2. modify выбирает _id=LED-1; set задаёт price=1350 и supplier.city=Ржев
  3. До execute цена остаётся1200; unset планирует убрать obsolete
  4. После execute: price=1350, supplier.code=S-7, stock=4; obsolete отсутствует

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

В строке условия :id — место для значения; ключ массива bind пишется id без двоеточия. Стрелка -> вызывает метод объекта, а возвращённый CollectionModify позволяет продолжить цепочку. set получает путь поля и новое значение. Точка в supplier.city выбирает вложенное поле, не весь supplier. unset принимает массив имён, даже для одного поля. execute отправляет накопленное изменение; getOne делает отдельное чтение. array_key_exists проверяет само присутствие ключа, поэтому отличает удаление от сохранённого null.

$operation = $collection->modify('_id = :id')->bind(['id'=>'LED-1']);
$operation->set('price', 1350);
$receipt = $operation->execute();
$after = $collection->getOne('LED-1');

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

Ожидаемая ведомость

НаблюдениеРезультат
Цена до отправки1200
Цена после отправки1350
Город поставщикаРжев
Код поставщикаS-7
Остаток4
Устаревшее поле удаленода
Изменено документов1
Отсутствующий ключ: изменено0
Самостоятельная программа, исходные данные и архив
Исправить цену, сохранив остальные свойстваСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x139Config();
$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 = x139Name('preserve');
$handle = $schema->getCollection($name);
$existed = $handle->existsInDatabase();
if ($existed) {throw new RuntimeException('Случайное имя уже занято.');}
$collection = $schema->createCollection($name);
$owned[] = $name;

    $collection->add(['_id'=>'LED-1','name'=>'Лампа','price'=>1200,
        'stock'=>4,'supplier'=>['city'=>'Тверь','code'=>'S-7'],'obsolete'=>'старый ярлык'])->execute();
    $before = $collection->getOne('LED-1');
    $operation = $collection->modify('_id = :id')->bind(['id'=>'LED-1'])
        ->set('price', 1350)->set('supplier.city', 'Ржев')->unset(['obsolete']);
    $prepared = $collection->getOne('LED-1');
    $receipt = $operation->execute();
    $after = $collection->getOne('LED-1');
    $missing = $collection->modify('_id = :id')->bind(['id'=>'NO-SUCH'])
        ->set('price', 900)->execute();
    $rows = [['Цена до отправки', $prepared['price']], ['Цена после отправки', $after['price']],
        ['Город поставщика', $after['supplier']['city']], ['Код поставщика', $after['supplier']['code']],
        ['Остаток', $after['stock']], ['Устаревшее поле удалено', !array_key_exists('obsolete',$after)],
        ['Изменено документов', $receipt->getAffectedItemsCount()],
        ['Отсутствующий ключ: изменено', $missing->getAffectedItemsCount()]];
    $checks = ['preparation_does_not_write'=>$prepared===$before,
        'specific_fields_changed'=>$after['price']===1350 && $after['supplier']['city']==='Ржев',
        'siblings_preserved'=>$after['name']==='Лампа' && $after['stock']===4 && $after['supplier']['code']==='S-7',
        'obsolete_absent'=>!array_key_exists('obsolete',$after),
        'affected_is_one'=>$receipt->getAffectedItemsCount()===1,
        'missing_is_zero'=>$missing->getAffectedItemsCount()===0 && ($collection->getOne('NO-SUCH') ?? null)===null];
    $observations = ['before'=>$before,'before_execute'=>$prepared,'after'=>$after];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x139Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x139Report('mysql139-x-preserve-properties', 'Исправить цену, сохранив остальные свойства', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Цена до отправки — 1200
Цена после отправки — 1350
Город поставщика — Ржев
Код поставщика — S-7
Остаток — 4
Устаревшее поле удалено — да
Изменено документов — 1
Отсутствующий ключ: изменено — 0

Программа получает настоящую квитанцию MySQL, читает сохранённые поля и в finally очищает только свои коллекции. result.html показывает строки, result.json сохраняет наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · JSON сохранённого запуска.

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

Условие true выбрало бы всю коллекцию. Ноль в квитанции для NO-SUCH — отсутствие совпадения, а не создание новой карточки. Чтение после записи показывает состояние в момент чтения; при других писателях оно не доказывает, что документ больше не изменится. Пути полей задаёт автор программы, переменные значения передаются отдельно.

В вопросе о Node.js операция была настроена, но не отправлена через execute. Это реальная путаница между объектом команды и результатом; PHP-синтаксис здесь взят из PHP Manual, а не из JavaScript-ответа. Реальная ситуация: Collection.modify не меняет документ.

Следующее различие — полная замена карточки. Отсутствие свойства и JSON null сравниваются в трёх состояниях JSON.

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

Измените только supplier.code на S-8. Убедитесь, что город Ржев и остаток4 сохранились.

Как проверить решение

Вызовите modify('_id = :id')->bind(['id'=>'LED-1'])->set('supplier.code','S-8')->execute(), затем getOne('LED-1'). Сравните code, city и stock по отдельности; замена всего supplier массивом с одним code убрала бы соседний city.

Дополнить комплект и поставить элемент на нужное место

В инструкции по сборке важен порядок: шайба дополняет комплект в конце, а клипса должна стоять перед винтом. Сравним две операции на одном сохранённом массиве и проверим именно последовательность элементов.

Путь данных этой программы
  1. options[0]=Основа, options[1]=Винт
  2. arrayAppend(options, Шайба) → Основа, Винт, Шайба
  3. arrayInsert(options[1], Клипса) → Основа, Клипса, Винт, Шайба
  4. Строка price + 1 → обычный пятый элемент, не вычисление

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

Квадратные скобки в строке options[1] принадлежат пути документа:0 — первый элемент,1 — второй. arrayAppend выбирает конец, arrayInsert — указанную позицию; прежние элементы справа сдвигаются. PHP implode соединяет прочитанный массив строк для понятной ведомости, но не меняет документ. В названии параметра expression_or_literal слово expression не превращает любую строку в выражение: для выражений API есть отдельный mysql_xdevapi\Expression; здесь намеренно передаём литералы.

$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayAppend('options', 'Шайба')->execute();
$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayInsert('options[1]', 'Клипса')->execute();

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

Ожидаемая ведомость

НаблюдениеРезультат
Исходный комплектОснова → Винт
После добавления в конецОснова → Винт → Шайба
После вставки на индекс1Основа → Клипса → Винт → Шайба
Текст добавлен буквальноprice + 1
Квитанции трёх изменений3
Самостоятельная программа, исходные данные и архив
Дополнить комплект и поставить элемент на нужное местоСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x139Config();
$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 = x139Name('options');
$handle = $schema->getCollection($name);
$existed = $handle->existsInDatabase();
if ($existed) {throw new RuntimeException('Случайное имя уже занято.');}
$collection = $schema->createCollection($name);
$owned[] = $name;

    $collection->add(['_id'=>'KIT-1','name'=>'Набор крепежа','options'=>['Основа','Винт']])->execute();
    $before = $collection->getOne('KIT-1');
    $append = $collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
        ->arrayAppend('options', 'Шайба')->execute();
    $appended = $collection->getOne('KIT-1');
    $insert = $collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
        ->arrayInsert('options[1]', 'Клипса')->execute();
    $inserted = $collection->getOne('KIT-1');
    $literal = $collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
        ->arrayAppend('options', 'price + 1')->execute();
    $after = $collection->getOne('KIT-1');
    $rows = [['Исходный комплект',implode(' → ',$before['options'])],
        ['После добавления в конец',implode(' → ',$appended['options'])],
        ['После вставки на индекс1',implode(' → ',$inserted['options'])],
        ['Текст добавлен буквально',$after['options'][4]],
        ['Квитанции трёх изменений',$append->getAffectedItemsCount()+$insert->getAffectedItemsCount()+$literal->getAffectedItemsCount()]];
    $checks = ['append_at_end'=>$appended['options']===['Основа','Винт','Шайба'],
        'insert_at_position'=>$inserted['options']===['Основа','Клипса','Винт','Шайба'],
        'literal_not_expression'=>$after['options'][4]==='price + 1',
        'unchanged_name'=>$after['name']==='Набор крепежа',
        'three_receipts'=>$append->getAffectedItemsCount()===1 && $insert->getAffectedItemsCount()===1 && $literal->getAffectedItemsCount()===1];
    $observations = ['before'=>$before,'appended'=>$appended,'inserted'=>$inserted,'after'=>$after];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x139Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x139Report('mysql139-x-array-options', 'Дополнить комплект и поставить элемент на нужное место', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Исходный комплект — Основа → Винт
После добавления в конец — Основа → Винт → Шайба
После вставки на индекс1 — Основа → Клипса → Винт → Шайба
Текст добавлен буквально — price + 1
Квитанции трёх изменений — 3

Программа получает настоящую квитанцию MySQL, читает сохранённые поля и в finally очищает только свои коллекции. result.html показывает строки, result.json сохраняет наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · JSON сохранённого запуска.

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

В тексте PHP Manual для arrayInsert встречается «после», но его собственный пример с индексом1 показывает вставку перед прежним вторым элементом. Урок проверяет этот порядок повторным чтением. Пример заранее создаёт options как массив и не обещает одинакового поведения для отсутствующего поля, скаляра и неверного пути. Два отдельных execute — две самостоятельные записи.

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

Добавьте «Инструкция» в начало списка и сохраните прежнюю последовательность остальных элементов.

Как проверить решение

В отдельной операции примените arrayInsert('options[0]','Инструкция'). После чтения ожидается Инструкция, Основа, Клипса, Винт, Шайба, price + 1. Не заменяйте позицию строковой интерполяцией пользовательского пути; выбирайте её в проверенном коде.

Выбрать полную замену или явное создание

Поставщик прислал целую новую карточку. Здесь забытое поле должно исчезнуть, а неизвестный артикул нельзя незаметно создать обычной командой замены. Сравним две явно выбранные политики и сохраним название с кириллицей, кавычками и è без изменения. Реальный запуск выявил дефект replaceOne в mysql_xdevapi8.0.30: сначала покажем испорченное чтение, затем выполним проверенную полную замену через корень документа.

Путь данных этой программы
  1. CARD-1 содержит name, price, obsolete
  2. replaceOne(CARD-1, новый документ) → счётчик1, но Unicode искажён в выбранной версии
  3. modify по _id + set корня $ готовой JSON-строкой → точное название, obsolete отсутствует
  4. replaceOne и полная замена корня для CARD-2 →0 и нормализованный null, потому что CARD-2 ещё нет
  5. addOrReplaceOne(CARD-2, документ) → создаёт; следующий вызов заменяет целиком

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

JSON описывает весь новый документ. json_encode преобразует PHP-массив в JSON; JSON_THROW_ON_ERROR делает ошибку кодирования исключением, JSON_UNESCAPED_UNICODE оставляет буквы в строке. Путь '$' в set означает корень документа, поэтому заменяется целиком выбранная карточка. В этой полной форме _id явно включён и совпадает со связанным условием. Передаётся именно готовая JSON-строка, не PHP-массив: в исследованной версии внутреннее повторное кодирование массива снова портит Unicode. Строка формируется json_encode из данных приложения, а не принимается как произвольное выражение посетителя. replaceOne/addOrReplaceOne — короткие методы, которые уже выполняют запись и возвращают Result; после них дополнительного execute нет. getOne с ?? null отдельно проверяет фактические поля и отсутствие.

$json = json_encode(['_id'=>'CARD-1','name'=>'Кафе «Тёплый свет» — è','price'=>1500],
    JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$receipt = $collection->modify('_id = :id')->bind(['id'=>'CARD-1'])
    ->set('$', $json)->execute();
$saved = $collection->getOne('CARD-1') ?? null;

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

Ожидаемая ведомость

НаблюдениеРезультат
Замена существующей: затронуто1
Unicode после replaceOne совпалнет
Полная замена через корень: затронуто1
Поле obsolete отсутствуетда
Сохранённое названиеКафе «Тёплый свет» — è
Замена отсутствующей: затронуто0
После replaceOne CARD-2 отсутствуетда
Полная замена отсутствующей: затронуто0
После addOrReplaceOne CARD-2 существуетда
Цена CARD-2 после повторной замены1600
Всего карточек2
Самостоятельная программа, исходные данные и архив
Выбрать полную замену или явное созданиеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x139Config();
$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 = x139Name('replace');
$handle = $schema->getCollection($name);
$existed = $handle->existsInDatabase();
if ($existed) {throw new RuntimeException('Случайное имя уже занято.');}
$collection = $schema->createCollection($name);
$owned[] = $name;

    $collection->add(['_id'=>'CARD-1','name'=>'Старая карточка','price'=>1200,'obsolete'=>'убрать'])->execute();
    $replacement = json_encode(['name'=>'Кафе «Тёплый свет» — è','price'=>1500], JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR);
    $replaced = $collection->replaceOne('CARD-1', $replacement);
    $afterShortcut = $collection->getOne('CARD-1');
    $shortcutUnicodeExact = $afterShortcut['name'] === 'Кафе «Тёплый свет» — è';
    // В mysql_xdevapi8.0.30 replaceOne искажает Unicode: сохраняем наблюдение, а не выдаём его за успех.
    // Передаём готовую JSON-строку прямо в set корня документа, сохраняя тот же _id.
    $rootReplacement = json_encode(['_id'=>'CARD-1','name'=>'Кафе «Тёплый свет» — è','price'=>1500],
        JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR);
    $rootReplaced = $collection->modify('_id = :id')->bind(['id'=>'CARD-1'])
        ->set('$', $rootReplacement)->execute();
    $afterReplace = $collection->getOne('CARD-1');
    $missing = $collection->replaceOne('CARD-2', $replacement);
    $stillMissing = $collection->getOne('CARD-2') ?? null;
    $missingRootReplacement = json_encode(['_id'=>'CARD-2','name'=>'Кафе «Тёплый свет» — è','price'=>1500],
        JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR);
    $missingRoot = $collection->modify('_id = :id')->bind(['id'=>'CARD-2'])
        ->set('$', $missingRootReplacement)->execute();
    $stillMissingAfterRoot = $collection->getOne('CARD-2') ?? null;
    $created = $collection->addOrReplaceOne('CARD-2', $replacement);
    $afterCreate = $collection->getOne('CARD-2');
    $upsertReplacement = json_encode(['name'=>'Кафе «Тёплый свет» — è','price'=>1600], JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR);
    $upsertExisting = $collection->addOrReplaceOne('CARD-2', $upsertReplacement);
    $afterUpsert = $collection->getOne('CARD-2');
    $rows = [['Замена существующей: затронуто',$replaced->getAffectedItemsCount()],
        ['Unicode после replaceOne совпал',$shortcutUnicodeExact],
        ['Полная замена через корень: затронуто',$rootReplaced->getAffectedItemsCount()],
        ['Поле obsolete отсутствует',!array_key_exists('obsolete',$afterReplace)],
        ['Сохранённое название',$afterReplace['name']],
        ['Замена отсутствующей: затронуто',$missing->getAffectedItemsCount()],
        ['После replaceOne CARD-2 отсутствует',$stillMissing===null],
        ['Полная замена отсутствующей: затронуто',$missingRoot->getAffectedItemsCount()],
        ['После addOrReplaceOne CARD-2 существует',$afterCreate!==null],
        ['Цена CARD-2 после повторной замены',$afterUpsert['price']],['Всего карточек',$collection->count()]];
    $checks = ['replace_existing'=>$replaced->getAffectedItemsCount()===1 && $afterShortcut['_id']==='CARD-1',
        'shortcut_unicode_mismatch_observed'=>$shortcutUnicodeExact===false,
        'root_replacement_applied'=>$rootReplaced->getAffectedItemsCount()===1 && $afterReplace['_id']==='CARD-1',
        'omitted_field_removed'=>!array_key_exists('obsolete',$afterReplace),
        'unicode_roundtrip'=>$afterReplace['name']==='Кафе «Тёплый свет» — è',
        'replace_missing_does_not_insert'=>$missing->getAffectedItemsCount()===0 && $stillMissing===null,
        'root_missing_does_not_insert'=>$missingRoot->getAffectedItemsCount()===0 && $stillMissingAfterRoot===null,
        'explicit_upsert_inserts'=>$created->getAffectedItemsCount()===1 && $afterCreate['_id']==='CARD-2'
            && $afterCreate['name']==='Кафе «Тёплый свет» — è' && $afterCreate['price']===1500,
        'explicit_upsert_replaces'=>$afterUpsert['price']===1600 && $afterUpsert['name']==='Кафе «Тёплый свет» — è' && $collection->count()===2];
    $observations = ['after_replaceone'=>$afterShortcut,'replaceone_unicode_exact'=>$shortcutUnicodeExact,
        'root_replacement_input'=>$rootReplacement,'after_replace'=>$afterReplace,'missing_before_upsert'=>$stillMissing,
        'missing_after_root'=>$stillMissingAfterRoot,
        'created'=>$afterCreate,'after_upsert'=>$afterUpsert,
        'upsert_existing_affected'=>$upsertExisting->getAffectedItemsCount()];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x139Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x139Report('mysql139-x-replacement-policy', 'Выбрать полную замену или явное создание', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Замена существующей: затронуто — 1
Unicode после replaceOne совпал — нет
Полная замена через корень: затронуто — 1
Поле obsolete отсутствует — да
Сохранённое название — Кафе «Тёплый свет» — è
Замена отсутствующей: затронуто — 0
После replaceOne CARD-2 отсутствует — да
Полная замена отсутствующей: затронуто — 0
После addOrReplaceOne CARD-2 существует — да
Цена CARD-2 после повторной замены — 1600
Всего карточек — 2

Программа получает настоящую квитанцию MySQL, читает сохранённые поля и в finally очищает только свои коллекции. result.html показывает строки, result.json сохраняет наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · JSON сохранённого запуска.

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

Счётчик1 от replaceOne не доказывает точность текста: исходное искажённое значение сохранено в observations и не скрывается исправлением. В mysql_xdevapi8.0.30 внешнего JSON_UNESCAPED_UNICODE для replaceOne недостаточно из-за повторного внутреннего кодирования; рабочий путь этой программы — modify/set корня готовой строкой. Диагностический вызов replaceOne и последующая исправленная запись — два отдельных действия, без обещания атомарности. Полная замена убирает пропущенные свойства; для формы одного поля используйте set поля. Уникальные индексы остаются обязательными и при addOrReplaceOne. Создание неизвестного ID разрешается только явной политикой upsert. Чтение→изменение массива→полная замена может затереть параллельную правку.

Автор на PHP7.3.19 и расширении8.0.22 сообщил об изменении è при замене прочитанного документа. Ответ в обсуждении не даёт подтверждённого исправления. Отдельная реальная диагностика нашего стенда PHP8.3.35/mysql_xdevapi8.0.30 воспроизвела искажение replaceOne для JSON-строки и PHP-массива; вывод ограничен этими проверенными версиями. Самостоятельный пример сохраняет исходный readback и показывает проверенную полную замену через set корня готовой JSON-строкой. Реальная ситуация: После replaceOne изменилось представление Unicode.

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

Сохраните новое название существующей CARD-1, но оставьте цену1500. Затем объясните, почему replacement с одним name не подходит.

Как проверить решение

Для изменения одного поля используйте modify('_id = :id')->bind(['id'=>'CARD-1'])->set('name','Новая лампа')->execute(). При replaceOne JSON с одним name удалит price; при настоящем полном replacement нужно сознательно передать все сохраняемые поля.

Убрать одну карточку и ограниченную очередь старых записей

Оператор подтверждает удаление OLD-1, затем очищает не больше двух архивных записей за проход. В ведомости должны остаться действующая LIVE-1 и ещё не обработанная OLD-4. Порядок важен: одинаковый priority разрешается уникальным _id.

Путь данных этой программы
  1. Предпросмотр OLD-1 → removeOne(OLD-1) → квитанция1
  2. archived: OLD-2(priority2), OLD-3(priority2), OLD-4(priority3)
  3. sort(priority ASC, _id ASC) + limit2 → OLD-2, OLD-3
  4. Повторное чтение → LIVE-1, OLD-4; предпросмотр сам ничего не удаляет

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

removeOne принимает один _id и сразу возвращает Result. remove строит CollectionRemove: bind заполняет условие, sort задаёт порядок, limit ограничивает число документов, execute выполняет удаление. ASC означает возрастание. array_column извлекает _id из прочитанных строк, implode делает список для человека. limit2 — максимум документов, а не номер страницы и не число полей.

$one = $collection->removeOne('OLD-1');
$batch = $collection->remove('status = :state')->bind(['state'=>'archived'])
    ->sort('priority ASC','_id ASC')->limit(2)->execute();

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

Ожидаемая ведомость

НаблюдениеРезультат
Карточка перед удалениемOLD-1
Удалено по одному ключу1
Отсутствующий ключ: удалено0
Предпросмотр ограниченной очередиOLD-2, OLD-3
Удалено из очереди2
Оставшиеся ключиLIVE-1, OLD-4
Самостоятельная программа, исходные данные и архив
Убрать одну карточку и ограниченную очередь старых записейСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x139Config();
$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 = x139Name('remove');
$handle = $schema->getCollection($name);
$existed = $handle->existsInDatabase();
if ($existed) {throw new RuntimeException('Случайное имя уже занято.');}
$collection = $schema->createCollection($name);
$owned[] = $name;

    $collection->add(['_id'=>'OLD-1','status'=>'archived','priority'=>1],
        ['_id'=>'OLD-2','status'=>'archived','priority'=>2],
        ['_id'=>'OLD-3','status'=>'archived','priority'=>2],
        ['_id'=>'OLD-4','status'=>'archived','priority'=>3],
        ['_id'=>'LIVE-1','status'=>'active','priority'=>0])->execute();
    $previewOne = $collection->getOne('OLD-1');
    $one = $collection->removeOne('OLD-1');
    $missing = $collection->removeOne('NO-SUCH');
    $previewBatch = $collection->find('status = :state')->bind(['state'=>'archived'])
        ->sort('priority ASC','_id ASC')->limit(2)->execute()->fetchAll();
    $batch = $collection->remove('status = :state')->bind(['state'=>'archived'])
        ->sort('priority ASC','_id ASC')->limit(2)->execute();
    $remaining = $collection->find()->sort('_id ASC')->execute()->fetchAll();
    $previewIds = array_column($previewBatch,'_id');
    $remainingIds = array_column($remaining,'_id');
    $rows = [['Карточка перед удалением',$previewOne['_id']],['Удалено по одному ключу',$one->getAffectedItemsCount()],
        ['Отсутствующий ключ: удалено',$missing->getAffectedItemsCount()],
        ['Предпросмотр ограниченной очереди',implode(', ',$previewIds)],
        ['Удалено из очереди',$batch->getAffectedItemsCount()],['Оставшиеся ключи',implode(', ',$remainingIds)]];
    $checks = ['single_removed'=>$one->getAffectedItemsCount()===1 && ($collection->getOne('OLD-1') ?? null)===null,
        'missing_is_zero'=>$missing->getAffectedItemsCount()===0,
        'preview_order'=>$previewIds===['OLD-2','OLD-3'],
        'bounded_count'=>$batch->getAffectedItemsCount()===2,
        'survivors'=>$remainingIds===['LIVE-1','OLD-4']];
    $observations = ['single_preview'=>$previewOne,'batch_preview'=>$previewBatch,'remaining'=>$remaining];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x139Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x139Report('mysql139-x-bounded-removal', 'Убрать одну карточку и ограниченную очередь старых записей', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Карточка перед удалением — OLD-1
Удалено по одному ключу — 1
Отсутствующий ключ: удалено — 0
Предпросмотр ограниченной очереди — OLD-2, OLD-3
Удалено из очереди — 2
Оставшиеся ключи — LIVE-1, OLD-4

Программа получает настоящую квитанцию MySQL, читает сохранённые поля и в finally очищает только свои коллекции. result.html показывает строки, result.json сохраняет наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · JSON сохранённого запуска.

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

Предпросмотр и удаление — два запроса без общей атомарности. При параллельных изменениях состав может отличаться; для подтверждения конкретных ключей сохраните выбранные IDs и задайте отдельную политику проверки. В этом упражнении коллекция изолирована и других писателей нет. remove("true") выбрало бы все записи. Удаление не является архивом или отменой; храните нужную историю отдельно.

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

За следующий проход удалите только последнюю архивную OLD-4 и докажите, что LIVE-1 сохранилась.

Как проверить решение

Повторите то же условие archived с limit(2). Квитанция должна дать1, поскольку осталась одна подходящая запись; итоговый список содержит только LIVE-1. Не заменяйте условие на true ради короткого примера.

Сохранить карточку вместе с записью журнала

Новый товар должен появиться вместе с событием «создан». Отдельно сохранённый товар без журнала оставляет работу незавершённой. В одном сеансе проверим успешную пару и второй случай: запись товара прошла, но вставка события получила уже занятый _id.

Путь данных этой программы
  1. До начала: две собственные коллекции, EV-BUSY принадлежит PREEXISTING
  2. startTransaction → P-OK + EV-OK → commit → обе записи читаются
  3. startTransaction → P-FAIL записан → EV-BUSY конфликтует → catch
  4. rollback → P-FAIL отсутствует, событий для него0, старое EV-BUSY не изменено

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

Этот короткий фрагмент показывает границы; полный пример ниже дополнительно обрабатывает отказ самого rollback. startTransaction начинает транзакцию Session, commit подтверждает её, rollback отменяет незавершённые изменения. Обе коллекции получены из Schema того же Session. try содержит работу, catch получает исключение, throw передаёт неожиданный отказ вызывающему коду. Флаг active изменяется только после завершившегося вызова. instanceof проверяет вид исключения. На нашем стенде расширение оборачивает документный конфликт5116 внешним исключением10000. Цикл for начинает с пойманного Throwable и через getPrevious проходит к исходной причине до null; проверяем код5116 во всей цепочке, а не любое сообщение внешней ошибки. Классы и коды сохраняются в observations. Для отсутствующего P-FAIL выражение getOne(...) ?? null нормализует дефект возврата выбранного расширения в ожидаемый null.

$session->startTransaction();
try {
    $products->add($product)->execute();
    $events->add($event)->execute();
    $session->commit();
} catch (Throwable $error) {
    $session->rollback();
    throw $error;
}

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

Ожидаемая ведомость

НаблюдениеРезультат
После commit есть P-OKда
Журнал ссылается на P-OKP-OK
Второй insert отклонёнда
Явный rollback завершёнда
После rollback есть P-FAILнет
Событий для P-FAIL0
Ранее существовавшее событиеPREEXISTING
Самостоятельная программа, исходные данные и архив
Сохранить карточку вместе с записью журналаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x139Config();
$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']];

    $productsName = x139Name('tx_products');
    if ($schema->getCollection($productsName)->existsInDatabase()) {throw new RuntimeException('Имя занято');}
    $products = $schema->createCollection($productsName); $owned[] = $productsName;
    $eventsName = x139Name('tx_events');
    if ($schema->getCollection($eventsName)->existsInDatabase()) {throw new RuntimeException('Имя занято');}
    $events = $schema->createCollection($eventsName); $owned[] = $eventsName;
    // Коллекции и намеренный конфликт созданы до startTransaction: DDL не включаем в транзакцию.
    $events->add(['_id'=>'EV-BUSY','product'=>'PREEXISTING','kind'=>'fixture'])->execute();
    $active = false;
    try {
        $session->startTransaction(); $active = true;
        $products->add(['_id'=>'P-OK','name'=>'Лампа','price'=>1200])->execute();
        $events->add(['_id'=>'EV-OK','product'=>'P-OK','kind'=>'created'])->execute();
        $session->commit(); $active = false;
    } catch (Throwable $error) {
        if ($active) {
            try {$session->rollback(); $active = false;}
            catch (Throwable $rollbackError) {throw new RuntimeException('Откат не подтверждён',0,$error);}
        }
        throw $error;
    }
    $committedProduct = $products->getOne('P-OK');
    $committedEvent = $events->getOne('EV-OK');
    $duplicateCaught = false; $rollbackCompleted = false; $failureCode = null; $failureChain = [];
    try {
        $session->startTransaction(); $active = true;
        $products->add(['_id'=>'P-FAIL','name'=>'Несохранённая лампа','price'=>900])->execute();
        // Второй insert получает занятый _id. Ошибка statement не заменяет явный rollback.
        $events->add(['_id'=>'EV-BUSY','product'=>'P-FAIL','kind'=>'created'])->execute();
        $session->commit(); $active = false;
    } catch (Throwable $error) {
        $failureCode = $error->getCode();
        for ($cause = $error; $cause !== null; $cause = $cause->getPrevious()) {
            $failureChain[] = ['class'=>get_class($cause),'code'=>(int)$cause->getCode()];
            if ($cause instanceof mysql_xdevapi\Exception && (int)$cause->getCode() === 5116) {
                $duplicateCaught = true;
            }
        }
        if ($active) {
            try {$session->rollback(); $active = false; $rollbackCompleted = true;}
            catch (Throwable $rollbackError) {throw new RuntimeException('Откат не подтверждён',0,$error);}
        }
        if (!$duplicateCaught) {throw $error;}
    }
    $failedProduct = $products->getOne('P-FAIL') ?? null;
    $failedEvents = $events->find('product = :id')->bind(['id'=>'P-FAIL'])->execute()->fetchAll();
    $busyEvent = $events->getOne('EV-BUSY');
    $rows = [['После commit есть P-OK',$committedProduct!==null],
        ['Журнал ссылается на P-OK',$committedEvent['product']],['Второй insert отклонён',$duplicateCaught],
        ['Явный rollback завершён',$rollbackCompleted],['После rollback есть P-FAIL',$failedProduct!==null],
        ['Событий для P-FAIL',count($failedEvents)],['Ранее существовавшее событие',$busyEvent['product']]];
    $checks = ['committed_pair'=>$committedProduct!==null && $committedEvent['product']==='P-OK',
        'duplicate_confirmed'=>$duplicateCaught,'rollback_completed'=>$rollbackCompleted,
        'new_pair_absent'=>$failedProduct===null && $failedEvents===[],
        'existing_event_preserved'=>$busyEvent['product']==='PREEXISTING'];
    $observations = ['committed_product'=>$committedProduct,'committed_event'=>$committedEvent,
        'failure_code'=>$failureCode,'failure_chain'=>$failureChain,'after_rollback_product'=>$failedProduct,'after_rollback_events'=>$failedEvents,
        'preexisting_event'=>$busyEvent];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x139Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x139Report('mysql139-x-transaction-receipt', 'Сохранить карточку вместе с записью журнала', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
После commit есть P-OK — да
Журнал ссылается на P-OK — P-OK
Второй insert отклонён — да
Явный rollback завершён — да
После rollback есть P-FAIL — нет
Событий для P-FAIL — 0
Ранее существовавшее событие — PREEXISTING

Программа получает настоящую квитанцию MySQL, читает сохранённые поля и в finally очищает только свои коллекции. result.html показывает строки, result.json сохраняет наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · JSON сохранённого запуска.

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

Коллекции создаются до начала, удаляются после завершения: DDL может неявно подтвердить транзакцию. Ошибка duplicate key обычно отменяет только неудавшуюся инструкцию; без rollback первая вставка могла бы сохраниться при commit. Потеря связи во время commit/rollback оставляет исход неподтверждённым — программа не печатает успешную квитанцию. Внешняя отправка письма или HTTP-запрос не откатывается этой транзакцией. Вложенный startTransaction не создаёт вложенную транзакцию; для промежуточных точек есть savepoints.

В общем вопросе о MySQL после успешного insert возникла ошибка следующего действия, а commit сохранил первую запись. Это не специальный случай PHP X DevAPI; различие statement rollback и transaction rollback объясняется здесь по официальной документации InnoDB. Реальная ситуация: Ошибка одной инструкции не отменила всю транзакцию.

Общая модель «вместе или отменить» разобрана в транзакциях. Здесь используем Session X DevAPI и документные операции. Этот сценарий не измеряет конкуренцию блокировок и не переносит SQL-код1062 на документный конфликт5116.

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

Выдайте второму событию свободный _id EV-SECOND. Как поменяются обе стороны второй попытки?

Как проверить решение

Теперь оба insert могут завершиться, после commit будут P-FAIL и событие с product=P-FAIL. Для отдельного успешного упражнения уберите ожидание5116 и замените проверки на наличие обеих записей; не оставляйте сообщение «откат» при успешном commit.

Из списка каталога к сохранённой цене

Сотрудник выбирает товар из списка, видит текущую цену, отправляет новую и после перехода снова читает карточку. Ошибка ввода оставляет данные и введённый текст на месте; неизвестный адрес не создаёт товар. CLI-пример проверяет предметные действия, а файлы web/ в том же ZIP дают настоящую форму.

Путь данных этой программы
  1. GET index.php → две карточки → ссылка ?id=LED-1
  2. GET ?id=LED-1 → цена1200 и форма POST
  3. POST price=1390 → проверка поля/CSRF → modify/set/execute →303
  4. Следующий GET → чтение цены1390; POST -7 →422 без записи; неизвестный id →404

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

Здесь validatedInput означает уже проверенную строку формы: один int-cast сам по себе не проверяет ввод. HTML form с method=post отправляет поля в $_POST; GET-адреса читаются из $_GET. PHP проверяет, что значение строковое, состоит из цифр и лежит в диапазоне1..100000 рублей. Перед выводом htmlspecialchars заменяет специальные HTML-символы. Скрытый CSRF-токен связан с PHP-сессией посетителя; это отдельная сущность от Session соединения MySQL. После успешного POST ответ303 уводит на GET, поэтому обновление страницы не повторяет запись.

$price = (int)$validatedInput;
$receipt = $collection->modify('_id = :id')->bind(['id'=>$id])
    ->set('price', $price)->execute();
$saved = $collection->getOne($id) ?? null;

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

Ожидаемая ведомость

НаблюдениеРезультат
Карточек в списке2
Цена до сохранения1200
Цена после сохранения1390
Остаток сохранён4
Отрицательная цена отклоненада
После отказа цена прежняя1390
Неизвестная карточка отсутствуетда
HTML в названии экранированда
Самостоятельная программа, исходные данные и архив
Из списка каталога к сохранённой ценеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x139Config();
$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 = x139Name('catalog_cli');
$handle = $schema->getCollection($name);
$existed = $handle->existsInDatabase();
if ($existed) {throw new RuntimeException('Случайное имя уже занято.');}
$collection = $schema->createCollection($name);
$owned[] = $name;

    $collection->add(['_id'=>'LED-1','name'=>'Лампа «Тёплый свет»','price'=>1200,'category'=>'workshop','stock'=>4],
        ['_id'=>'KIT-2','name'=>'Набор <стойка> & крепёж','price'=>850,'category'=>'workshop','stock'=>2])->execute();
    $list = $collection->find('category = :category')->bind(['category'=>'workshop'])
        ->sort('_id ASC')->execute()->fetchAll();
    $before = $collection->getOne('LED-1');
    $inputPrice = '1390';
    $validPrice = preg_match('/\\A[0-9]{1,6}\\z/D', $inputPrice) === 1
        && (int)$inputPrice >= 1 && (int)$inputPrice <= 100000;
    if (!$validPrice) {throw new RuntimeException('Недопустимая учебная цена');}
    $receipt = $collection->modify('_id = :id')->bind(['id'=>'LED-1'])
        ->set('price', (int)$inputPrice)->execute();
    $after = $collection->getOne('LED-1');
    $invalidInput = '-7';
    $invalidRejected = preg_match('/\\A[0-9]{1,6}\\z/D', $invalidInput) !== 1;
    // Отказ валидации: здесь нет вызова modify/execute.
    $unchangedAfterInvalid = $collection->getOne('LED-1');
    $missing = $collection->getOne('NO-SUCH') ?? null;
    $escapedName = htmlspecialchars($list[0]['name'], ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8');
    $rows = [['Карточек в списке',count($list)],['Цена до сохранения',$before['price']],
        ['Цена после сохранения',$after['price']],['Остаток сохранён',$after['stock']],
        ['Отрицательная цена отклонена',$invalidRejected],['После отказа цена прежняя',$unchangedAfterInvalid['price']],
        ['Неизвестная карточка отсутствует',$missing===null],['HTML в названии экранирован',str_contains($escapedName,'&lt;стойка&gt;')]];
    $checks = ['two_cards'=>count($list)===2,'saved_price'=>$receipt->getAffectedItemsCount()===1 && $after['price']===1390,
        'siblings_preserved'=>$after['stock']===4 && $after['name']===$before['name'],
        'invalid_did_not_write'=>$invalidRejected && $unchangedAfterInvalid===$after,
        'missing_not_created'=>$missing===null,'html_escaped'=>str_contains($escapedName,'&lt;стойка&gt;')];
    $observations = ['list'=>$list,'before'=>$before,'after'=>$after,'invalid_input'=>$invalidInput,
        'after_invalid'=>$unchangedAfterInvalid,'missing'=>$missing,'escaped_name'=>$escapedName];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x139Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x139Report('mysql139-x-catalog-screen', 'Из списка каталога к сохранённой цене', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Карточек в списке — 2
Цена до сохранения — 1200
Цена после сохранения — 1390
Остаток сохранён — 4
Отрицательная цена отклонена — да
После отказа цена прежняя — 1390
Неизвестная карточка отсутствует — да
HTML в названии экранирован — да

Программа получает настоящую квитанцию MySQL, читает сохранённые поля и в finally очищает только свои коллекции. result.html показывает строки, result.json сохраняет наблюдения, metrics.json — проверки.

Таблица сохранённого запуска · JSON сохранённого запуска.

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

Веб-лаборатория предназначена для локального запуска на127.0.0.1 с отдельной учебной схемой. Для рабочего сервиса нужны вход сотрудника и проверка его прав на товар; одного CSRF недостаточно. Поле price выбирается самим кодом: имя изменяемого поля не приходит из запроса. При параллельных писателях возможна последняя победившая запись; здесь не обещается контроль версий или блокировка. CLI и HTTP — разные проверки; успешный CLI не доказывает отправку формы.

Пошаговый HTTP-запуск расположен в разделе формы. Веб-коллекция не удаляется после каждого запроса; cleanup вызывается отдельно.

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

Отправьте -7, затем верните1390. Проверьте, что ошибка показывает введённое -7, а сохранённая цена остаётся1390. Откройте неизвестный id.

Как проверить решение

Проверка строки должна завершиться до modify. Ответ422 возвращает форму с прежним вводом и текстом у поля; новое GET-чтение подтверждает неизменность документа. Для неизвестного корректно оформленного id нужен404 и ссылка к каталогу, а не upsert.

Настоящая HTML-форма: список, карточка, POST и новое чтение

В ZIP сценария catalog-screen находятся example.php с CLI-проверкой и папка web/ с работающим экраном. Одна проверка не подменяет другую. Веб-форма читает только нужные поля: _id, name, price, stock; список ограничен двадцатью документами.

Жизнь учебных данных между запросами
  1. CLI setup → собственная коллекция с LED-1 и KIT-2 → fixture.json за пределами web/
  2. GET списка → GET карточки → POST проверенной цены с токеном той же PHP-сессии
  3. 303 → новый GET читает сохранённую цену; закрытие Session оставляет коллекцию
  4. После проверки: остановка HTTP сервера → CLI cleanup → проверка owned имени → удаление только своей коллекции

Подготовьте переменные окружения по README, распакуйте ZIP в отдельную папку и выполните команды PowerShell из её корня. $php83 и $phpIni хранят полные пути к тому же подготовленному PHP8.3 и его конфигурации, что использовались для CLI: замените C:\PATH\TO\PHP83 своими путями. Оператор & запускает выбранный exe, -c указывает ini. Системная команда php может относиться к другой установке без mysql_xdevapi. Встроенный сервер слушает только127.0.0.1; если порт занят, выберите свободный и измените адрес в браузере. Откройте адрес только после успешного setup.

$php83 = 'C:\PATH\TO\PHP83\php.exe'
$phpIni = 'C:\PATH\TO\PHP83\php.ini'
& $php83 -c $phpIni .\web\setup.php
& $php83 -c $phpIni -S 127.0.0.1:8779 -t web

Откройте http://127.0.0.1:8779/index.php. Форма категории отправляется через GET и только читает данные; форма цены отправляется через POST. PHP-массивы $_GET/$_POST содержат строки запроса, но злоумышленник может отправить вместо строки массив: программа проверяет форму значения прежде, чем работать с ним. Структура запроса не даёт посетителю права менять произвольное поле.

ДействиеОжидаемый результат
Открыть каталог / выбрать «Мастерская»HTTP200; две карточки, цена LED-1 —1200. Угловые скобки в названии KIT-2 видны текстом.
Открыть LED-1, сохранить1390POST303; следующий GET200 читает1390 и stock4. Зелёное сообщение связано с серверной flash-записью этой PHP-сессии.
Отправить -7HTTP422; поле сохраняет введённое -7, ошибка объясняет диапазон, данные остаются1390.
Открыть ?id=NO-SUCHHTTP404 с понятным сообщением и возвратом к каталогу; новая карточка не создаётся.
Отправить POST с неверным CSRFHTTP403; сохранения нет, предлагается заново открыть форму.
Запустить без корректного fixtureHTTP503; нет автоматического создания или выбора чужой коллекции.

Форма имеет явную подпись цены и единицы, подсказку диапазона, связь ошибки с полем и кнопки высотой не меньше44px. CSS Bootstrap5 находится в архиве, внешняя сеть для оформления не нужна. На узком экране карточки и элементы фильтра становятся в одну колонку. Проверка реальных HTTP-ответов и экранов выполняется отдельно от синтаксического контроля.

При успехе redirect303 завершает POST; новый GET делает чтение MySQL. Экранирование выполняется при выводе, а не перед сохранением: строка с <стойка> хранится как исходное название, но не исполняется браузером как HTML. Bind защищает границу выражения запроса, htmlspecialchars — границу HTML, CSRF-токен — связь отправки с открытой формой. Это разные обязанности; для рабочего сервиса дополнительно нужны права сотрудника.

Остановив сервер, очистите лабораторию командой ниже. fixture.json хранит только имя своей коллекции и схему, без URI. Если он повреждён, cleanup отказывает; нельзя заменять это произвольным drop всех коллекций. Сначала восстановите проверенную запись владения своей лаборатории.

& $php83 -c $phpIni .\web\cleanup.php

Механика форм и перенаправления подробно объяснена в HTML-формах, экранирование — в границах доверия, сессии посетителя — в сеансах и входе. Для изменения одного поля эта форма использует set; полный replaceOne мог бы убрать не показанные сотруднику свойства.

Найти действие по задаче

Две карточки классов сохраняют полный перечень членов официальных страниц. Девятнадцать выбранных методов имеют отдельный контракт и два контекстных применения. Члены, ведущие на внешний Manual, остаются отдельными будущими ступенями; один список класса не объявляет всю ветвь X DevAPI законченной.

APIПолезный результат
CollectionModifyПодготовить несколько изменений свойств до отправки одним execute.
CollectionRemoveПодготовить условие, порядок и предел удаления документов.
Collection::modifyВыбрать документы и подготовить изменение отдельных свойств.
CollectionModify::bindСвязать значения с именами в условии изменения.
CollectionModify::setЗадать или обновить значение по пути документа.
CollectionModify::unsetУбрать перечисленные свойства из выбранных документов.
CollectionModify::arrayAppendДобавить один элемент в конец массива документа.
CollectionModify::arrayInsertВставить элемент на явную позицию в массиве документа.
CollectionModify::executeОтправить подготовленное изменение и получить квитанцию.
Collection::replaceOneЗаменить существующий документ целиком по _id.
Collection::addOrReplaceOneЯвно разрешить создание при отсутствии либо полную замену по _id.
Collection::removeOneУдалить один документ по его известному _id.
Collection::removeПодготовить удаление документов, соответствующих условию.
CollectionRemove::bindПередать значения условию удаления отдельно от выражения.
CollectionRemove::sortОпределить, какие документы попадут в ограниченное удаление первыми.
CollectionRemove::limitЗадать максимум удаляемых документов за выполнение.
CollectionRemove::executeВыполнить настроенное удаление и получить ответ сервера.
Session::startTransactionНачать общую транзакцию действий одного сеанса.
Session::commitПодтвердить изменения текущей транзакции.
Session::rollbackОтменить незавершённые изменения транзакции этого сеанса.
Session::closeЗакрыть соединение, когда работа этого сеанса закончена.

Выбрать следующий шаг для растущего каталога

Выбор действия начинается с требований: сохранить соседние поля, заменить весь документ, явно разрешить создание, ограничить удаление или связать несколько записей транзакцией. После отправки проверяются и квитанция, и нужные свойства. Маленький limit ограничивает результат запроса, но не гарантирует малый объём поиска: стоимость зависит от условия, индексов и данных.

В растущем каталоге следующий шаг — продумать уникальные ключи, индексы, проверку формы документа, конкурентное редактирование и доступ сотрудника. HTTP-форма здесь локальная; её ошибки и границы сделаны видимыми, но она не объявлена готовой системой управления складом. Вернуться к чтению и страницам каталога или сравнить с JSON в реляционном SQL.

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

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

mysql_xdevapi\CollectionModifyПодготовить несколько изменений свойств до отправки одним execute.

PECL mysql_xdevapi8.0.11+; учебный runtime8.0.30 · mysql_xdevapi

class mysql_xdevapi\CollectionModify implements Executable, CrudOperationBindable, CrudOperationLimitable, CrudOperationSkippable, CrudOperationSortable

Подготовить несколько изменений свойств до отправки одним execute. Это объект ещё не исполненной операции, полученный у Collection. Он не является ни JSON-документом, ни массивом результатов.

Роль CollectionModify
  1. Collection выбирает условие → CollectionModify
  2. Методы настройки меняют план этой операции
  3. execute отправляет запрос → Result
  4. getAffectedItemsCount и отдельное чтение проверяют результат

Класс доступен с PECL mysql_xdevapi8.0.11. В synopsis нет публичных свойств. Интерфейсы Executable, CrudOperationBindable, CrudOperationLimitable, CrudOperationSkippable, CrudOperationSortable описывают доступные виды действий. Закрытый конструктор не предназначен для new в прикладном коде; объект возвращает Collection::modify.

Полный список членов официальной страницы

ЧленНазначение
arrayAppend
arrayAppend(string $collection_field, string $expression_or_literal): CollectionModify
Добавить элемент в конец массива.
arrayInsert
arrayInsert(string $collection_field, string $expression_or_literal): CollectionModify
Вставить элемент на заданную позицию.
bind
bind(array $placeholder_values): CollectionModify
Связать значения условия.
__construct
private __construct()
Получать объект через Collection::modify, не создавать вручную.
execute
execute(): Result
Отправить подготовленное изменение.
limit
limit(int $rows): CollectionModify
Ограничить число изменяемых документов.
patch
patch(string $document): CollectionModify
Применить документ исправления; это отдельная операция API.
replace
replace(string $collection_field, string $expression_or_literal): CollectionModify
Заменить поле документа.
set
set(string $collection_field, string $expression_or_literal): CollectionModify
Задать/обновить атрибут.
skip
skip(int $position): CollectionModify
Позиция пропуска; доступность конкретной формы нужно сверять с runtime.
sort
sort(string $sort_expr): CollectionModify
Порядок обработки документов.
unset
unset(array $fields): CollectionModify
Убрать перечисленные атрибуты.

Краткие сигнатуры здесь сохранены по Manual. У set/arrayAppend/arrayInsert тип второго аргумента указан string, но в PECL8.0.30 это zval; у sort краткая запись не показывает все принимаемые строки. Подробные замечания находятся в карточках выбранных методов. Перечень других членов сохраняет обзор класса и не объявляет их отдельными завершёнными уроками.

Применение 1

$change = $collection->modify('_id = :id')->bind(['id'=>'LED-1']);
$change->set('price',1350)->unset(['obsolete']);
$receipt = $change->execute();

Одна выбранная карточка получает цену1350 и теряет obsolete; stock и соседние поля supplier остаются. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayAppend('options','Шайба')->execute();

Другой вид изменения: существующий список комплекта получает элемент в конце. Возвращённая квитанция не содержит сам список; его читают отдельно. Опора: самостоятельная программа и исходные данные.

Серверный отказ обрабатывается исключением; успешный запрос без совпадений даёт нулевой счётчик. Лаборатория работает только со своей случайно названной коллекцией и очищает её после проверки. Чтобы продвигаться дальше, сравните CollectionRemove и квитанцию Result.

mysql_xdevapi\CollectionRemoveПодготовить условие, порядок и предел удаления документов.

PECL mysql_xdevapi8.0.11+; учебный runtime8.0.30 · mysql_xdevapi

class mysql_xdevapi\CollectionRemove implements Executable, CrudOperationBindable, CrudOperationLimitable, CrudOperationSortable

Подготовить условие, порядок и предел удаления документов. Это объект ещё не исполненной операции, полученный у Collection. Он не является ни JSON-документом, ни массивом результатов.

Роль CollectionRemove
  1. Collection выбирает условие → CollectionRemove
  2. Методы настройки меняют план этой операции
  3. execute отправляет запрос → Result
  4. getAffectedItemsCount и отдельное чтение проверяют результат

Класс доступен с PECL mysql_xdevapi8.0.11. В synopsis нет публичных свойств. Интерфейсы Executable, CrudOperationBindable, CrudOperationLimitable, CrudOperationSortable описывают доступные виды действий. Закрытый конструктор не предназначен для new в прикладном коде; объект возвращает Collection::remove.

Полный список членов официальной страницы

ЧленНазначение
bind
bind(array $placeholder_values): CollectionRemove
Связать значения условия.
__construct
private __construct()
Объект создаёт Collection::remove.
execute
execute(): Result
Отправить удаление.
limit
limit(int $rows): CollectionRemove
Максимум удаляемых документов.
sort
sort(string $sort_expr): CollectionRemove
Порядок для ограниченной очереди.

У sort краткая сигнатура Manual показывает одну строку, но PECL8.0.30 принимает несколько критериев. Предел означает максимум документов. Конструктор закрыт, поэтому готовый объект получают через remove.

Применение 1

$removal = $collection->remove('status = :state')->bind(['state'=>'archived']);
$receipt = $removal->sort('priority ASC','_id ASC')->limit(2)->execute();

Из основной очереди удаляются OLD-2 и OLD-3; LIVE-1 и OLD-4 остаются. Опора: самостоятельная программа и исходные данные.

Применение 2

$receipt = $collection->remove('_id = :id')->bind(['id'=>'NO-SUCH'])->execute();

Отсутствующий адрес даёт0 в Result. Объект удаления сам по себе не гарантирует хотя бы одно совпадение. Опора: самостоятельная программа и исходные данные.

Серверный отказ обрабатывается исключением; успешный запрос без совпадений даёт нулевой счётчик. Лаборатория работает только со своей случайно названной коллекцией и очищает её после проверки. Чтобы продвигаться дальше, сравните CollectionModify и квитанцию Result.

mysql_xdevapi\Collection::modifyВыбрать документы и подготовить изменение отдельных свойств.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Collection::modify(string $search_condition): mysql_xdevapi\CollectionModify

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

От выбранного действия к наблюдаемому результату
  1. До execute чтение LED-1 по-прежнему даёт1200; после исполнения —1350.
  2. Collection::modify получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Collection::modify(string $search_condition): mysql_xdevapi\CollectionModify
ПараметрЧто передать
search_conditionОбязательное выражение отбора документов. true совпадает со всеми; допустимы операторы и функции, например age MOD 2 = 0, CAST(_id AS SIGNED) >= 10 и _id IN ["2","5"]. Переменные значения удобнее связать placeholders.

Что вернётся

CollectionModify для дальнейших операций. Только его execute возвращает Result исполненной записи.

Правила и границы контракта

Можно добавить несколько изменений и связать параметры. На странице Return Values слово Modify употреблено сокращённо и также описан результат исполненной цепочки; сам modify не выполняет её. Условие — X DevAPI expression, а не полная SQL-команда UPDATE.

Применение 1

$operation = $collection->modify('_id = :id')->bind(['id'=>'LED-1']);
$operation->set('price',1350);

До execute чтение LED-1 по-прежнему даёт1200; после исполнения —1350. Опора: самостоятельная программа и исходные данные.

Применение 2

$receipt = $collection->modify('_id = :id')->bind(['id'=>$id])
    ->set('price',$price)->execute();

Форма адресует проверенный ключ и меняет только цену, сохраняя name и stock. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

В условии true легко выбрать всю коллекцию. Объект операции и отсутствие ошибки при его создании ещё не означают, что цена сохранена.

Связанные действия: CollectionModify::set, Collection::replaceOne, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionModify::bindСвязать значения с именами в условии изменения.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionModify::bind(array $placeholder_values): mysql_xdevapi\CollectionModify

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

От выбранного действия к наблюдаемому результату
  1. Точный ключ выбирает лампу, а неизвестный NO-SUCH даёт0 затронутых документов.
  2. CollectionModify::bind получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionModify::bind(array $placeholder_values): mysql_xdevapi\CollectionModify
ПараметрЧто передать
placeholder_valuesМассив имя=>значение. В условии обязательно :NAME, в ключе массива NAME. Можно связать несколько placeholders одним массивом.

Что вернётся

Тот же тип CollectionModify: можно добавить изменение или вызвать execute.

Правила и границы контракта

Связанные значения относятся к условию отбора. Значение для set передаётся аргументом set; имена путей полей выбираются кодом и не превращаются в placeholders.

Применение 1

$operation = $collection->modify('_id = :id')->bind(['id'=>'LED-1']);

Точный ключ выбирает лампу, а неизвестный NO-SUCH даёт0 затронутых документов. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayAppend('options','Шайба')->execute();

То же связывание используется для другой карточки и другой операции: в конец списка добавляется шайба. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Проверьте соответствие всех имён. PHP-форма bind принимает mapping, а примеры bind(name,value) из других Connector не являются PHP-контрактом. Связывание не проверяет права пользователя.

Связанные действия: Collection::modify, CollectionModify::set, Collection::replaceOne, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionModify::setЗадать или обновить значение по пути документа.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionModify::set(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify

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

От выбранного действия к наблюдаемому результату
  1. После отправки price1350, cityРжев; codeS-7 и stock4 остаются прежними.
  2. CollectionModify::set получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionModify::set(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify
ПараметрЧто передать
collection_fieldПуть свойства, например price либо supplier.city.
expression_or_literalНовое значение. Краткая сигнатура Manual показывает string; PECL8.0.30 принимает zval, поэтому число и PHP-массив не обязаны предварительно превращаться в строку.

Что вернётся

CollectionModify; запись выполняет последующий execute.

Правила и границы контракта

set создаёт/обновляет атрибут. Вложенный путь supplier.city сохраняет соседнее supplier.code; установка всего supplier новым объектом имеет другой смысл. В mysql_xdevapi8.0.30 обычный строковый литерал передаётся как значение, но строка, начинающаяся с { или [, распознаётся как документ/массив, а строка с начальным : — как связывание выражения. Для явного выражения есть Expression. В полной замене set корня $ получает именно JSON-строку с нужным _id; это разобрано отдельно в сценарии замены.

Применение 1

$operation->set('price',1350)->set('supplier.city','Ржев');

После отправки price1350, cityРжев; codeS-7 и stock4 остаются прежними. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->modify('_id = :id')->bind(['id'=>'LED-1'])
    ->set('price',(int)$validatedInput)->execute();

После проверки формы1390 становится JSON-числом. Приведение типа само по себе не заменяет проверку ввода. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

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

Связанные действия: Collection::modify, Collection::replaceOne, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionModify::unsetУбрать перечисленные свойства из выбранных документов.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionModify::unset(array $fields): mysql_xdevapi\CollectionModify

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

От выбранного действия к наблюдаемому результату
  1. В самостоятельном сценарии hasOld=false, остальные свойства остаются.
  2. CollectionModify::unset получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionModify::unset(array $fields): mysql_xdevapi\CollectionModify
ПараметрЧто передать
fieldsМассив путей удаляемых атрибутов, например ["obsolete"] или ["obsolete","temporaryNote"].

Что вернётся

CollectionModify для продолжения обработки и execute.

Правила и границы контракта

Удаление свойства отличается от установки null: после unset ключа нет. Этот метод принадлежит объекту операции; одноимённая конструкция PHP unset удаляет переменную/элемент в памяти PHP и сама не пишет в MySQL.

Применение 1

$operation->unset(['obsolete'])->execute();
$hasOld = array_key_exists('obsolete',$collection->getOne('LED-1'));

В самостоятельном сценарии hasOld=false, остальные свойства остаются. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->modify('_id = :id')->bind(['id'=>'LED-1'])
    ->unset(['obsolete','temporaryNote'])->execute();

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

Ошибки и выбор соседнего инструмента

Передавайте массив, даже если поле одно. Не путайте удаление поля с removeOne, удаляющим весь документ.

Связанные действия: Collection::modify, CollectionModify::set, Collection::replaceOne, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionModify::arrayAppendДобавить один элемент в конец массива документа.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionModify::arrayAppend(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify

Добавить один элемент в конец массива документа. Полная рабочая история — сценарий с данными и ожидаемой ведомостью.

От выбранного действия к наблюдаемому результату
  1. Основа, Винт превращается в Основа, Винт, Шайба.
  2. CollectionModify::arrayAppend получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionModify::arrayAppend(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify
ПараметрЧто передать
collection_fieldПуть поля, в котором хранится массив, например options.
expression_or_literalНовый элемент. Строка в наших примерах — значение; PECL8.0.30 также принимает числовые значения и массивы как zval.

Что вернётся

CollectionModify, который можно дополнить и исполнить.

Правила и границы контракта

Всегда дополняет конец, в отличие от arrayInsert с позицией. PHP-массив, переданный как один элемент, становится вложенным массивом и не означает автоматическую вставку каждого его элемента.

Применение 1

$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayAppend('options','Шайба')->execute();

Основа, Винт превращается в Основа, Винт, Шайба. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayAppend('options','price + 1')->execute();

Вторая ситуация — строка, похожая на выражение: в документе сохраняется точный текст price + 1. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Урок начинает с существующего массива. Не обещайте, что все отсутствующие/скалярные пути ведут себя как массив. Повторный execute новой операции с append может добавить дубль; это не идемпотентная установка.

Связанные действия: Collection::modify, CollectionModify::set, Collection::replaceOne, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionModify::arrayInsertВставить элемент на явную позицию в массиве документа.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionModify::arrayInsert(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify

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

От выбранного действия к наблюдаемому результату
  1. После append исходный список Основа, Винт, Шайба становится Основа, Клипса, Винт, Шайба.
  2. CollectionModify::arrayInsert получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionModify::arrayInsert(string $collection_field, string $expression_or_literal): mysql_xdevapi\CollectionModify
ПараметрЧто передать
collection_fieldПуть FIELD_NAME[INDEX], например options[1]; индекс начинается с0.
expression_or_literalВставляемый элемент; в PECL mysql_xdevapi8.0.30 аргумент принимает zval, хотя краткая сигнатура Manual показывает string.

Что вернётся

CollectionModify для дальнейших настроек или исполнения.

Правила и границы контракта

Параметры и описание PHP Manual говорят «после» указанного элемента, но его пример с traits[1] и официальный тест PECL8.0.30 показывают вставку на индекс1 перед прежним вторым элементом. Существующие элементы справа сдвигаются; в лаборатории этот порядок проверяется readback.

Применение 1

$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayInsert('options[1]','Клипса')->execute();

После append исходный список Основа, Винт, Шайба становится Основа, Клипса, Винт, Шайба. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->modify('_id = :id')->bind(['id'=>'KIT-1'])
    ->arrayInsert('options[0]','Инструкция')->execute();

Контекстное упражнение добавляет инструкцию первой; остальные элементы сохраняют взаимный порядок. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

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

Связанные действия: Collection::modify, CollectionModify::set, Collection::replaceOne, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionModify::executeОтправить подготовленное изменение и получить квитанцию.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionModify::execute(): mysql_xdevapi\Result

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

От выбранного действия к наблюдаемому результату
  1. Три изменения полей одного LED-1 дают1 затронутый документ.
  2. CollectionModify::execute получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionModify::execute(): mysql_xdevapi\Result

Параметров нет. Скобки после имени метода всё равно обязательны: они означают вызов.

Что вернётся

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

Правила и границы контракта

Это граница между настройкой modify/bind/set/unset и действием на сервере. Метод используется в цепочке; после него работайте с Result либо сделайте отдельное чтение документа.

Применение 1

$receipt = $operation->execute();
$count = $receipt->getAffectedItemsCount();

Три изменения полей одного LED-1 дают1 затронутый документ. Опора: самостоятельная программа и исходные данные.

Применение 2

$receipt = $collection->modify('_id = :id')->bind(['id'=>'NO-SUCH'])
    ->set('price',900)->execute();

Успешная отправка без совпадения даёт0 и не создаёт NO-SUCH. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Полученный Result не содержит новую карточку. Сбой сервера/запроса может выбросить исключение, а успешная операция с отсутствующим ключом даёт0; не смешивайте эти исходы.

Связанные действия: Collection::modify, CollectionModify::set, Collection::replaceOne, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Collection::replaceOneЗаменить существующий документ целиком по _id.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Collection::replaceOne(string $id, string $doc): mysql_xdevapi\Result

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

От выбранного действия к наблюдаемому результату
  1. CARD-1 получает цену1500 и теряет obsolete; affected1. Однако Unicode-имя на выбранной версии искажено, поэтому эта первая квитанция не принимается за корректную карточку. Рабочая полная замена и её точный readback показаны в том же сценарии.
  2. Collection::replaceOne получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Collection::replaceOne(string $id, string $doc): mysql_xdevapi\Result
ПараметрЧто передать
idКлюч заменяемого документа. Часто он был создан сервером при добавлении, но в уроке задан явно.
docПолный новый документ. Сигнатура показывает string, текст Parameters допускает также объект документа; пример Manual передаёт PHP-массив, прочитанный getOne. Урок использует явный JSON.

Что вернётся

Result для количества затронутых элементов и предупреждений. Если ключ не найден, вставки нет.

Правила и границы контракта

Метод исполняется сразу. Пропущенные свойства исчезают. PECL8.0.30 подставляет переданный id в документ, поэтому JSON без _id сохраняет адрес из первого аргумента; генерация нового ключа здесь не требуется. На нашем проверенном PHP8.3.35/mysql_xdevapi8.0.30 replaceOne искажает Unicode даже с внешним JSON_UNESCAPED_UNICODE: после счётчика1 читается текст с буквальными последовательностями обратная косая черта + uXXXX. Это дефект проверенной реализации, не требуемое поведение JSON или контракта метода.

Применение 1

$receipt = $collection->replaceOne('CARD-1',$replacement);
$observed = $collection->getOne('CARD-1') ?? null;

CARD-1 получает цену1500 и теряет obsolete; affected1. Однако Unicode-имя на выбранной версии искажено, поэтому эта первая квитанция не принимается за корректную карточку. Рабочая полная замена и её точный readback показаны в том же сценарии. Опора: самостоятельная программа и исходные данные.

Применение 2

$receipt = $collection->replaceOne('CARD-2',$replacement);
$card = $collection->getOne('CARD-2') ?? null;

На отсутствующем ключе квитанция0, нормализованный card=null; сама замена не становится upsert. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Квитанция1 не доказывает точность полей. В самостоятельном сценарии сохранено исходное искажённое чтение, затем проверенная альтернатива modify по _id + set корня $ готовой JSON-строкой с тем же _id получает точное имя. Не заменяйте её PHP-массивом на этом стенде: он снова проходит внутреннее кодирование. Если нужно поправить одно поле, выбирайте set поля. Полная замена старой копией может затереть параллельную правку; создание при отсутствии разрешает только отдельная политика upsert.

Связанные действия: Collection::modify, CollectionModify::set, Collection::addOrReplaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Collection::addOrReplaceOneЯвно разрешить создание при отсутствии либо полную замену по _id.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Collection::addOrReplaceOne(string $id, string $doc): mysql_xdevapi\Result

Явно разрешить создание при отсутствии либо полную замену по _id. Полная рабочая история — сценарий с данными и ожидаемой ведомостью.

От выбранного действия к наблюдаемому результату
  1. В ранее пустой позиции создаётся CARD-2 с ценой1500, affected1.
  2. Collection::addOrReplaceOne получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Collection::addOrReplaceOne(string $id, string $doc): mysql_xdevapi\Result
ПараметрЧто передать
idАдрес документа; серверные _id можно получить из квитанции добавления. В этом методе ключ выбран вызывающим кодом.
docJSON-строка полного документа для добавления или замены; PECL8.0.30 также принимает PHP-представление документа.

Что вернётся

Result завершённой записи; дополнительного execute нет.

Правила и границы контракта

Пять случаев Manual: (1) ID и уникальные значения свободны — вставка; (2) ID новый, но уникальное значение занято другим документом — ошибка; (3) ID найден, других unique keys нет — замена; (4) ID найден и unique values свободны либо относятся к той же записи — замена; (5) ID найден, но unique value относится к другому документу — ошибка. Обобщённая фраза в Parameters об обновлении по любому unique key не отменяет этот более точный список.

Применение 1

$created = $collection->addOrReplaceOne('CARD-2',$replacement);

В ранее пустой позиции создаётся CARD-2 с ценой1500, affected1. Опора: самостоятельная программа и исходные данные.

Применение 2

$changed = $collection->addOrReplaceOne('CARD-2',$upsertReplacement);

Теперь CARD-2 уже существует: цена1600, точное Unicode-имя сохраняется, всего документов остаётся2. Проверяется и вставленная, и заменённая карточка. Фактический affected сохраняется в observations без подмены входным количеством. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Это не поиск произвольной совпавшей записи по уникальному полю. Полная замена убирает пропущенные поля; разрешение создавать должно следовать из задачи. Счётчик замены может отличаться от вставки: в тесте PECL8.0.30 замена существующего даёт2, вставка1, поэтому проверяйте ещё readback.

Связанные действия: Collection::modify, CollectionModify::set, Collection::replaceOne. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Collection::removeOneУдалить один документ по его известному _id.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Collection::removeOne(string $id): mysql_xdevapi\Result

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

От выбранного действия к наблюдаемому результату
  1. Предварительно показанная OLD-1 удалена: affected1, последующее чтение null.
  2. Collection::removeOne получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Collection::removeOne(string $id): mysql_xdevapi\Result
ПараметрЧто передать
idКлюч удаляемого документа; это может быть серверный _id либо ранее заданный приложением ключ.

Что вернётся

Result со счётчиком и предупреждениями; действие выполняется сразу.

Правила и границы контракта

PHP-эквивалент короткой формы: remove("_id = :id")->bind(["id"=>$id])->execute(). В shortcut на странице Manual встречается bind("id",id) из другого языка; для PHP используется массив mapping.

Применение 1

$receipt = $collection->removeOne('OLD-1');

Предварительно показанная OLD-1 удалена: affected1, последующее чтение null. Опора: самостоятельная программа и исходные данные.

Применение 2

$receipt = $collection->removeOne('NO-SUCH');

Известный отсутствующий ключ даёт0 и не затрагивает LIVE-1. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Не добавляйте execute к Result. Ненайденный документ — квитанция0, а не успех удаления одной записи; при необходимости проверьте отдельным getOne.

Связанные действия: Collection::remove, CollectionRemove::execute. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Collection::removeПодготовить удаление документов, соответствующих условию.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Collection::remove(string $search_condition): mysql_xdevapi\CollectionRemove

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

От выбранного действия к наблюдаемому результату
  1. Отбор касается только архивных записей; LIVE-1 исключена условием.
  2. Collection::remove получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Collection::remove(string $search_condition): mysql_xdevapi\CollectionRemove
ПараметрЧто передать
search_conditionОбязательное выражение отбора. Допускается true для всех документов, но задача обычно требует узкого условия.

Что вернётся

CollectionRemove; дальше bind/sort/limit и execute.

Правила и границы контракта

Значения можно связать отдельно, объём удаления ограничить. До execute созданный объект не означает, что данные удалены. Квитанция Result появляется после исполнения.

Применение 1

$operation = $collection->remove('status = :state')->bind(['state'=>'archived']);

Отбор касается только архивных записей; LIVE-1 исключена условием. Опора: самостоятельная программа и исходные данные.

Применение 2

$receipt = $collection->remove('_id = :id')->bind(['id'=>'OLD-1'])->execute();

Контекстная длинная форма удаления одного известного документа; removeOne решает ту же адресную задачу короче. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Предпросмотр find и remove не образуют одну транзакцию сами по себе. Не заменяйте выражение пустой/всеобщей формой, если пользователь выбрал конкретную группу.

Связанные действия: Collection::removeOne, CollectionRemove::execute. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionRemove::bindПередать значения условию удаления отдельно от выражения.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionRemove::bind(array $placeholder_values): mysql_xdevapi\CollectionRemove

Передать значения условию удаления отдельно от выражения. Полная рабочая история — сценарий с данными и ожидаемой ведомостью.

От выбранного действия к наблюдаемому результату
  1. Контекстный выбор по статусу удаляет максимум две архивные записи.
  2. CollectionRemove::bind получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionRemove::bind(array $placeholder_values): mysql_xdevapi\CollectionRemove
ПараметрЧто передать
placeholder_valuesМассив PLACEHOLDER_NAME=>PLACEHOLDER_VALUE для одного или нескольких значений. Двоеточие находится в выражении :state, а ключ mapping — state.

Что вернётся

CollectionRemove для продолжения настроек и исполнения.

Правила и границы контракта

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

Применение 1

$collection->remove('status = :state')->bind(['state'=>'archived'])
    ->sort('_id ASC')->limit(2)->execute();

Контекстный выбор по статусу удаляет максимум две архивные записи. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->remove('_id = :id')->bind(['id'=>'NO-SUCH'])->execute();

Связанный неизвестный ключ остаётся обычным значением: удалено0, соседние записи не затронуты. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

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

Связанные действия: Collection::removeOne, Collection::remove, CollectionRemove::execute. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionRemove::sortОпределить, какие документы попадут в ограниченное удаление первыми.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionRemove::sort(string $sort_expr): mysql_xdevapi\CollectionRemove

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

От выбранного действия к наблюдаемому результату
  1. При одинаковом priority2 сначала OLD-2, затем OLD-3; OLD-4 остаётся.
  2. CollectionRemove::sort получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionRemove::sort(string $sort_expr): mysql_xdevapi\CollectionRemove
ПараметрЧто передать
sort_exprВыражение порядка ASC/DESC. Краткая сигнатура показывает одну строку; PECL8.0.30 принимает несколько строк либо массив строк. Критерии применяются слева направо.

Что вернётся

CollectionRemove; действие выполнит execute.

Правила и границы контракта

priority ASC выбирает меньший priority раньше, следующий _id ASC разрешает равенство. Порядок полезен вместе с limit; сам sort не уменьшает число совпадений.

Применение 1

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

При одинаковом priority2 сначала OLD-2, затем OLD-3; OLD-4 остаётся. Опора: самостоятельная программа и исходные данные.

Применение 2

$operation->sort('priority DESC','_id ASC')->limit(1);

Контекстная альтернативная политика: среди исходных архивных записей сначала OLD-4 с priority3. DESC меняет очередь, не статус документов. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Без устойчивого дополнительного ключа равные значения не дают выбранного автором порядка. Ввод посетителя не должен становиться произвольным sort-expression.

Связанные действия: Collection::removeOne, Collection::remove, CollectionRemove::execute. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionRemove::limitЗадать максимум удаляемых документов за выполнение.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionRemove::limit(int $rows): mysql_xdevapi\CollectionRemove

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

От выбранного действия к наблюдаемому результату
  1. Основной сценарий удаляет OLD-2 и OLD-3, affected2.
  2. CollectionRemove::limit получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionRemove::limit(int $rows): mysql_xdevapi\CollectionRemove
ПараметрЧто передать
rowsЦелое ограничение количества документов. В уроке положительное2; отрицательное значение PECL8.0.30 отклоняет исключением.

Что вернётся

CollectionRemove для исполнения/дальнейших настроек.

Правила и границы контракта

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

Применение 1

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

Основной сценарий удаляет OLD-2 и OLD-3, affected2. Опора: самостоятельная программа и исходные данные.

Применение 2

$collection->remove('status = :state')->bind(['state'=>'archived'])
    ->sort('_id ASC')->limit(2)->execute();

Контекстное продолжение после основного запуска удаляет только OLD-4: максимум2, фактически1; LIVE-1 остаётся. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Число берётся из проверенной политики приложения. Большой limit не делает безусловное удаление безопасным; небольшая партия не заменяет права доступа и критерий отбора.

Связанные действия: Collection::removeOne, Collection::remove, CollectionRemove::execute. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\CollectionRemove::executeВыполнить настроенное удаление и получить ответ сервера.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\CollectionRemove::execute(): mysql_xdevapi\Result

Выполнить настроенное удаление и получить ответ сервера. Полная рабочая история — сценарий с данными и ожидаемой ведомостью.

От выбранного действия к наблюдаемому результату
  1. После настроенной очереди deleted=2, в коллекции остаются LIVE-1 и OLD-4.
  2. CollectionRemove::execute получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\CollectionRemove::execute(): mysql_xdevapi\Result

Параметров нет. Скобки после имени метода всё равно обязательны: они означают вызов.

Что вернётся

Result: количество затронутых элементов, предупреждения и сведения об исполнении.

Правила и границы контракта

До вызова определены условие, параметры, порядок и предел. После него getAffectedItemsCount сообщает удалённое количество; оставшиеся документы читаются отдельным find.

Применение 1

$receipt = $operation->execute();
$deleted = $receipt->getAffectedItemsCount();

После настроенной очереди deleted=2, в коллекции остаются LIVE-1 и OLD-4. Опора: самостоятельная программа и исходные данные.

Применение 2

$receipt = $collection->remove('_id = :id')->bind(['id'=>'NO-SUCH'])->execute();

Вторая ситуация — успешный запрос без совпадений: affected0, никаких документов результата удаления нет. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Исключение записи и успешный нулевой результат различаются. Число в квитанции не перечисляет удалённые ключи и не является текущим count всей коллекции.

Связанные действия: Collection::removeOne, Collection::remove. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Session::startTransactionНачать общую транзакцию действий одного сеанса.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Session::startTransaction(): void

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

От выбранного действия к наблюдаемому результату
  1. Начало успешной пары: тот же Session затем пишет EV-OK и подтверждает обе записи.
  2. Session::startTransaction получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Session::startTransaction(): void

Параметров нет. Скобки после имени метода всё равно обязательны: они означают вызов.

Что вернётся

В synopsis Manual указан void, а в Return Values — SqlStatementResult. PECL8.0.30 возвращает результат служебного запроса; урок не определяет успех через truthiness этого возврата.

Правила и границы контракта

Последующие изменения того же Session завершаются commit или rollback. Коллекции создаём заранее: CREATE/DROP и повторный START TRANSACTION могут неявно подтвердить предыдущую транзакцию. Вложенность этим методом не создаётся; для промежуточных точек нужны savepoints.

Применение 1

$session->startTransaction();
$products->add(['_id'=>'P-OK','name'=>'Лампа'])->execute();

Начало успешной пары: тот же Session затем пишет EV-OK и подтверждает обе записи. Опора: самостоятельная программа и исходные данные.

Применение 2

$session->startTransaction();
$products->add(['_id'=>'P-FAIL','name'=>'Несохранённая лампа'])->execute();

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

Ошибки и выбор соседнего инструмента

Две разные Session не становятся одной транзакцией от одинакового имени схемы. Ошибку начала нельзя замаскировать активным флагом: active=true ставится после завершившегося вызова.

Связанные действия: Session::commit, Session::rollback, Session::close. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Session::commitПодтвердить изменения текущей транзакции.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Session::commit(): Object

Подтвердить изменения текущей транзакции. Полная рабочая история — сценарий с данными и ожидаемой ведомостью.

От выбранного действия к наблюдаемому результату
  1. Короткий фрагмент успешной ветви: после чтения доступны P-OK и EV-OK/product=P-OK.
  2. Session::commit получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Session::commit(): Object

Параметров нет. Скобки после имени метода всё равно обязательны: они означают вызов.

Что вернётся

Manual показывает Object в synopsis и SqlStatementResult в Return Values. Конкретный возврат не используется как логическое доказательство сохранения пары.

Правила и границы контракта

Вызов идёт после успешных предметных операций того же сеанса. После подтверждения отдельное чтение проверяет обе записи и их связь. Если нужно отменить ещё незавершённые изменения, существует rollback.

Применение 1

$products->add($product)->execute();
$events->add($event)->execute();
$session->commit();

Короткий фрагмент успешной ветви: после чтения доступны P-OK и EV-OK/product=P-OK. Опора: самостоятельная программа и исходные данные.

Применение 2

// В упражнении у второго события свободный _id EV-SECOND.
$session->commit();

Если обе вставки второй попытки успешны, подтверждение сохраняет P-FAIL и EV-SECOND; проверки упражнения должны ожидать их наличие, а не откат. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Ошибка промежуточного statement не всегда откатывает всю транзакцию. Не вызывайте commit в finally независимо от исхода. Потеря ответа commit — неопределённый для клиента исход, а не основание безусловно повторить всю бизнес-операцию.

Связанные действия: Session::startTransaction, Session::rollback, Session::close. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Session::rollbackОтменить незавершённые изменения транзакции этого сеанса.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Session::rollback(): void

Отменить незавершённые изменения транзакции этого сеанса. Полная рабочая история — сценарий с данными и ожидаемой ведомостью.

От выбранного действия к наблюдаемому результату
  1. После ожидаемого конфликта EV-BUSY новая P-FAIL отсутствует; старое событие PREEXISTING сохраняется. ?? null нормализует дефект возврата отсутствующего документа в выбранном расширении.
  2. Session::rollback получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Session::rollback(): void

Параметров нет. Скобки после имени метода всё равно обязательны: они означают вызов.

Что вернётся

Synopsis Manual говорит void, Return Values — SqlStatementResult; PECL8.0.30 возвращает результат служебного запроса. Для вывода об откате нужны завершившийся вызов и проверенное состояние.

Правила и границы контракта

При duplicate key обычно откатывается только неудавшаяся инструкция, поэтому явный rollback убирает прежнюю успешную вставку в этой транзакции. Deadlock и некоторые другие ошибки имеют другие границы отката; смотрите InnoDB Error Handling.

Применение 1

$session->rollback();
$failedProduct = $products->getOne('P-FAIL') ?? null;

После ожидаемого конфликта EV-BUSY новая P-FAIL отсутствует; старое событие PREEXISTING сохраняется. ?? null нормализует дефект возврата отсутствующего документа в выбранном расширении. Опора: самостоятельная программа и исходные данные.

Применение 2

try {$session->rollback();}
catch (Throwable $rollbackError) {
    throw new RuntimeException('Откат не подтверждён',0,$originalError);
}

Контекстная ветвь отказа самого отката: не печатаем «обе записи отменены», передаём ошибку выше и сохраняем исходную причину. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

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

Связанные действия: Session::startTransaction, Session::commit, Session::close. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

mysql_xdevapi\Session::closeЗакрыть соединение, когда работа этого сеанса закончена.

Первая версия метода не всегда указана Manual; учебный runtime mysql_xdevapi8.0.30 · mysql_xdevapi

public function mysql_xdevapi\Session::close(): bool

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

От выбранного действия к наблюдаемому результату
  1. Даже если очистка своего учебного набора закончилась ошибкой, выполняется закрытие Session.
  2. Session::close получает явно указанные аргументы
  3. Ответ API сопоставляется с повторным чтением либо завершением сеанса

Сигнатура и вход

public function mysql_xdevapi\Session::close(): bool

Параметров нет. Скобки после имени метода всё равно обязательны: они означают вызов.

Что вернётся

true при закрытом сеансе по описанию Manual.

Правила и границы контракта

После закрытия этот объект не предназначен для дальнейших запросов. В самостоятельных CLI-примерах сначала завершаются транзакции, затем удаляются собственные учебные коллекции и в самом внутреннем finally закрывается Session.

Применение 1

try {$removed = x139Cleanup($schema,$owned);}
finally {$session->close();}

Даже если очистка своего учебного набора закончилась ошибкой, выполняется закрытие Session. Опора: самостоятельная программа и исходные данные.

Применение 2

finally {if ($session !== null) {$session->close();}}

Каждый HTTP-запрос освобождает своё соединение. Сама owned-коллекция сохраняется до отдельного CLI cleanup, поэтому следующий GET читает новую цену. Опора: самостоятельная программа и исходные данные.

Ошибки и выбор соседнего инструмента

Close не является прикладной квитанцией commit. Сначала явно решите судьбу транзакции. Не удаляйте коллекцию веб-лаборатории при закрытии каждого запроса: данные должны переживать GET и POST.

Связанные действия: Session::startTransaction, Session::commit, Session::rollback. Квитанцию читает Result::getAffectedItemsCount, документ — Collection::getOne.

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

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

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

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

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