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

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

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

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

Глава 84 · После подключения CUBRID и параметризованных запросов

CUBRID: перенос остатков, отмена пакета и граница autocommit

Два остатка одной операции, отмена по бизнес-условию и UNIQUE, режим autocommit и подтверждённый результат.

Зачем объединять изменения одной границей

Перенос коробок между ячейками должен либо уменьшить один остаток и увеличить другой, либо не изменить ни один. Сначала разберём это на двух строках: А = 12, Б = 4. Принятый перенос трёх даст 9/7; отказ назначения оставит 12/4. Затем тот же принцип поможет сохранить пакет правок карточки и понять, почему поздний rollback не отменяет автоматически подтверждённое изменение.

Транзакция — несколько обращений к базе с общей границей подтверждения. commit означает «принять накопленную работу», rollback — «отменить ещё не подтверждённую». Autocommit задаёт автоматическую фиксацию отдельных команд. Это разные вещи: выбор режима не равен успешному выполнению заказа. Соединение $db определяет, к какой базе и текущей работе относятся вызовы.

Перенос трёх коробок — одна операция
  1. Исходные строки: А 12 и Б 4, всего 16
  2. Режим false: UPDATE А − 3; проверить 1 строку → UPDATE Б + 3; проверить 1 строку
  3. Решение: оба шага приняты → commit; любой отказ → rollback
  4. Результат принятия 9/7 либо результат отмены 12/4; повторное чтение показывает выбранную ветку

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

Самый маленький осмысленный шаг

// $db — открытое соединение; прежняя работа уже завершена.
if (cubrid_set_autocommit($db, false) !== true) {
    throw new RuntimeException('Не установлен ручной режим');
}
$mode = cubrid_get_autocommit($db);
if ($mode === null) { throw new RuntimeException('Режим неизвестен'); }
echo $mode ? 'Автофиксация включена' : 'Нужен явный commit';
// Ожидаемый текст: Нужен явный commit

Имя $db хранит ресурс соединения. Второй аргумент false является логическим значением без кавычек. set сообщает, удалось ли задать режим; get возвращает сам режим. Проверка === null отделяет отказ чтения настройки от нормального false. throw останавливает зависимую работу при отказе; полные проекты добавляют try/finally для освобождения своих ресурсов. Если эти конструкции незнакомы, рядом есть вызовы и аргументы функций, условия и ошибки и исключения. Текущая роль каждого вызова разобрана непосредственно здесь.

Перед этими уроками удобно пройти подключение и первую таблицу и параметры SQL и affected_rows. Функции c166Read/c166Command в полном файле только готовят/исполняют SQL, читают нужные строки и закрывают request; их код находится в lab.php. Они не фиксируют бизнес-команды автоматически. Подготовка учебной таблицы подтверждается отдельно до начала сценария.

Native-расширение здесь закреплено как cubrid 11.2.0.0002 с PHP 7.4.33 и сервером CUBRID 11.3.5 в отдельной Linux-лаборатории. Это исторический профиль совместимости, а не предложение понизить PHP сайта. Пароли, личные пути и данные действующего сайта не нужны: README описывает отдельную базу, namespace и запуск каждой программы.

ЗадачаЧто человек увидитПолная программа
Принять перенос12/4 →9/7, всего 16Два UPDATE и commit
Получатель не существуетВременные 9 → восстановленные 12/4Отказ условия и rollback
Второй артикул занятЦена 9900 отменена обратно до 1200Ошибка SQL и отмена пакета
Поздняя отмена не помогла12 →14 сохранено;17 →14 отмененоДва режима на одной записи

Перенести три коробки между двумя ячейками склада

Кладовщик перемещает три коробки кабеля из ячейки А в Б. Одно действие пользователя требует двух UPDATE: уменьшить 12 до 9 и увеличить 4 до 7. Если подтвердить только первую половину, три коробки исчезнут из учёта. Транзакция позволяет зафиксировать обе записи вместе; повторное подключение покажет 9 и 7, всего по-прежнему 16.

Путь конкретных значений
  1. Две строки одной таблицы: id 1 / А / 12; id 2 / Б / 4
  2. Autocommit false: UPDATE id 1 вычитает 3 → 9; UPDATE id 2 прибавляет 3 → 7
  3. Обе команды изменили по одной строке → commit подтверждает весь перенос
  4. Новый connection → SELECT id,title,qty → А 9, Б 7, сумма 16

Минимальный вызов и его смысл

// $db уже подключён; связанные UPDATE уже успешно выполнены.
if (cubrid_commit($db) !== true) {
    throw new RuntimeException('Перенос не подтверждён');
}
echo 'Обе записи сохранены';

cubrid_commit получает соединение $db, в котором выполнялись обе команды. Скобки вызывают функцию, !== true проверяет точный успешный результат. true подтверждает завершение транзакции; это не количество коробок и не количество UPDATE. В полной программе перед списанием проверяется положительное количество, а условие qty>=? находится в самом SQL. c166Command выполняет prepare/bind/execute, сразу снимает affected_rows и освобождает запрос. Ни внутри него, ни между двумя UPDATE нет commit.

Результат самостоятельной программы

