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

Дерево руководства
Глава 84 · После подключения CUBRID и параметризованных запросов
CUBRID: перенос остатков, отмена пакета и граница autocommit
Два остатка одной операции, отмена по бизнес-условию и UNIQUE, режим autocommit и подтверждённый результат.
Зачем объединять изменения одной границей
Перенос коробок между ячейками должен либо уменьшить один остаток и увеличить другой, либо не изменить ни один. Сначала разберём это на двух строках: А = 12, Б = 4. Принятый перенос трёх даст 9/7; отказ назначения оставит 12/4. Затем тот же принцип поможет сохранить пакет правок карточки и понять, почему поздний rollback не отменяет автоматически подтверждённое изменение.
Транзакция — несколько обращений к базе с общей границей подтверждения. commit означает «принять накопленную работу», rollback — «отменить ещё не подтверждённую». Autocommit задаёт автоматическую фиксацию отдельных команд. Это разные вещи: выбор режима не равен успешному выполнению заказа. Соединение $db определяет, к какой базе и текущей работе относятся вызовы.
- Исходные строки: А 12 и Б 4, всего 16
- Режим false: UPDATE А − 3; проверить 1 строку → UPDATE Б + 3; проверить 1 строку
- Решение: оба шага приняты → commit; любой отказ → rollback
- Результат принятия 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.
- Две строки одной таблицы: id 1 / А / 12; id 2 / Б / 4
- Autocommit false: UPDATE id 1 вычитает 3 → 9; UPDATE id 2 прибавляет 3 → 7
- Обе команды изменили по одной строке → commit подтверждает весь перенос
- Новый 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: учёт не потеряет коробки.
- А 12 → незавершённое списание 3 → в текущем connection А 9
- UPDATE назначения id 9999 → affected_rows 0: бизнес-условие не выполнено
- Ветка отказа: rollback отменяет предшествующее списание
- Новый 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.
- Карточки: CABLE-166 / 1200; CLAMP-166 / 1800
- Первая правка цены → 9900 внутри незавершённой транзакции
- Вторая правка SKU → CABLE-166 уже занят → execute false и немедленно сохранённая диагностика
- 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 |
| Исходный каталог восстановлен | да |
Полная программа, файлы и архив для запуска
<?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.
- Зафиксированный исходный остаток 12; явное завершение прежней работы
- Режим true → UPDATE +2 уже подтверждён → поздний rollback оставляет 14
- Режим false → UPDATE +3 пока не подтверждён → внутри connection видно 17
- 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 при autocommit | 14 |
| После выключения autocommit и +3 | 17 |
| После 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 | Открытое соединение, которым владеет текущий участок кода. Настройка действует на него, а не на весь сервер. |
| mode | true включает автоматическую фиксацию, 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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