НаблюдениеРезультат
До переноса: А / Б12 / 4
Перенесено коробок3
После commit: А / Б9 / 7
Всего коробок16
Новое подключение видит переносда
Autocommit после commitнет
Полная программа, файлы и архив для запуска
Перенести три коробки между двумя ячейками складаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);

    $name=c166CreateTable($db,$owned,'transfer',[
        ['id'=>1,'sku'=>'CELL-A-166','title'=>'Кабель: ячейка А','category'=>'storage','price_cents'=>1200,'qty'=>12,'note'=>null],
        ['id'=>2,'sku'=>'CELL-B-166','title'=>'Кабель: ячейка Б','category'=>'storage','price_cents'=>1200,'qty'=>4,'note'=>null]
    ]);
    $table=c166Qualified($name);
    $before=c166Read($db,'SELECT id,qty FROM '.$table.' ORDER BY id LIMIT 2');
    $modeBefore=cubrid_get_autocommit($db);
    if ($modeBefore!==false) {throw new RuntimeException('Для переноса нужен явный режим транзакции.');}
    $amount=3;$sourceId=1;$destinationId=2;
    if ($amount<=0 || $sourceId===$destinationId) {throw new InvalidArgumentException('Неверные данные переноса.');}
    $committed=false;
    try {
        $debited=c166Command($db,'UPDATE '.$table.' SET qty=qty-? WHERE id=? AND qty>=?',[$amount,$sourceId,$amount]);
        if ($debited!==1) {throw new RuntimeException('Нет источника или недостаточно коробок.');}
        $credited=c166Command($db,'UPDATE '.$table.' SET qty=qty+? WHERE id=?',[$amount,$destinationId]);
        if ($credited!==1) {throw new RuntimeException('Ячейка назначения не найдена.');}
        $commit=c166Capture(static function() use ($db) {return cubrid_commit($db);});
        if ($commit['value']!==true || $commit['warning']) {c166Failure('Перенос не подтверждён',$commit);}
        $committed=true;
    } catch (Throwable $failure) {
        c166Rollback($db);
        throw $failure;
    }
    $modeAfter=cubrid_get_autocommit($db);
    $sameConnection=c166Read($db,'SELECT id,qty FROM '.$table.' ORDER BY id LIMIT 2');
    c166Commit($db);
    c166Close($db);$db=c166Connect();
    $fresh=c166Read($db,'SELECT id,title,qty FROM '.$table.' ORDER BY id LIMIT 2');
    $quantities=array_map(static function(array $row):int {return (int)$row['qty'];},$fresh);
    $rows=[['До переноса: А / Б','12 / 4'],['Перенесено коробок',$amount],
        ['После commit: А / Б',$quantities[0].' / '.$quantities[1]],['Всего коробок',array_sum($quantities)],
        ['Новое подключение видит перенос',$quantities===[9,7]],['Autocommit после commit',$modeAfter]];
    $checks=['before_source'=>(int)$before[0]['qty']===12,'before_destination'=>(int)$before[1]['qty']===4,
        'explicit_mode'=>$modeBefore===false,'debit_one'=>$debited===1,'credit_one'=>$credited===1,
        'commit_confirmed'=>$committed && $commit['value']===true,'mode_stays_manual'=>$modeAfter===false,
        'same_connection_usable'=>array_column($sameConnection,'qty')===['9','7'],
        'fresh_source'=>$quantities[0]===9,'fresh_destination'=>$quantities[1]===7,
        'total_preserved'=>array_sum($quantities)===16,'same_two_cells'=>array_column($fresh,'id')===['1','2']];
    $observations=['before'=>$before,'amount'=>$amount,'affected_rows'=>[$debited,$credited],
        'same_connection_after_commit'=>$sameConnection,'fresh_connection_rows'=>$fresh,
        'mode_before'=>$modeBefore,'mode_after_commit'=>$modeAfter,'commit_return'=>$commit['value']];

} finally {
    if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}

c166Report('cub166-transfer-commit','Перенести три коробки между двумя ячейками склада',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До переноса: А / Б — 12 / 4
Перенесено коробок — 3
После commit: А / Б — 9 / 7
Всего коробок — 16
Новое подключение видит перенос — да
Autocommit после commit — нет

Распакуйте все файлы и выполните example.php по README.txt в отдельном CUBRID-окружении. lab.php проверяет namespace/базу, создаёт только собственную случайную таблицу и удаляет её в finally после наблюдений. result.html показывает результат, result.json сохраняет прочитанные значения, metrics.json — проверки и очистку. Сырой пароль и серверные сообщения в публичный отчёт не попадают.

Открыть фактический результат · Открыть данные JSON.

Ошибка, граница и развитие задачи

В учебной таблице SKU обозначает ячейку с конкретным товаром; в настоящем учёте товар и складские места обычно связывают отдельными ключами. Этот пример не измеряет конкуренцию двух кладовщиков, deadlock или повтор заказа после сетевого обрыва. Проверка суммы не заменяет правильные id и права доступа. После отказа ответа commit исход операции может быть неизвестен: не повторяйте списание вслепую. Транзакция охватывает одну базу/соединение; отправленное письмо или HTTP-запрос она не отменяет.

Измените требование

Что нужно изменить, чтобы перенести все 12 коробок из А в Б?

Разбор решения

Передайте amount=12. При исходных 12 и 4 оба UPDATE должны дать по одной строке, а итог — 0 и 16. Нулевой остаток допустим и должен отображаться. Если amount=13, первый UPDATE даст 0; до зачисления и commit программа не должна доходить.

Отменить списание, если ячейка назначения не существует

Номер ячейки назначения устарел: приложение пытается зачислить коробки в id 9999, которого нет. Сам UPDATE синтаксически правильный и не является SQL-ошибкой, но affected_rows равен 0. Первый UPDATE уже уменьшил А до 9 в текущей транзакции. После rollback новое подключение снова прочитает 12 и 4: учёт не потеряет коробки.

Путь конкретных значений
  1. А 12 → незавершённое списание 3 → в текущем connection А 9
  2. UPDATE назначения id 9999 → affected_rows 0: бизнес-условие не выполнено
  3. Ветка отказа: rollback отменяет предшествующее списание
  4. Новый connection читает А 12 / Б 4; перенос не принят

Минимальный вызов и его смысл

// Получатель не найден; первый UPDATE ещё не был подтверждён.
if (cubrid_rollback($db) !== true) {
    throw new RuntimeException('Не удалось подтвердить отмену');
}
echo 'Перенос отклонён, списание отменено';

rollback получает то же соединение, которое выполнило списание. Он отменяет незавершённую транзакцию целиком, а не только последнюю команду. Значение 0 от affected_rows означает «не подошла ни одна строка»; решение считать это отказом принимает код переноса. Обычному изменению необязательной заметки такая политика может не подойти. SELECT перед rollback специально показывает, что собственное незавершённое изменение уже читается в этом connection, но ещё может быть отменено.

Результат самостоятельной программы

НаблюдениеРезультат
До переноса: А / Б12 / 4
А внутри незавершённого переноса9
Найдено ячеек назначения0
После rollback: А / Б12 / 4
Всего коробок16
Перенос принятнет
Полная программа, файлы и архив для запуска
Отменить списание, если ячейка назначения не существуетСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);

    $name=c166CreateTable($db,$owned,'destination',[
        ['id'=>1,'sku'=>'CELL-A-166','title'=>'Кабель: ячейка А','category'=>'storage','price_cents'=>1200,'qty'=>12,'note'=>null],
        ['id'=>2,'sku'=>'CELL-B-166','title'=>'Кабель: ячейка Б','category'=>'storage','price_cents'=>1200,'qty'=>4,'note'=>null]
    ]);
    $table=c166Qualified($name);$amount=3;$destinationId=9999;
    $before=c166Read($db,'SELECT id,qty FROM '.$table.' ORDER BY id LIMIT 2');
    $modeBefore=cubrid_get_autocommit($db);
    if ($modeBefore!==false) {throw new RuntimeException('Для переноса нужен autocommit=false.');}
    $debited=c166Command($db,'UPDATE '.$table.' SET qty=qty-? WHERE id=? AND qty>=?',[$amount,1,$amount]);
    if ($debited!==1) {throw new RuntimeException('Первое списание не выполнено.');}
    $pending=c166Read($db,'SELECT qty FROM '.$table.' WHERE id=?',[1]);
    $credited=c166Command($db,'UPDATE '.$table.' SET qty=qty+? WHERE id=?',[$amount,$destinationId]);
    if ($credited!==0) {throw new RuntimeException('Ожидалась отсутствующая ячейка назначения.');}
    // SQL был корректным, но бизнес-операцию нельзя завершить частично.
    $rollback=c166Capture(static function() use ($db) {return cubrid_rollback($db);});
    if ($rollback['value']!==true || $rollback['warning']) {c166Failure('Перенос не отменён',$rollback);}
    $sameConnection=c166Read($db,'SELECT id,qty FROM '.$table.' ORDER BY id LIMIT 2');
    c166Commit($db);c166Close($db);$db=c166Connect();
    $fresh=c166Read($db,'SELECT id,title,qty FROM '.$table.' ORDER BY id LIMIT 2');
    $quantities=array_map(static function(array $row):int {return (int)$row['qty'];},$fresh);
    $modeAfter=cubrid_get_autocommit($db);
    $rows=[['До переноса: А / Б','12 / 4'],['А внутри незавершённого переноса',(int)$pending[0]['qty']],
        ['Найдено ячеек назначения',$credited],['После rollback: А / Б',$quantities[0].' / '.$quantities[1]],
        ['Всего коробок',array_sum($quantities)],['Перенос принят',false]];
    $checks=['before_two_cells'=>count($before)===2,'initial_quantities'=>array_column($before,'qty')===['12','4'],
        'manual_mode'=>$modeBefore===false,'first_update_happened'=>$debited===1,
        'own_pending_change_visible'=>(int)$pending[0]['qty']===9,'second_update_matched_nothing'=>$credited===0,
        'rollback_confirmed'=>$rollback['value']===true,'same_connection_restored'=>array_column($sameConnection,'qty')===['12','4'],
        'fresh_state_restored'=>$quantities===[12,4],'total_preserved'=>array_sum($quantities)===16,
        'destination_not_created'=>array_column($fresh,'id')===['1','2'],'manual_mode_preserved'=>$modeAfter===false];
    $observations=['before'=>$before,'pending_source'=>$pending,'destination_requested'=>$destinationId,
        'affected_rows'=>[$debited,$credited],'rollback_return'=>$rollback['value'],
        'same_connection_after_rollback'=>$sameConnection,'fresh_connection_rows'=>$fresh,
        'refusal_reason'=>'Ячейка назначения не найдена; перенос отменён целиком.'];

} finally {
    if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}

c166Report('cub166-transfer-rollback','Отменить списание, если ячейка назначения не существует',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До переноса: А / Б — 12 / 4
А внутри незавершённого переноса — 9
Найдено ячеек назначения — 0
После rollback: А / Б — 12 / 4
Всего коробок — 16
Перенос принят — нет

Распакуйте все файлы и выполните example.php по README.txt в отдельном CUBRID-окружении. lab.php проверяет namespace/базу, создаёт только собственную случайную таблицу и удаляет её в finally после наблюдений. result.html показывает результат, result.json сохраняет прочитанные значения, metrics.json — проверки и очистку. Сырой пароль и серверные сообщения в публичный отчёт не попадают.

Открыть фактический результат · Открыть данные JSON.

Ошибка, граница и развитие задачи

Не исправляйте частичный перенос дополнительным UPDATE qty=qty+3 вместо rollback: можно ошибиться при повторе, параллельном изменении или неверном id. rollback не является способом отменить вчерашний уже подтверждённый перенос; для него нужна новая учётная операция. В демонстрации нет сетевого обрыва, поэтому подтверждение отмены получено обычным ответом сервера. При отказе rollback соединение нельзя продолжать использовать как заведомо чистое.

В вопросе о mysqli отмены стояли после отдельных INSERT, а связанные записи сохранялись по-разному. Исходный вопрос. Здесь заново сформулирована граница одной складской операции: два UPDATE и одно решение. Особенности движков MySQL и код ответа не перенесены в CUBRID.

Измените требование

Если назначения нет, можно ли сначала сохранить списание, а затем показать пользователю ошибку?

Разбор решения

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

Отменить пакет редактирования, когда второй SKU уже занят

Менеджер отправил одной формой две правки: новую цену кабеля и новый артикул клеммы. Цена записалась, но второй артикул уже занят кабелем. Правило формы — принять весь пакет либо ничего. Выполнение второго UPDATE вернёт отказ UNIQUE; после явного rollback цена снова будет 1200, артикул клеммы останется CLAMP-166.

Путь конкретных значений
  1. Карточки: CABLE-166 / 1200; CLAMP-166 / 1800
  2. Первая правка цены → 9900 внутри незавершённой транзакции
  3. Вторая правка SKU → CABLE-166 уже занят → execute false и немедленно сохранённая диагностика
  4. rollback всего пакета → цена 1200, уникальные SKU сохранены

Минимальный вызов и его смысл

$ok = cubrid_execute($request);
if ($ok !== true) {
    $code = cubrid_error_code(); // до других операций
    if (cubrid_rollback($db) !== true) {
        throw new RuntimeException('Отмена пакета не подтверждена');
    }
    echo 'Пакет отклонён, код ', $code;
}

Здесь request уже подготовлен и получил параметры второго изменения. native cubrid обычно сообщает такой отказ значением false и предупреждением, а не автоматически бросает исключение, подходящее вашему catch. Поэтому код проверяет возврат явно. Полный файл временно перехватывает предупреждение, сразу сохраняет code/facility/наличие сообщения и лишь затем закрывает request и отменяет транзакцию. Он не публикует сырой текст сервера со случайным именем таблицы.

Результат самостоятельной программы

НаблюдениеРезультат
Цена кабеля до пакета, коп.1200
Цена внутри пакета, коп.9900
Повторный SKU принятнет
Цена после rollback, коп.1200
SKU второй карточкиCLAMP-166
Исходный каталог восстановленда
Полная программа, файлы и архив для запуска
Отменить пакет редактирования, когда второй SKU уже занятСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);

    $name=c166CreateTable($db,$owned,'batch',[
        ['id'=>1,'sku'=>'CABLE-166','title'=>'Кабель','category'=>'electrical','price_cents'=>1200,'qty'=>12,'note'=>null],
        ['id'=>2,'sku'=>'CLAMP-166','title'=>'Клемма','category'=>'electrical','price_cents'=>1800,'qty'=>4,'note'=>null]
    ]);
    $table=c166Qualified($name);
    $before=c166Read($db,'SELECT id,sku,price_cents,qty FROM '.$table.' ORDER BY id LIMIT 2');
    $changed=c166Command($db,'UPDATE '.$table.' SET price_cents=? WHERE id=?',[9900,1]);
    if ($changed!==1) {throw new RuntimeException('Цена не изменена.');}
    $pending=c166Read($db,'SELECT price_cents FROM '.$table.' WHERE id=?',[1]);
    $request=null;
    try {
        $request=cubrid_prepare($db,'UPDATE '.$table.' SET sku=? WHERE id=?');
        if ($request===false) {c166Failure('Изменение SKU не подготовлено');}
        if (!cubrid_bind($request,1,'CABLE-166','string') || !cubrid_bind($request,2,2,'number')) {
            c166Failure('Параметры SKU не переданы');
        }
        $failure=c166Capture(static function() use ($request) {return cubrid_execute($request);});
        // Capture сохранил ошибку ДО close_request и rollback.
    } finally {if (is_resource($request)) {c166Release($request);}}
    if ($failure['value']!==false) {throw new RuntimeException('Ожидался отказ UNIQUE-ограничения.');}
    $rollback=c166Capture(static function() use ($db) {return cubrid_rollback($db);});
    if ($rollback['value']!==true || $rollback['warning']) {c166Failure('Пакет не отменён',$rollback);}
    $after=c166Read($db,'SELECT id,sku,price_cents,qty FROM '.$table.' ORDER BY id LIMIT 2');
    $modeAfter=cubrid_get_autocommit($db);
    $diagnostic=['code'=>$failure['code'],'facility'=>$failure['facility'],
        'warning'=>$failure['warning'],'message_present'=>$failure['message_present']];
    $rows=[['Цена кабеля до пакета, коп.',1200],['Цена внутри пакета, коп.',(int)$pending[0]['price_cents']],
        ['Повторный SKU принят',$failure['value']!==false],['Цена после rollback, коп.',(int)$after[0]['price_cents']],
        ['SKU второй карточки',$after[1]['sku']],['Исходный каталог восстановлен',$after===$before]];
    $checks=['two_initial_items'=>count($before)===2,'first_edit_applied'=>$changed===1,
        'pending_price_visible'=>(int)$pending[0]['price_cents']===9900,'duplicate_sku_rejected'=>$failure['value']===false,
        'diagnostic_saved'=>$failure['code']===-670 && $failure['facility']===1 && $failure['message_present'],
        'warning_captured'=>$failure['warning']===true,'request_released'=>$request===null,
        'rollback_confirmed'=>$rollback['value']===true,'all_fields_restored'=>$after===$before,
        'original_price_restored'=>(int)$after[0]['price_cents']===1200,'original_sku_preserved'=>$after[1]['sku']==='CLAMP-166',
        'manual_mode_preserved'=>$modeAfter===false];
    $observations=['before'=>$before,'pending_first_price'=>$pending,'after'=>$after,
        'failed_execute_return'=>$failure['value'],'failure'=>$diagnostic,'rollback_return'=>$rollback['value'],
        'business_policy'=>'Один пакет двух изменений: при любом отказе не подтверждать ни одно.'];

} finally {
    if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}

c166Report('cub166-batch-rollback','Отменить пакет редактирования, когда второй SKU уже занят',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Цена кабеля до пакета, коп. — 1200
Цена внутри пакета, коп. — 9900
Повторный SKU принят — нет
Цена после rollback, коп. — 1200
SKU второй карточки — CLAMP-166
Исходный каталог восстановлен — да

Распакуйте все файлы и выполните example.php по README.txt в отдельном CUBRID-окружении. lab.php проверяет namespace/базу, создаёт только собственную случайную таблицу и удаляет её в finally после наблюдений. result.html показывает результат, result.json сохраняет прочитанные значения, metrics.json — проверки и очистку. Сырой пароль и серверные сообщения в публичный отчёт не попадают.

Открыть фактический результат · Открыть данные JSON.

Ошибка, граница и развитие задачи

UNIQUE-ограничение охраняет правило даже при параллельной записи; предварительный поиск свободного SKU сам по себе не заменяет его. Явный rollback показывает политику всего пакета: не следует предполагать, что любая ошибка одной команды автоматически отменила все предыдущие команды именно так, как требует форма. Полный сценарий проверяет конкретное ограничение, а не все коды ошибок CUBRID. Таблица создаётся и подтверждается до бизнес-пакета; DDL и миграции в этот пакет не включены.

Автор ожидал, что ошибка первого PDO-вызова автоматически остановит остальные команды и попадёт в catch. Исходный вопрос. Собственный native CUBRID-сценарий проверяет false и предупреждение прямо, затем явно выполняет rollback; режим исключений PDO не выдаётся за свойство cubrid.

Измените требование

Продуктовое требование изменилось: разрешено сохранять правильную цену, даже если SKU отклонён. Достаточно убрать rollback?

Разбор решения

Сначала явно измените границы бизнес-операций: сохранение цены должно стать отдельным подтверждённым действием с собственным статусом, а правка SKU — другим. Простое удаление rollback оставляет неясный исход пакета и не сообщает пользователю, какие поля приняты. Для режима «всё или ничего» текущая граница остаётся правильной.

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

В коде вызван rollback, а остаток всё равно изменился. Важно узнать не только «был ли вызов», но и когда запись стала подтверждённой. Здесь autocommit=true сохраняет прибавление двух коробок: 12 → 14, поздний rollback это не отменяет. Затем autocommit=false позволяет временно получить 17 и отменить только новую прибавку трёх: итог снова 14.

Путь конкретных значений
  1. Зафиксированный исходный остаток 12; явное завершение прежней работы
  2. Режим true → UPDATE +2 уже подтверждён → поздний rollback оставляет 14
  3. Режим false → UPDATE +3 пока не подтверждён → внутри connection видно 17
  4. rollback ручной правки → 14; на выходе режим false

Минимальный вызов и его смысл

$mode = cubrid_get_autocommit($db);
if ($mode === null) { throw new RuntimeException('Режим неизвестен'); }
echo $mode ? 'Автоматическая фиксация' : 'Явная фиксация';
if (cubrid_set_autocommit($db, false) !== true) {
    throw new RuntimeException('Режим не изменён');
}

get возвращает состояние режима, а set возвращает успех изменения режима. Поэтому false от get — нормальное значение «выключено», а false от set — отказ. В закреплённом PHP 7.4 strict_types второй аргумент set — настоящий bool: используйте false/true без кавычек. Числовая константа CUBRID_AUTOCOMMIT_FALSE равна 0, но её тип int не подходит этому строгому вызову. Полный пример проверяет get после set и читает сохранённый остаток, а не выводит только флаг.

Результат самостоятельной программы

НаблюдениеРезультат
Исходный остаток12
Autocommit включёнда
После +2 и rollback при autocommit14
После выключения autocommit и +317
После rollback ручной правки14
Autocommit на выходенет
Полная программа, файлы и архив для запуска
Различить уже сохранённое изменение и ещё отменяемую правкуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);

    $name=c166CreateTable($db,$owned,'autocommit',[
        ['id'=>1,'sku'=>'CABLE-166','title'=>'Кабель','category'=>'electrical','price_cents'=>1200,'qty'=>12,'note'=>null]
    ]);
    $table=c166Qualified($name);$initialMode=cubrid_get_autocommit($db);
    if ($initialMode!==false) {throw new RuntimeException('Начальное состояние должно быть явным.');}
    $initial=c166Read($db,'SELECT qty FROM '.$table.' WHERE id=?',[1]);
    c166Commit($db); // Завершить предыдущую работу перед сменой режима.
    try {
        $enabled=c166Capture(static function() use ($db) {return cubrid_set_autocommit($db,true);});
        if ($enabled['value']!==true || $enabled['warning']) {c166Failure('Autocommit не включён',$enabled);}
        $automaticMode=cubrid_get_autocommit($db);
        $automaticUpdate=c166Command($db,'UPDATE '.$table.' SET qty=qty+? WHERE id=?',[2,1]);
        $automaticRollback=c166Capture(static function() use ($db) {return cubrid_rollback($db);});
        if ($automaticRollback['value']!==true || $automaticRollback['warning']) {c166Failure('ROLLBACK не выполнен',$automaticRollback);}
        $afterAutomatic=c166Read($db,'SELECT qty FROM '.$table.' WHERE id=?',[1]);
        $disabled=c166Capture(static function() use ($db) {return cubrid_set_autocommit($db,false);});
        if ($disabled['value']!==true || $disabled['warning']) {c166Failure('Явный режим не восстановлен',$disabled);}
        $manualMode=cubrid_get_autocommit($db);
        $manualUpdate=c166Command($db,'UPDATE '.$table.' SET qty=qty+? WHERE id=?',[3,1]);
        $pending=c166Read($db,'SELECT qty FROM '.$table.' WHERE id=?',[1]);
        $manualRollback=c166Capture(static function() use ($db) {return cubrid_rollback($db);});
        if ($manualRollback['value']!==true || $manualRollback['warning']) {c166Failure('Ручная правка не отменена',$manualRollback);}
        $afterManual=c166Read($db,'SELECT qty FROM '.$table.' WHERE id=?',[1]);
    } finally {
        // Лаборатория дальше работает в явном режиме; включения true здесь нет.
        $restored=c166Capture(static function() use ($db) {return cubrid_set_autocommit($db,false);});
        if ($restored['value']!==true || $restored['warning']) {c166Failure('Режим очистки не восстановлен',$restored);}
    }
    $finalMode=cubrid_get_autocommit($db);
    $rows=[['Исходный остаток',(int)$initial[0]['qty']],['Autocommit включён',$automaticMode],
        ['После +2 и rollback при autocommit',(int)$afterAutomatic[0]['qty']],
        ['После выключения autocommit и +3',(int)$pending[0]['qty']],
        ['После rollback ручной правки',(int)$afterManual[0]['qty']],['Autocommit на выходе',$finalMode]];
    $checks=['initial_quantity'=>(int)$initial[0]['qty']===12,'initial_manual'=>$initialMode===false,
        'enable_confirmed'=>$enabled['value']===true,'automatic_mode'=>$automaticMode===true,
        'automatic_update_one'=>$automaticUpdate===1,'automatic_rollback_return'=>$automaticRollback['value']===true,
        'already_committed_change_remains'=>(int)$afterAutomatic[0]['qty']===14,
        'disable_confirmed'=>$disabled['value']===true,'manual_mode'=>$manualMode===false,
        'manual_update_one'=>$manualUpdate===1,'pending_manual_visible'=>(int)$pending[0]['qty']===17,
        'manual_rollback_return'=>$manualRollback['value']===true,'manual_change_cancelled'=>(int)$afterManual[0]['qty']===14,
        'final_manual_mode'=>$finalMode===false];
    $observations=['initial'=>$initial,'autocommit_phase'=>['mode'=>$automaticMode,'affected'=>$automaticUpdate,
        'rollback_return'=>$automaticRollback['value'],'after'=>$afterAutomatic],
        'manual_phase'=>['mode'=>$manualMode,'affected'=>$manualUpdate,'pending'=>$pending,
        'rollback_return'=>$manualRollback['value'],'after'=>$afterManual],
        'final_mode'=>$finalMode,'mode_restored_without_switching_on'=>true];

} finally {
    if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}

c166Report('cub166-autocommit-boundary','Различить уже сохранённое изменение и ещё отменяемую правку',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Исходный остаток — 12
Autocommit включён — да
После +2 и rollback при autocommit — 14
После выключения autocommit и +3 — 17
После rollback ручной правки — 14
Autocommit на выходе — нет

Распакуйте все файлы и выполните example.php по README.txt в отдельном CUBRID-окружении. lab.php проверяет namespace/базу, создаёт только собственную случайную таблицу и удаляет её в finally после наблюдений. result.html показывает результат, result.json сохраняет прочитанные значения, metrics.json — проверки и очистку. Сырой пароль и серверные сообщения в публичный отчёт не попадают.

Открыть фактический результат · Открыть данные JSON.

Ошибка, граница и развитие задачи

Не включайте autocommit в безусловном finally ради «возврата настройки», пока есть незавершённая работа: граница смены режима требует отдельного внимания. Здесь режим true включается только после явного commit, а finally устанавливает false. rollback не является исторической кнопкой «отменить последнее сохранение». Оба этапа выполняются последовательно в одном клиенте; одновременная работа других клиентов и сетевой обрыв не моделируются. Правила PDO/mysqli beginTransaction нельзя автоматически переносить на native cubrid.

В обсуждении смешались настройка autocommit и границы явной транзакции: было непонятно, что именно отменяет rollback. Исходный вопрос. Здесь две фазы показаны на одной числовой цепочке 12→14→17→14. Семантика проверяется по CUBRID/CCI и отдельному native-запуску, а не по MySQL START TRANSACTION.

Измените требование

Почему последнее значение равно 14, а не исходным 12?

Разбор решения

Прибавление двух уже подтверждено в режиме true и является новой сохранённой точкой. Ручная транзакция началась после неё и добавила только три. Её rollback отменил 14 → 17, сохранив прежние 14. Чтобы отменить уже принятую приходную операцию, требуется новая осмысленная корректировка учёта с собственным commit.

Четыре функции управления границей изменений

ФункцияНазначение
cubrid_get_autocommitУзнать, фиксируются ли команды автоматически, и отличить выключенный режим от ошибки.
cubrid_set_autocommitВыбрать границы подтверждения изменений в конкретном соединении.
cubrid_commitПодтвердить все изменения одной законченной бизнес-операции.
cubrid_rollbackОтменить незавершённые изменения после отказа условия или SQL.

Смена режима тоже может завершить работу

Переход false → true нельзя считать безобидной настройкой оформления. PHP Manual предупреждает о подтверждении ожидающей работы; в закреплённом CUBRID/CCI это проверено отдельно: незавершённый остаток 17 после смены режима и последующего rollback остался 17. Поэтому учебные бизнес-программы не используют смену режима вместо явного решения commit/rollback.

СитуацияКак понимать
get вернул falseРежим выключен; это нормальное состояние, не ошибка.
set вернул trueНастройка принята; это не значение режима.
commit/rollback вернули trueПодтверждена соответствующая граница; режим сам по себе не включился.
Режим false → true с ожидающей записьюНеявная фиксация может сохранить работу, которую приложение ещё хотело отменить.

Нельзя заранее угадать значение по умолчанию только по старой странице документации: учитываются версия и конфигурация клиента/брокера. Явно задавайте режим до связанных изменений. После ошибки сперва завершайте текущую работу, затем меняйте настройку; исключение из PHP-кода само по себе не означает успешный rollback.

Связать границу записи с результатом экрана

Сначала определите, что пользователь считает одним действием. Отключите автофиксацию до связанных команд, проверяйте каждый возврат и ожидаемое число строк. Только после принятия всех шагов вызывайте commit; при отказе явно подтверждайте rollback. После завершения читайте результат SQL, а не считайте изменённый PHP-массив доказательством сохранения.

Дальше полезно выбрать, как представить нужные строки экрану или экспорту: массивы, объекты и число строк CUBRID. Сначала запрос выбирает поля, фильтр, порядок и предел, затем PHP оформляет ответ. Общие темы: транзакции и конкурентный доступ, cubrid_affected_rows и диагностика отказов.

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

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

cubrid_get_autocommitУзнать, фиксируются ли команды автоматически, и отличить выключенный режим от ошибки.

PECL CUBRID >= 8.4.0; версии PHP и сервера согласуются отдельно. · cubrid

cubrid_get_autocommit(resource $conn_identifier): bool|null

Узнать, фиксируются ли команды автоматически, и отличить выключенный режим от ошибки.

Базовая запись

cubrid_get_autocommit(resource $conn_identifier): bool|null

Это справочная сигнатура: resource обозначает соединение native cubrid, а не request запроса. Знак | перечисляет варианты возврата и не является выполняемой аннотацией PHP 7.4. Реквизиты соединения получаются отдельно через cubrid_connect.

ПараметрНазначение
conn_identifierОткрытое соединение, состояние которого действительно нужно проверить. Аргумент обязателен; режим одного connection не описывает другой.
РезультатЧто делать
trueАвтоматическая фиксация включена. Это значение режима, не ответ о последнем UPDATE.
falseАвтоматическая фиксация выключена: связанные изменения нужно явно завершить commit/rollback.
nullПолучить режим не удалось. Сохраните диагностику и не начинайте бизнес-операцию с неизвестным состоянием.

Функция появилась в PECL CUBRID 8.4.0. PHP Manual различает старые значения по умолчанию: в 8.4.0 режим был выключен, с 8.4.1 — включён. Общая фраза «по умолчанию выключен», встречающаяся на соседних старых страницах, не подходит как правило для любого профиля. CCI также учитывает настройку брокера CCI_DEFAULT_AUTOCOMMIT. Поэтому программа читает состояние и явно задаёт требуемый режим. В наших проектах соединение намеренно переводится в false до бизнес-кода.

Применение 1: проверить границу складского переноса

$mode = cubrid_get_autocommit($db);
if ($mode === null) {
    throw new RuntimeException('Не удалось узнать режим соединения');
}
if ($mode !== false) {
    throw new RuntimeException('Перенос требует явной транзакции');
}
// Теперь выполнить оба UPDATE, проверить их результаты и вызвать commit.

Выражение if (!$mode) здесь недостаточно: оно смешало бы нормальный false и ошибочный null. Проверка не начинает отдельную транзакцию и ничего не фиксирует. Перенос между ячейками после проверки получает 9 и 7, а не две независимо сохранённые половины. Это контракт входа в операцию: владелец соединения заранее выбрал нужный режим.

Применение 2: объяснить, почему поздний rollback не изменил остаток

$mode = cubrid_get_autocommit($db);
if ($mode === null) { throw new RuntimeException('Режим неизвестен'); }
$label = $mode ? 'Автоматическая фиксация включена' : 'Нужен явный commit';
echo $label;

Диагностический текст пригоден для закрытого административного экрана. Он не должен выдавать реквизиты подключения. В сравнении двух режимов mode=true объясняет сохранённые 14, а mode=false — возможность отменить временные 17. Одного текущего флага всё равно недостаточно, чтобы доказать историю вчерашних операций: нужны известные границы и наблюдаемый результат.

get не показывает наличие незавершённой бизнес-операции, глубину вложенных транзакций или «сколько запросов осталось». Не вызывайте его как проверку жизнеспособности произвольно закрытого ресурса. На странице PHP Manual краткая сигнатура bool соседствует с отдельным null в разделе возвратов; здесь этот отказ сохранён явно. Далее: cubrid_set_autocommit, cubrid_commit и cubrid_rollback.

cubrid_set_autocommitВыбрать границы подтверждения изменений в конкретном соединении.

PECL CUBRID >= 8.4.0; версии PHP и сервера согласуются отдельно. · cubrid

cubrid_set_autocommit(resource $conn_identifier, bool $mode): bool

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

Базовая запись

cubrid_set_autocommit(resource $conn_identifier, bool $mode): bool

Это справочная сигнатура: resource обозначает соединение native cubrid, а не request запроса. Знак | перечисляет варианты возврата и не является выполняемой аннотацией PHP 7.4. Реквизиты соединения получаются отдельно через cubrid_connect.

ПараметрНазначение
conn_identifierОткрытое соединение, которым владеет текущий участок кода. Настройка действует на него, а не на весь сервер.
modetrue включает автоматическую фиксацию, false выключает. В закреплённом строгом PHP-вызове передавайте именно bool без кавычек.
РезультатЧто делать
trueДрайвер принял настройку. Это не само значение режима и не количество сохранённых строк.
falseИзменить настройку не удалось; не выполняйте зависимые записи в предполагаемом режиме.

Доступна с PECL CUBRID 8.4.0. Официальная страница также перечисляет CUBRID_AUTOCOMMIT_FALSE и CUBRID_AUTOCOMMIT_TRUE. В используемом native 11.2.0.0002 эти константы имеют целочисленный тип; PHP 7.4.33 с declare(strict_types=1) не принимает int вместо bool в этом вызове. Поэтому выполняемые примеры используют false/true. Неправильный PHP-тип может привести к TypeError ещё до серверного результата; это отличается от штатного false драйвера.

Применение 1: подготовить одну операцию из двух UPDATE

// Владелец соединения уже завершил прежнюю работу.
if (cubrid_set_autocommit($db, false) !== true) {
    throw new RuntimeException('Ручная граница не установлена');
}
// UPDATE источника; проверить affected_rows === 1.
// UPDATE назначения; проверить affected_rows === 1.
// Всё принято → commit; любой отказ → rollback.

В успешном переносе это позволяет подтвердить 12/4 → 9/7 вместе. В отсутствующем назначении та же настройка позволяет отменить уже выполненное списание. Она не проверяет число коробок или существование ячейки: это обязанности конкретных UPDATE и кода приложения.

Применение 2: отдельное независимое изменение без общего пакета

// Сначала осмысленно завершить прежнюю транзакцию.
if (cubrid_commit($db) !== true) {
    throw new RuntimeException('Прежняя работа не подтверждена');
}
if (cubrid_set_autocommit($db, true) !== true) {
    throw new RuntimeException('Автофиксация не включена');
}
// Теперь выполнить один независимый UPDATE и проверить его результат.

Сценарий прихода двух коробок после такого UPDATE получает 14. Поздний rollback уже не вернёт 12. В фрагменте commit уместен только потому, что владелец действительно решил принять прежнюю работу. Если её нужно отменить, перед сменой режима выбирают rollback; нельзя бездумно вставлять commit в универсальную функцию настройки.

PHP Manual описывает переключение false → true как границу, которая автоматически подтверждает ожидающую работу. Не используйте смену настройки вместо явного, проверенного решения commit/rollback. Особенности закреплённого CCI и наблюдённая граница приведены в разделе режима ниже; проекты переключают true только после отдельного успешного commit. Они не полагаются на неявное подтверждение.

Включать true в finally после неудачного пакета опасно: finally исполняется и при ошибке. Сначала подтвердите отмену/фиксацию, затем верните согласованный режим. Переключение false не создаёт вложенный уровень и не отменяет ранее подтверждённые команды. Не смешивайте управление состоянием одним соединением в нескольких независимых функциях без договора о владельце. Связанные вызовы: cubrid_get_autocommit, cubrid_commit, cubrid_rollback.

cubrid_commitПодтвердить все изменения одной законченной бизнес-операции.

PECL CUBRID >= 8.3.0; версии PHP и сервера согласуются отдельно. · cubrid

cubrid_commit(resource $conn_identifier): bool

Подтвердить все изменения одной законченной бизнес-операции.

Базовая запись

cubrid_commit(resource $conn_identifier): bool

Это справочная сигнатура: resource обозначает соединение native cubrid, а не request запроса. Знак | перечисляет варианты возврата и не является выполняемой аннотацией PHP 7.4. Реквизиты соединения получаются отдельно через cubrid_connect.

ПараметрНазначение
conn_identifierСоединение, в котором выполнена вся подтверждаемая операция. У функции нет параметров имени таблицы, request или списка запросов.
РезультатЧто делать
trueТекущая транзакция успешно подтверждена. Все её принятые изменения получают общую границу.
falseПодтверждение не удалось. Немедленно сохраните диагностику; не сообщайте пользователю безусловный успех.

Доступна с PECL CUBRID 8.3.0. Чтобы сгруппировать несколько команд, до них нужен явный режим false. Вызов commit не включает autocommit сам по себе. Официальная формулировка о завершении серверного соединения при сохранении connection handle относится к границе работы клиента с сервером: PHP-ресурс не следует считать освобождённым. В закреплённом PHP-исходнике функция вызывает cci_end_tran(..., CCI_TRAN_COMMIT); для окончательного отключения есть отдельный disconnect. Наш полный перенос выполняет следующий SELECT на прежнем handle и затем читает результат свежим подключением.

Применение 1: подтвердить обе половины переноса

// Оба UPDATE уже вернули ожидаемое affected_rows === 1.
$committed = cubrid_commit($db);
if ($committed !== true) {
    $code = cubrid_error_code();
    throw new RuntimeException('Перенос не подтверждён: '.$code);
}
echo 'Три коробки перемещены';

В самостоятельной программе сообщение подкрепляется SELECT после commit: А=9, Б=7, всего 16. Ни один промежуточный helper не должен незаметно подтвердить первое списание. Ресурс запроса освобождается отдельно; close_request не является заменой этой общей границы.

Применение 2: принять целиком пакет редактирования

// Приложение уже выполнило все изменения и проверило каждое.
if (!$allEditsAccepted) {
    if (cubrid_rollback($db) !== true) {
        throw new RuntimeException('Отмена не подтверждена');
    }
} elseif (cubrid_commit($db) !== true) {
    throw new RuntimeException('Пакет не подтверждён');
}

Переменная allEditsAccepted — итог проверки каждого выполнения, ограничений и ожидаемого количества строк; её нельзя просто установить true перед циклом и больше не менять. Конфликт артикула иллюстрирует противоположную ветку: второй UPDATE отклонён, поэтому до commit дело не доходит. Для принятого пакета commit должен находиться после последней успешной правки, а не внутри цикла.

Фиксация не доказывает правильность выбранного пользователя, товара, количества или валюты. Она не отправляет письмо и не откатывает внешнюю оплату. Держите транзакцию короткой: не ждите внутри неё ответа пользователя или медленного внешнего API. Если связь оборвалась во время ответа commit, нельзя по одному сетевому отказу уверенно объявить, что запись не состоялась; повторная операция требует отдельной защиты от дублей. Автоматические повторы и распределённые сценарии здесь не испытаны. Дальше: cubrid_rollback, cubrid_affected_rows и общие границы транзакций.

cubrid_rollbackОтменить незавершённые изменения после отказа условия или SQL.

PECL CUBRID >= 8.3.0; версии PHP и сервера согласуются отдельно. · cubrid

cubrid_rollback(resource $conn_identifier): bool

Отменить незавершённые изменения после отказа условия или SQL.

Базовая запись

cubrid_rollback(resource $conn_identifier): bool

Это справочная сигнатура: resource обозначает соединение native cubrid, а не request запроса. Знак | перечисляет варианты возврата и не является выполняемой аннотацией PHP 7.4. Реквизиты соединения получаются отдельно через cubrid_connect.

ПараметрНазначение
conn_identifierСоединение с отменяемой незавершённой работой. Отмена другого соединения не исправит этот перенос.
РезультатЧто делать
trueОперация rollback успешно выполнена. Это не доказательство, что до неё вообще оставались незафиксированные изменения.
falseСервер/клиент не подтвердил отмену. Сохраните диагностику и не продолжайте как после успешной очистки.

Доступна с PECL CUBRID 8.3.0. Отмена действует на текущую транзакцию, а не на один request и не на произвольное прошлое сохранение. Официальное примечание сохраняет действительность PHP connection handle после завершения серверной транзакционной работы; это не вызов disconnect. В исходнике 11.2.0.0002 используется cci_end_tran(..., CCI_TRAN_ROLLBACK). Режим autocommit сам по себе этим вызовом не переключается.

Применение 1: отменить бизнес-отказ без SQL-ошибки

// $credited снят сразу после UPDATE ячейки назначения.
if ($credited !== 1) {
    if (cubrid_rollback($db) !== true) {
        throw new RuntimeException('Не подтверждена отмена переноса');
    }
    echo 'Назначение недоступно: списание отменено';
}

В полном примере SQL вернул допустимое число 0, но это не выполняет обещание «перенести коробки». Поэтому приложение выбирает rollback и возвращает А из 9 в 12. Отсутствие исключения не является основанием продолжить к commit.

Применение 2: отменить предыдущую правку после ошибки ограничения

if (cubrid_execute($editRequest) !== true) {
    $code = cubrid_error_code();
    $hasMessage = cubrid_error_msg() !== '';
    if (cubrid_rollback($db) !== true) {
        throw new RuntimeException('Пакет требует разбора: отмена не подтверждена');
    }
    // В журнале — безопасный код; посетителю — понятный отказ без реквизитов.
}

В пакете SKU/цены ошибка UNIQUE не превращается в потерянную цену: код сохраняет диагностику до вызовов, способных её заменить, и явно отменяет весь пакет. PHP Warning от native-драйвера не обязательно станет Throwable; полный файл отдельно перехватывает его и проверяет возврат. Контекстный фрагмент показывает границу, а самостоятельный проект содержит перехват и освобождение request.

После успешного commit или автоматического подтверждения rollback не возвращает прошлую версию записи: остаток 14 остаётся 14 даже при возврате true. Не используйте обратный UPDATE как универсальную замену отмене и не игнорируйте неудачу rollback. Сначала завершите текущую работу, затем принимайте новую бизнес-операцию. Подробный разбор savepoint, изоляции, блокировок и сетевого восстановления относится к отдельным темам; этот раздел не заявляет их испытанными. Рядом: cubrid_commit, cubrid_get_autocommit и немедленная диагностика.

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

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

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

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

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