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

Дерево руководства
Глава 91 · После SQL-параметров и транзакций CUBRID
CUBRID: найти объект по OID, изменить карточку и согласовать двух редакторов
Карточка по OID с NULL, правка и отмена, удаление черновика и настоящий конфликт read/write lock с новым резервом.
Зачем нужен OID и какой результат получает человек
В каталоге уже найден товар CAB-169. Следующая задача — показать его карточку, изменить разрешённые поля, убрать ненужный черновик или согласовать короткую правку двух редакторов. У CUBRID есть объектный интерфейс: OID связывает выбранную строку с экземпляром базы. Начнём с того, чем этот идентификатор отличается от привычных id и SKU, и затем пройдём четыре законченные ситуации.
В результате читатель увидит Кабель с остатком 3 и отсутствующим примечанием; временное название, отменённое до прежнего значения; сохранённый Кабель 2 м с остатком 5; удалённый только черновик; конфликт двух соединений и новый остаток 2 после освобождения блокировки. Получение OID само по себе не является полезным пользовательским выводом — оно служит промежуточным шагом к этим результатам.
| Понятие | На нашем примере | Что оно не делает |
|---|---|---|
| PRIMARY KEY | id=1 однозначно задаёт строку в схеме таблицы. | Не является готовым разрешением доступа для текущего пользователя. |
| Бизнес-ключ | sku=CAB-169 выбирает нужный товар в параметризованном WHERE. | Не обязательно совпадает с внутренним устройством хранения. |
| OID | Внутренняя строка, полученная от текущей строки referable-таблицы. | Не вычисляется из id; не служит публичным секретом или универсальным ключом между базами. |
| Экземпляр CUBRID | Конкретный товар с title=Кабель и qty=3. | Это объект данных в базе, а не созданный оператором new PHP-объект. |
| PHP-массив/объект результата | Прочитанные значения title и qty находятся в памяти программы. | Не обновляются автоматически после чужого commit; не держат lock сами по себе. |
- SKU CAB-169 → SELECT id,title WHERE sku=? с CUBRID_INCLUDE_OID
- Курсор установлен на найденный Кабель → current_oid получает внутреннюю ссылку
- get(title,qty,note) → Кабель / 3 / NULL; для записи сначала lock и повторное чтение
- put разрешённых полей или drop выбранного DRAFT → решение commit / rollback
- Новый SELECT → пользователь видит сохранённую карточку либо восстановленное прежнее состояние
OID похож на внутренний адрес ячейки хранения, а SKU — на артикул на коробке. Аналогия помогает различить назначение, но база управляет транзакциями, блокировками и режимом повторного использования адресов. Не нужно разбирать или конструировать строку OID вручную. По правилам referable-таблиц проекты явно создают только собственную таблицу с DONT_REUSE_OID, не меняя серверную конфигурацию.
Перед этой главой нужны соединение и первая таблица, параметры SQL и commit/rollback. Соседняя глава о схеме объясняет ключи, внешние связи и права. Здесь class означает класс данных CUBRID; это не то же, что класс PHP.
Ни один проект не получает сырой OID из произвольного URL. Сначала приложение выбирает карточку в допустимой области данных, затем работает с её внутренним идентификатором. Реальное приложение добавляет проверку пользователя, владельца и действия: OWASP описывает эту границу отдельно. Непредсказуемый или скрытый идентификатор не заменяет такие проверки.
Прочитать одну выбранную карточку и различить NULL и отсутствие объекта
Административная утилита уже нашла товар по артикулу CAB-169 и должна показать выбранные поля именно этой карточки. OID позволяет обратиться к найденному экземпляру базы. На экране нужны Кабель, остаток 3 и понятное «примечание не задано», а не пустой непонятный вывод false.
- SKU CAB-169 → параметр WHERE sku=? → одна строка referable-таблицы
- Курсор на строке → current_oid → внутренний адрес этого экземпляра
- get(title) → Кабель; get([sku,title,qty,note]) → четыре поля
- SQL NULL → false у одиночного get, но null внутри карты; Хомут qty=0 остаётся числом наличия
Самый простой вызов и его смысл
$title = cubrid_get($db, $oid, 'title');
if ($title === false && cubrid_error_code() !== 0) {
throw new RuntimeException('Не прочитан товар');
}
echo $title; // Кабель: для уже выбранного объекта из проекта ниже.Первый аргумент — соединение, второй — полученный от базы OID, третий — имя атрибута. Строка title означает название поля, а не весь SQL. Результат здесь строка Кабель. Полная программа сначала создаёт referable-таблицу, выбирает строку по параметру и только затем вызывает get. Если нужен nullable-атрибут, карта get с массивом имён яснее показывает NULL.
Видимый итог самостоятельного проекта
| Наблюдение | Результат |
|---|---|
| Товар | Кабель |
| Артикул | CAB-169 |
| Остаток | 3 |
| Примечание карточки | не задано (SQL NULL) |
| Один NULL-атрибут вернул false | да |
| Ошибка этого чтения | 0 |
| Примечание Хомута | пустая строка |
| Остаток Хомута | 0 |
| Все атрибуты одного объекта | 7 |
| Объект принадлежит учебному каталогу | да |
Полный проект, файлы и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c169Config();$db=c169Connect();$runtime=c169Runtime($db);
function pickOid169($db,string $table,string $sku): string {
$request=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE sku=?',CUBRID_INCLUDE_OID);
if($request===false){throw new RuntimeException('Не подготовлен выбор карточки.');}
try {
if(!cubrid_bind($request,1,$sku,'string')||!cubrid_execute($request)){
throw new RuntimeException('Не выполнен выбор карточки.');
}
if(cubrid_num_rows($request)!==1){throw new RuntimeException('Ожидалась одна карточка с данным SKU.');}
if(cubrid_move_cursor($request,1,CUBRID_CURSOR_FIRST)!==true){
throw new RuntimeException('Курсор не поставлен на найденную карточку.');
}
$oid=cubrid_current_oid($request);
if(!is_string($oid)||$oid===''){throw new RuntimeException('Нет OID выбранной карточки.');}
return $oid;
} finally {c169Release($request);}
}
$name=c169CreateTable($db,$owned,'oidcard',[
['id'=>1,'sku'=>'CAB-169','title'=>'Кабель','category'=>'electric','price_cents'=>12900,'qty'=>3,'note'=>null],
['id'=>2,'sku'=>'CLAMP-169','title'=>'Хомут','category'=>'electric','price_cents'=>250,'qty'=>0,'note'=>'']
],true);$table=c169Qualified($name);
$oid=pickOid169($db,$table,'CAB-169');
$exists=cubrid_is_instance($db,$oid);
if($exists!==1){throw new RuntimeException('Выбранная карточка недоступна.');}
$class=cubrid_get_class_name($db,$oid);
if(!is_string($class)){throw new RuntimeException('Не прочитан класс объекта.');}
$title=cubrid_get($db,$oid,'title');
$card=cubrid_get($db,$oid,['sku','title','qty','note']);
$all=cubrid_get($db,$oid);
$nullValue=c169Capture(static function()use($db,$oid){return cubrid_get($db,$oid,'note');});
$second=pickOid169($db,$table,'CLAMP-169');
$empty=cubrid_get($db,$second,'note');$zero=cubrid_get($db,$second,'qty');
if(!is_array($card)||!is_array($all)){throw new RuntimeException('Не получены атрибуты карточки.');}
c169Commit($db);
$classMatches=in_array(strtolower($class),[$name,'dba.'.$name],true);
$rows=[['Товар',$title],['Артикул',$card['sku']],['Остаток',(int)$card['qty']],
['Примечание карточки',$card['note']===null?'не задано (SQL NULL)':'задано'],
['Один NULL-атрибут вернул false',$nullValue['value']===false],['Ошибка этого чтения',$nullValue['code']],
['Примечание Хомута',$empty===''?'пустая строка':'другое'],['Остаток Хомута',$zero],
['Все атрибуты одного объекта',count($all)],['Объект принадлежит учебному каталогу',$classMatches]];
$checks=['instance_exists'=>$exists===1,'title'=>$title==='Кабель',
'selected_card'=>$card===['sku'=>'CAB-169','title'=>'Кабель','qty'=>'3','note'=>null],
'all_seven'=>count($all)===7&&$all['id']==='1'&&$all['price_cents']==='12900',
'null_not_error'=>$nullValue['value']===false&&$nullValue['code']===0&&!$nullValue['warning'],
'empty_and_zero'=>$empty===''&&$zero==='0','class_matches'=>$classMatches];
$observations=['selected_card'=>$card,'all_attributes'=>$all,'single_null'=>$nullValue,
'second_note'=>$empty,'second_qty'=>$zero,'class_name'=>$class,'instance_state'=>$exists,
'oid_received'=>is_string($oid)&&$oid!=='','referable_table'=>true];
} finally {
if (function_exists('c169LockChildStopped') && !c169LockChildStopped()) {
throw new RuntimeException('Дочерний процесс ещё работает; очистка таблицы запрещена до его остановки.');
}
if (is_resource($db)) {try {$removed=c169Cleanup($db,$owned);} finally {c169Close($db);}}
}
c169Report('cub169-oid-card','Прочитать одну выбранную карточку и различить NULL и отсутствие объекта',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Товар — Кабель
Артикул — CAB-169
Остаток — 3
Примечание карточки — не задано (SQL NULL)
Один NULL-атрибут вернул false — да
Ошибка этого чтения — 0
Примечание Хомута — пустая строка
Остаток Хомута — 0
Все атрибуты одного объекта — 7
Объект принадлежит учебному каталогу — да
Распакуйте полный архив и выполните example.php по README.txt в отдельной CUBRID-лаборатории. Проект создаёт собственную referable-таблицу и удаляет её в finally. В result.html видны карточки и исходы операции, result.json сохраняет наблюдения, metrics.json — проверяемые условия. Реквизиты и сырой OID не превращаются в публичное разрешение на изменение.
Открыть полезный результат · Открыть наблюдения JSON.
Границы решения и типичные ошибки
Получить все атрибуты одного объекта не означает скачать всю таблицу. Но ненужные поля, особенно большие данные, всё равно не следует читать для каждого экрана. OID не заменяет PRIMARY KEY, SKU, SQL-фильтр или проверку прав. is_instance проверяет состояние в момент вызова; другой клиент может удалить объект позже. В публичном отчёте сырой OID не служит ссылкой для изменения.
Практический вопрос: Почему NULL, строка NULL и пустой вывод не удаётся различить простой проверкой? Наш CUBRID-проект сопоставляет одиночный false без ошибки, null в карте, пустую строку и строку нуля. MySQLi-вызовы из обсуждения не выдаются за API CUBRID.
Изменить требование
Нужны только название и цена. Как изменить чтение, не загружая все семь атрибутов?
Разбор решения
Передать ['title','price_cents'] в get; результат станет ассоциативным массивом двух полей. Число копеек для отображения преобразовать явно. Поиск нужного товара по SKU и проверку допустимости доступа сохранить.
Предпросмотреть правку карточки, отменить её и сохранить выбранные поля
В утилите редактора нужна короткая управляемая правка уже найденной карточки. Сначала одна пробная правка отменяется, затем три разрешённых поля сохраняются вместе. Читатель видит не только true: SQL перечитывает конкретное название, остаток, цену и неизменённый соседний товар.
- CAB-169 → lock_write → title Временное название → видим внутри своей транзакции
- ROLLBACK → title снова Кабель, qty 3, старое примечание
- Новая операция → put карты title/qty/note → COMMIT
- SQL readback → Кабель 2 м / 5 / 12900 копеек / NULL; Хомут не изменён
Самый простой вызов и его смысл
if (!cubrid_put($db, $oid, 'title', 'Кабель 2 м')) {
throw new RuntimeException('Название не изменено');
}
// Для сохранения всей полезной операции нужен отдельный cubrid_commit($db).Четыре аргумента означают одну пару имя/значение. Три аргумента с ассоциативным массивом меняют несколько атрибутов: cubrid_put($db,$oid,[...]). Функция возвращает bool, а не новый объект и не число изменённых строк. Успех вызова при выключенном autocommit ещё не является подтверждением всей транзакции.
Видимый итог самостоятельного проекта
| Наблюдение | Результат |
|---|---|
| Временная правка | Временное название |
| После отмены | Кабель |
| Сохранённый товар | Кабель 2 м |
| Новый остаток | 5 |
| Цена осталась, копейки | 12900 |
| Примечание очищено до NULL | да |
| Соседняя карточка | Хомут / 0 |
Полный проект, файлы и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c169Config();$db=c169Connect();$runtime=c169Runtime($db);
function pickOid169($db,string $table,string $sku): string {
$request=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE sku=?',CUBRID_INCLUDE_OID);
if($request===false){throw new RuntimeException('Не подготовлен выбор карточки.');}
try {
if(!cubrid_bind($request,1,$sku,'string')||!cubrid_execute($request)){
throw new RuntimeException('Не выполнен выбор карточки.');
}
if(cubrid_num_rows($request)!==1){throw new RuntimeException('Ожидалась одна карточка с данным SKU.');}
if(cubrid_move_cursor($request,1,CUBRID_CURSOR_FIRST)!==true){
throw new RuntimeException('Курсор не поставлен на найденную карточку.');
}
$oid=cubrid_current_oid($request);
if(!is_string($oid)||$oid===''){throw new RuntimeException('Нет OID выбранной карточки.');}
return $oid;
} finally {c169Release($request);}
}
$name=c169CreateTable($db,$owned,'oidedit',[
['id'=>1,'sku'=>'CAB-169','title'=>'Кабель','category'=>'electric','price_cents'=>12900,'qty'=>3,'note'=>'без упаковки'],
['id'=>2,'sku'=>'CLAMP-169','title'=>'Хомут','category'=>'electric','price_cents'=>250,'qty'=>0]
],true);$table=c169Qualified($name);
$oid=pickOid169($db,$table,'CAB-169');
if(!cubrid_lock_write($db,$oid)){throw new RuntimeException('Карточка не захвачена для короткой правки.');}
if(!cubrid_put($db,$oid,'title','Временное название')){throw new RuntimeException('Предпросмотр не записан.');}
$preview=c169Read($db,'SELECT title,qty,note FROM '.$table.' WHERE sku=?',['CAB-169']);
c169Rollback($db);
$afterCancel=c169Read($db,'SELECT title,qty,note FROM '.$table.' WHERE sku=?',['CAB-169']);c169Commit($db);
$oid=pickOid169($db,$table,'CAB-169');
if(!cubrid_lock_write($db,$oid)){throw new RuntimeException('Карточка занята.');}
// These keys come from the application, never from an unfiltered POST array.
$changes=['title'=>'Кабель 2 м','qty'=>'5','note'=>null];
if(!cubrid_put($db,$oid,$changes)){c169Rollback($db);throw new RuntimeException('Изменения не приняты.');}
c169Commit($db);
$saved=c169Read($db,'SELECT sku,title,qty,price_cents,note FROM '.$table.' WHERE sku=?',['CAB-169']);
$other=c169Read($db,'SELECT title,qty FROM '.$table.' WHERE sku=?',['CLAMP-169']);c169Commit($db);
$rows=[['Временная правка',$preview[0]['title']],['После отмены',$afterCancel[0]['title']],
['Сохранённый товар',$saved[0]['title']],['Новый остаток',(int)$saved[0]['qty']],
['Цена осталась, копейки',(int)$saved[0]['price_cents']],['Примечание очищено до NULL',$saved[0]['note']===null],
['Соседняя карточка',$other[0]['title'].' / '.$other[0]['qty']]];
$checks=['preview_visible'=>$preview[0]['title']==='Временное название',
'rollback_restored'=>$afterCancel===[['title'=>'Кабель','qty'=>'3','note'=>'без упаковки']],
'map_committed'=>$saved===[['sku'=>'CAB-169','title'=>'Кабель 2 м','qty'=>'5','price_cents'=>'12900','note'=>null]],
'other_unchanged'=>$other===[['title'=>'Хомут','qty'=>'0']]];
$observations=['preview'=>$preview,'after_rollback'=>$afterCancel,'saved'=>$saved,'other'=>$other,
'allowed_attributes'=>['title','qty','note'],'referable_table'=>true];
} finally {
if (function_exists('c169LockChildStopped') && !c169LockChildStopped()) {
throw new RuntimeException('Дочерний процесс ещё работает; очистка таблицы запрещена до его остановки.');
}
if (is_resource($db)) {try {$removed=c169Cleanup($db,$owned);} finally {c169Close($db);}}
}
c169Report('cub169-oid-edit','Предпросмотреть правку карточки, отменить её и сохранить выбранные поля',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Временная правка — Временное название
После отмены — Кабель
Сохранённый товар — Кабель 2 м
Новый остаток — 5
Цена осталась, копейки — 12900
Примечание очищено до NULL — да
Соседняя карточка — Хомут / 0
Распакуйте полный архив и выполните example.php по README.txt в отдельной CUBRID-лаборатории. Проект создаёт собственную referable-таблицу и удаляет её в finally. В result.html видны карточки и исходы операции, result.json сохраняет наблюдения, metrics.json — проверяемые условия. Реквизиты и сырой OID не превращаются в публичное разрешение на изменение.
Открыть полезный результат · Открыть наблюдения JSON.
Границы решения и типичные ошибки
Внутренние имена полей задаёт приложение по разрешённому списку. Нельзя передать в put весь пользовательский POST: это позволит менять лишние атрибуты. OID адресует объект, но не содержит условие вроде qty>0 или version=староеЗначение. Для атомарной проверки таких условий часто понятнее параметризованный UPDATE с WHERE. Не держите транзакцию открытой, пока человек заполняет форму.
Изменить требование
Редактору разрешено менять только название. Что исключить из карты изменений и как убедиться, что остаток не изменился?
Разбор решения
Оставить ключ title, не просто скрыть поле qty в HTML. После commit перечитать title и qty по SKU; ожидаемый остаток останется 3. Проверку полномочий конкретного редактора выполняют на сервере до изменения.
Удалить выбранный черновик с проверкой статуса и возможностью отмены
В каталоге есть пробная и опубликованная карточки. Удалять разрешено только выбранный черновик. Программа сначала показывает обратимость неподтверждённого удаления, затем заново проверяет статус и подтверждает удаление именно черновика. Опубликованный кабель остаётся видимым.
- DRAFT-169 → OID → lock_write → перечитать note=DRAFT
- drop экземпляра → его нет в своей транзакции → ROLLBACK → черновик возвращён
- Новая проверенная операция → drop → COMMIT → экземпляр отсутствует
- SQL каталога → LIVE-169 / Опубликованный кабель / PUBLISHED
Самый простой вызов и его смысл
if (!cubrid_drop($db, $oid)) {
throw new RuntimeException('Объект не удалён');
}
// Здесь удалён экземпляр, а не таблица. До commit операция остаётся частью транзакции.drop получает соединение и OID одного экземпляра. Это не SQL DROP TABLE. Статус и права функция сама не угадывает. Сначала приложение находит допустимую карточку, получает write lock и читает текущий статус; затем удаляет и явно выбирает commit или rollback.
Видимый итог самостоятельного проекта
| Наблюдение | Результат |
|---|---|
| Выбранный черновик | Пробная карточка |
| После drop до rollback | 0 |
| После rollback | 1 |
| Вернувшийся статус | DRAFT |
| После подтверждённого удаления | 0 |
| Осталась карточка | Опубликованный кабель |
| Её статус | PUBLISHED |
| Строк осталось | 1 |
Полный проект, файлы и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c169Config();$db=c169Connect();$runtime=c169Runtime($db);
function pickOid169($db,string $table,string $sku): string {
$request=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE sku=?',CUBRID_INCLUDE_OID);
if($request===false){throw new RuntimeException('Не подготовлен выбор карточки.');}
try {
if(!cubrid_bind($request,1,$sku,'string')||!cubrid_execute($request)){
throw new RuntimeException('Не выполнен выбор карточки.');
}
if(cubrid_num_rows($request)!==1){throw new RuntimeException('Ожидалась одна карточка с данным SKU.');}
if(cubrid_move_cursor($request,1,CUBRID_CURSOR_FIRST)!==true){
throw new RuntimeException('Курсор не поставлен на найденную карточку.');
}
$oid=cubrid_current_oid($request);
if(!is_string($oid)||$oid===''){throw new RuntimeException('Нет OID выбранной карточки.');}
return $oid;
} finally {c169Release($request);}
}
$name=c169CreateTable($db,$owned,'oiddrop',[
['id'=>1,'sku'=>'DRAFT-169','title'=>'Пробная карточка','category'=>'electric','price_cents'=>100,'qty'=>0,'note'=>'DRAFT'],
['id'=>2,'sku'=>'LIVE-169','title'=>'Опубликованный кабель','category'=>'electric','price_cents'=>12900,'qty'=>3,'note'=>'PUBLISHED']
],true);$table=c169Qualified($name);
$oid=pickOid169($db,$table,'DRAFT-169');
if(!cubrid_lock_write($db,$oid)){throw new RuntimeException('Не захвачен выбранный черновик.');}
$state=cubrid_get($db,$oid,['title','note']);
if(!is_array($state)||$state['note']!=='DRAFT'){throw new RuntimeException('Удалять можно только выбранный черновик.');}
if(!cubrid_drop($db,$oid)){throw new RuntimeException('Удаление черновика не выполнено.');}
$during=cubrid_is_instance($db,$oid);
c169Rollback($db);
$restored=cubrid_is_instance($db,$oid);
$restoredRows=c169Read($db,'SELECT title,note FROM '.$table.' WHERE sku=?',['DRAFT-169']);c169Commit($db);
// A new decision: reacquire the current object and recheck its status under a write lock.
$oid=pickOid169($db,$table,'DRAFT-169');
if(!cubrid_lock_write($db,$oid)){throw new RuntimeException('Черновик занят.');}
$state=cubrid_get($db,$oid,['title','note']);
if(!is_array($state)||$state['note']!=='DRAFT'){throw new RuntimeException('Статус изменился; удаление запрещено.');}
if(!cubrid_drop($db,$oid)){c169Rollback($db);throw new RuntimeException('Удаление не принято.');}
c169Commit($db);
$afterCommit=cubrid_is_instance($db,$oid);
$remaining=c169Read($db,'SELECT sku,title,note FROM '.$table.' ORDER BY id');c169Commit($db);
$rows=[['Выбранный черновик',$restoredRows[0]['title']],['После drop до rollback',$during],
['После rollback',$restored],['Вернувшийся статус',$restoredRows[0]['note']],
['После подтверждённого удаления',$afterCommit],['Осталась карточка',$remaining[0]['title']],
['Её статус',$remaining[0]['note']],['Строк осталось',count($remaining)]];
$checks=['temporary_drop'=>$during===0,'rollback_recovered'=>$restored===1&&$restoredRows===[['title'=>'Пробная карточка','note'=>'DRAFT']],
'committed_drop'=>$afterCommit===0,'only_published_remains'=>$remaining===[['sku'=>'LIVE-169','title'=>'Опубликованный кабель','note'=>'PUBLISHED']]];
$observations=['before_delete'=>$state,'instance_after_drop'=>$during,'instance_after_rollback'=>$restored,
'after_rollback'=>$restoredRows,'instance_after_commit'=>$afterCommit,'remaining'=>$remaining,
'raw_oid_published'=>false,'referable_table'=>true];
} finally {
if (function_exists('c169LockChildStopped') && !c169LockChildStopped()) {
throw new RuntimeException('Дочерний процесс ещё работает; очистка таблицы запрещена до его остановки.');
}
if (is_resource($db)) {try {$removed=c169Cleanup($db,$owned);} finally {c169Close($db);}}
}
c169Report('cub169-oid-delete','Удалить выбранный черновик с проверкой статуса и возможностью отмены',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Выбранный черновик — Пробная карточка
После drop до rollback — 0
После rollback — 1
Вернувшийся статус — DRAFT
После подтверждённого удаления — 0
Осталась карточка — Опубликованный кабель
Её статус — PUBLISHED
Строк осталось — 1
Распакуйте полный архив и выполните example.php по README.txt в отдельной CUBRID-лаборатории. Проект создаёт собственную referable-таблицу и удаляет её в finally. В result.html видны карточки и исходы операции, result.json сохраняет наблюдения, metrics.json — проверяемые условия. Реквизиты и сырой OID не превращаются в публичное разрешение на изменение.
Открыть полезный результат · Открыть наблюдения JSON.
Границы решения и типичные ошибки
Проверка is_instance за минуту до удаления не защищает от изменения статуса другим клиентом. В показанном коротком сценарии проверка состояния и drop идут под одним write lock. При связях и триггерах возможны дополнительные ограничения или действия базы; ошибка удаления не равна отсутствию объекта. Для веб-интерфейса перед каждым запросом проверяют доступ пользователя к конкретной карточке.
Практический вопрос: Достаточно ли скрыть идентификатор, чтобы пользователь не смог изменить чужую запись? Разделяем OID, SKU и право на действие; идентификатор из базы не заменяет проверку конкретного объекта. Решение сверено с OWASP, а не с догадкой о непредсказуемости номера.
Изменить требование
Что делать, если после получения lock_write статус оказался PUBLISHED?
Разбор решения
Не вызывать drop. Завершить свою транзакцию rollback и показать отказ по бизнес-правилу. Нельзя считать найденный раньше OID разрешением удалить объект с любым новым статусом.
Два редактора: совместное чтение, отказ записи и новое изменение после освобождения
Два коротких серверных действия обращаются к одному товару. Пока первый участник держит read lock, второй тоже читает, но не получает write lock. После завершения первого второй начинает новую операцию, читает актуальный остаток под write lock и сохраняет 2 вместо 3. Это демонстрация координации с реальным вторым PHP-процессом.
- Первый процесс: OID Кабеля → lock_read → остаток 3, транзакция открыта
- Второй: lock_read → Кабель / 3 → rollback собственной транзакции
- Второй: lock_write → конечное ожидание → отказ; первый получает команду rollback и завершается
- Новая операция: lock_write → свежий get qty=3 → put qty=2 → commit → SQL показывает 2
Самый простой вызов и его смысл
if (!cubrid_lock_write($db, $oid)) {
throw new RuntimeException('Карточка занята или недоступна');
}
$card = cubrid_get($db, $oid, ['title', 'qty']);
// Проверить свежее значение, выполнить put и закончить транзакцию.Read lock и write lock — режимы экземпляра базы, не блокировка PHP-массива и не запрет редактировать HTML. Lock действует в контексте соединения и его транзакции. Отдельный лимит ожидания задаётся у соединения; в современном CCI число 1000 означает миллисекунды. После отказа нужно сохранить диагностику до следующего вызова и явно закончить неудачную операцию.
| Шаг | Соединение владельца | Соединение редактора |
|---|---|---|
| 1. Карточка Кабель / 3 | Получает read lock и сообщает locked. | Ждёт подтверждённого сообщения; не угадывает готовность по sleep. |
| 2. Совместное чтение | Продолжает держать read lock. | Тоже получает read lock, читает 3, заканчивает собственную транзакцию. |
| 3. Попытка записи | Ещё не закончил свою транзакцию. | Запрашивает write lock; конечное ожидание приводит к -75, затем rollback. |
| 4. Освобождение | По команде rollback заканчивает транзакцию и процесс. | Проверяет подтверждение завершения владельца. |
| 5. Новый резерв | Собственных блокировок больше нет. | Получает write lock, заново читает 3, записывает 2, commit и SQL-readback. |
Видимый итог самостоятельного проекта
| Наблюдение | Результат |
|---|---|
| Карточка второго читателя | Кабель / 3 |
| Чтение совместимо | да |
| Запись при чужом read lock отклонена | да |
| Код отказа | -75 |
| Владелец чтения завершён | да |
| Свежее значение перед новой правкой | 3 |
| Сохранённый товар | Кабель |
| Остаток после резерва | 2 |
Полный проект, файлы и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
require __DIR__.'/lock-lab.php';
$job=null;$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c169Config();$job=c169LockStartEarly();$db=c169Connect();$runtime=c169Runtime($db);
function pickOid169($db,string $table,string $sku): string {
$request=cubrid_prepare($db,'SELECT id,title FROM '.$table.' WHERE sku=?',CUBRID_INCLUDE_OID);
if($request===false){throw new RuntimeException('Не подготовлен выбор карточки.');}
try {
if(!cubrid_bind($request,1,$sku,'string')||!cubrid_execute($request)){
throw new RuntimeException('Не выполнен выбор карточки.');
}
if(cubrid_num_rows($request)!==1){throw new RuntimeException('Ожидалась одна карточка с данным SKU.');}
if(cubrid_move_cursor($request,1,CUBRID_CURSOR_FIRST)!==true){
throw new RuntimeException('Курсор не поставлен на найденную карточку.');
}
$oid=cubrid_current_oid($request);
if(!is_string($oid)||$oid===''){throw new RuntimeException('Нет OID выбранной карточки.');}
return $oid;
} finally {c169Release($request);}
}
$name=c169CreateTable($db,$owned,'oidlock',[
['id'=>1,'sku'=>'CAB-169','title'=>'Кабель','category'=>'electric','price_cents'=>12900,'qty'=>3]
],true);$table=c169Qualified($name);
$oid=pickOid169($db,$table,'CAB-169');c169Commit($db);
$initial=cubrid_get_db_parameter($db);$proof=null;$attempt=null;
if(!is_array($initial)){throw new RuntimeException('Нет исходных параметров.');}
try {
if(!cubrid_set_db_parameter($db,CUBRID_PARAM_LOCK_TIMEOUT,1000)){throw new RuntimeException('Не ограничено ожидание.');}
c169LockBegin($job,$name,$owned);
$readLock=cubrid_lock_read($db,$oid);
if($readLock!==true){throw new RuntimeException('Второе чтение не получило lock.');}
$readCard=cubrid_get($db,$oid,['title','qty']);c169Rollback($db);
$started=microtime(true);
$attempt=c169Capture(static function()use($db,$oid){return cubrid_lock_write($db,$oid);});
$elapsedMs=(microtime(true)-$started)*1000;
if($attempt['value']!==false||$attempt['code']!==-75||$attempt['facility']!==1||!$attempt['warning']){
throw new RuntimeException('Ожидался ограниченный конфликт записи с владельцем чтения.');
}
c169Rollback($db);
} finally {
c169Rollback($db);
if($job!==null&&!c169LockChildStopped()){$proof=c169LockStop($job);}
if(!cubrid_set_db_parameter($db,CUBRID_PARAM_LOCK_TIMEOUT,(int)$initial['PARAM_LOCK_TIMEOUT'])){
throw new RuntimeException('Исходный предел ожидания не восстановлен.');
}
}
// This new short operation reads the value after obtaining its write lock.
if(!cubrid_lock_write($db,$oid)){throw new RuntimeException('После завершения владельца write lock не получен.');}
$fresh=cubrid_get($db,$oid,['title','qty']);
if(!is_array($fresh)||(int)$fresh['qty']<1){c169Rollback($db);throw new RuntimeException('Нет остатка для резерва.');}
$newQty=(string)((int)$fresh['qty']-1);
if(!cubrid_put($db,$oid,'qty',$newQty)){c169Rollback($db);throw new RuntimeException('Новый остаток не записан.');}
c169Commit($db);
$saved=c169Read($db,'SELECT title,qty FROM '.$table.' WHERE sku=?',['CAB-169']);c169Commit($db);
$params=cubrid_get_db_parameter($db);
$rows=[['Карточка второго читателя',$readCard['title'].' / '.$readCard['qty']],['Чтение совместимо',$readLock],
['Запись при чужом read lock отклонена',$attempt['value']===false],['Код отказа',$attempt['code']],
['Владелец чтения завершён',$proof['stopped']&&$proof['exit_code']===0],['Свежее значение перед новой правкой',(int)$fresh['qty']],
['Сохранённый товар',$saved[0]['title']],['Остаток после резерва',(int)$saved[0]['qty']]];
$checks=['shared_read'=>$readLock===true&&$readCard===['title'=>'Кабель','qty'=>'3'],
'write_conflict'=>$attempt['value']===false&&$attempt['code']===-75&&$attempt['facility']===1&&$attempt['message_present'],
'owner_released'=>$proof['completed']['rolled_back']===true&&$proof['stopped']&&$proof['exit_code']===0&&!$proof['termination_by_parent'],
'fresh_then_write'=>$fresh===['title'=>'Кабель','qty'=>'3']&&$saved===[['title'=>'Кабель','qty'=>'2']],
'lock_setting_restored'=>$params['PARAM_LOCK_TIMEOUT']===$initial['PARAM_LOCK_TIMEOUT']];
$observations=['read_card'=>$readCard,'write_diagnostic'=>$attempt,'elapsed_ms_observed'=>$elapsedMs,
'fresh_before_put'=>$fresh,'saved'=>$saved,'lock_milliseconds'=>1000,
'lock_child'=>array_intersect_key($proof,array_flip(['started','stopped','exit_code','termination_by_parent',
'deadline_seconds','handshake','completed','launch_handshake'])),
'referable_table'=>true];
} finally {
if($job!==null&&!c169LockChildStopped()){c169LockStop($job);}
if (function_exists('c169LockChildStopped') && !c169LockChildStopped()) {
throw new RuntimeException('Дочерний процесс ещё работает; очистка таблицы запрещена до его остановки.');
}
if (is_resource($db)) {try {$removed=c169Cleanup($db,$owned);} finally {c169Close($db);}}
}
c169Report('cub169-oid-lock','Два редактора: совместное чтение, отказ записи и новое изменение после освобождения',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Карточка второго читателя — Кабель / 3
Чтение совместимо — да
Запись при чужом read lock отклонена — да
Код отказа — -75
Владелец чтения завершён — да
Свежее значение перед новой правкой — 3
Сохранённый товар — Кабель
Остаток после резерва — 2
Распакуйте полный архив и выполните example.php по README.txt в отдельной CUBRID-лаборатории. Проект создаёт собственную referable-таблицу и удаляет её в finally. В result.html видны карточки и исходы операции, result.json сохраняет наблюдения, metrics.json — проверяемые условия. Реквизиты и сырой OID не превращаются в публичное разрешение на изменение. Один короткий PHP-процесс запускается до первого подключения родителя; он наследует изолированный namespace и держит настоящий read lock. Обмен сообщениями ограничен сроком, остановка подтверждается до удаления таблицы. Полная команда запуска остаётся в закрытом process receipt.
Открыть полезный результат · Открыть наблюдения JSON.
Границы решения и типичные ошибки
Получить write lock и затем записать старую форму без перечитывания — не защита от потерянного обновления. Веб-пользователь может думать минуты: нельзя всё это время держать соединение и lock. Для таких форм обычно нужен version-столбец и условный UPDATE; здесь показан короткий серверный участок. Тайм-аут и deadlock различаются, а длительность не обязана равняться ровно 1000 мс.
Практический вопрос: Почему блокировка всё ещё допускает потерю изменения, если записывается старое значение? После получения write lock читаем новый qty, затем вычисляем резерв. Поведение Oracle/JPA не переносится: две CUBRID-сессии и их реальные исходы проверяются отдельно.
Изменить требование
Почему новое значение вычислено из fresh, а не из readCard, прочитанной до конфликта?
Разбор решения
Между этими действиями закончилась транзакция и мог поработать другой клиент. Новая операция сначала захватывает write lock, читает актуальное значение и только затем вычисляет изменение. Старое PHP-значение не обновляется автоматически.
Восемь функций работы с выбранным экземпляром
| Функция | Назначение |
|---|---|
| cubrid_current_oid | Получить внутренний идентификатор экземпляра, на который сейчас установлен курсор результата. |
| cubrid_get | Прочитать одно, несколько или все свойства одного выбранного экземпляра базы. |
| cubrid_get_class_name | Узнать, к какому классу базы относится найденный экземпляр. |
| cubrid_is_instance | Различить существующий экземпляр, его отсутствие и ошибку проверки. |
| cubrid_put | Изменить одно поле или явную карту полей выбранного экземпляра. |
| cubrid_drop | Удалить один выбранный экземпляр с дальнейшим подтверждением или отменой транзакции. |
| cubrid_lock_read | Получить разделяемую блокировку чтения конкретного экземпляра в короткой операции. |
| cubrid_lock_write | Получить блокировку изменения, затем перечитать данные и выполнить согласованную правку. |
Выбирать способ изменения по задаче, а не по наличию OID
Начинайте с нужного человеку действия: какую карточку открыть, что разрешено изменить, что должно остаться при отмене и какой результат подтвердить чтением. Выбирайте запись по бизнес-условию в SQL, используйте OID только в подходящем объектном интерфейсе, проверяйте отказ каждой операции и явно заканчивайте транзакцию.
Для обычных списков, массовых изменений и атомарного WHERE вернитесь к параметризованному SQL. Для повторной формы после паузы пользователя продумайте версию записи, а не длительную блокировку. Пределы ожидания помогут отделить занятость экземпляра от общего времени запроса; метаданные схемы — от бизнес-правил и полномочий.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
cubrid_current_oidПолучить внутренний идентификатор экземпляра, на который сейчас установлен курсор результата.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_current_oid(resource $req_identifier): string|falseПолучить внутренний идентификатор экземпляра, на который сейчас установлен курсор результата.
cubrid_current_oid(resource $req_identifier): string|falseФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| req_identifier | Живой результат обновляемого SELECT с CUBRID_INCLUDE_OID. Курсор должен указывать на нужную строку; один и тот же request на разных строках даст разные OID. |
Результат и отказ
Строка OID либо false при неуспехе. Это не число строк и не массив полей. Проверяйте строгий false; диагностику сохраняйте до следующего вызова драйвера. Наличие обычного SELECT-результата ещё не гарантирует, что он предоставляет OID.
Обновляемая выборка позволяет сопоставить строку результата конкретному экземпляру. Для начала используйте одну referable-таблицу, явные поля id/title и WHERE по уникальному SKU. Сводка SUM или произвольная комбинация JOIN не должна автоматически считаться такой выборкой. На современном сервере наши таблицы создаются с DONT_REUSE_OID; режим по умолчанию REUSE_OID не подходит для этой работы с внешними ссылками на экземпляр. Настройка относится к собственной таблице, а не ко всей базе.
Применение 1: открыть карточку найденного товара
$request = cubrid_prepare($db,
'SELECT id,title FROM products WHERE sku=?', CUBRID_INCLUDE_OID);
if ($request === false) { throw new RuntimeException('Не создан поиск'); }
try {
if (!cubrid_bind($request, 1, 'CAB-169', 'string') || !cubrid_execute($request)) {
throw new RuntimeException('Поиск не выполнен');
}
if (cubrid_num_rows($request) !== 1 ||
cubrid_move_cursor($request, 1, CUBRID_CURSOR_FIRST) !== true) {
throw new RuntimeException('Карточка не найдена однозначно');
}
$oid = cubrid_current_oid($request);
if ($oid === false) { throw new RuntimeException('Нет OID карточки'); }
// Дальше cubrid_get читает поля найденного товара.
} finally { cubrid_close_request($request); }products здесь — заранее созданная referable-таблица. Самостоятельный проект карточки содержит её создание, соединение и вывод Кабель / 3. Константа CUBRID_INCLUDE_OID просит драйвер сохранить идентификаторы экземпляров, но не добавляет на экран обязательную колонку oid. cubrid_bind передаёт значение SKU отдельно от SQL, а move_cursor выбирает первую строку результата.
Применение 2: выбрать черновик для управляемого удаления
// После SELECT ... WHERE sku=? и установки курсора на DRAFT-169:
$draftOid = cubrid_current_oid($request);
if ($draftOid === false) { throw new RuntimeException('Не выбран черновик'); }
// Перед удалением отдельно проверить права, lock и актуальный статус DRAFT.
// Получение OID само по себе ничего не удаляет.Удаление с отменой и подтверждением сначала показывает исчезновение черновика внутри транзакции, затем его возврат после rollback. После подтверждённого удаления остаётся только опубликованный кабель. Эта операция использует идентификатор выбранной строки, а не пытается собрать OID из id=1.
Нельзя складывать OID из предполагаемых чисел, считать его переносимым бизнес-ключом или передавать его между разными базами. cubrid_get_class_name поможет проверить класс, cubrid_is_instance — текущее существование, но ни одна из этих проверок не разрешает пользователю менять чужой объект.
cubrid_getПрочитать одно, несколько или все свойства одного выбранного экземпляра базы.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_get(resource $conn_identifier, string $oid, mixed $attr = ?): mixedПрочитать одно, несколько или все свойства одного выбранного экземпляра базы.
cubrid_get(resource $conn_identifier, string $oid, mixed $attr = ?): mixedФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| conn_identifier | Живое соединение именно с той базой, где был получен OID. Ресурс request сюда не подходит. |
| oid | Строковый идентификатор существующего экземпляра базы, полученный через current_oid. Не SKU, не id из PRIMARY KEY и не произвольная строка из формы. |
| attr, необязательный | Строка — одно имя атрибута; непрерывный массив имён с индексами 0,1,… — выбранные атрибуты; отсутствие аргумента — все атрибуты экземпляра. Это не SQL-выражение и не список через запятую. |
Результат и отказ
Одиночный атрибут возвращается строкой; массив имён даёт ассоциативную карту; без attr возвращается карта всех атрибутов. Для одиночного SQL NULL возвращается false, как и при ошибке: немедленный cubrid_error_code и предупреждение различают исходы. В карте SQL NULL представлен null, пустая строка — "", числовой ноль — строкой "0". Нельзя проверять все эти варианты одним empty().
Применение 1: показать название и отсутствие примечания
$card = cubrid_get($db, $oid, ['title', 'qty', 'note']);
if (!is_array($card)) { throw new RuntimeException('Карточка не прочитана'); }
$noteLabel = $card['note'] === null ? 'не задано' : $card['note'];
echo $card['title'], ' / остаток ', $card['qty'], ' / ', $noteLabel;
// Для Кабеля: Кабель / остаток 3 / не задано.Полный проект сопоставляет тот же NULL с одиночным get(note), который возвращает false при коде ошибки 0. Для Хомута выводится отдельная пустая строка и "0" остатка. Это три разных состояния данных, которые форма должна сохранять, а не превращать в одно «нет значения». При HTML-выводе названия и примечания дополнительно экранируются; result.html делает это в общем рендерере.
Применение 2: вычислить резерв из свежего значения
// Внутри короткой транзакции после успешного lock_write:
$current = cubrid_get($db, $oid, ['title', 'qty']);
if (!is_array($current)) { throw new RuntimeException('Нет текущего остатка'); }
$qty = (int) $current['qty'];
if ($qty < 1) { throw new RuntimeException('Товара нет в наличии'); }
$newQty = (string) ($qty - 1);
// put($db, $oid, 'qty', $newQty), затем проверяемый commit.В двух редакторах свежая карта после освобождения блокировки содержит Кабель / 3; резерв оставляет 2. Полученный PHP-массив — снимок значений в момент чтения. Он не меняется сам после чужого commit. Запросить все атрибуты одного экземпляра допустимо для диагностики, но для карточки нужны выбранные поля, а для списка многих товаров — ограниченный SELECT.
CUBRID-коллекции также входят в базовый контракт: одиночный get коллекции показывает строковое представление, а чтение в общей карте может вернуть вложенный массив элементов. Это зависит от типа атрибута; не следует разбирать произвольную SQL-коллекцию строковым explode. Основные проекты намеренно работают с VARCHAR/INTEGER/NULL, чтобы сначала освоить различие формы ответа и значения.
При прямом get(note) сохраните код сразу после вызова: последующий cubrid_is_instance не является способом получить прежнюю ошибку. При сложной проверке сначала выполните cubrid_lock_write, затем повторное чтение; проверка существования перед get не исключает удаление другим соединением между этими действиями.
cubrid_get_class_nameУзнать имя структуры CUBRID, которой принадлежит найденный экземпляр.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_get_class_name(resource $conn_identifier, string $oid): string|falseУзнать, к какому классу базы относится найденный экземпляр.
cubrid_get_class_name(resource $conn_identifier, string $oid): string|falseФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| conn_identifier | Живое соединение именно с той базой, где был получен OID. Ресурс request сюда не подходит. |
| oid | Строковый идентификатор существующего экземпляра базы, полученный через current_oid. Не SKU, не id из PRIMARY KEY и не произвольная строка из формы. |
Результат и отказ
Строковое имя класса либо false при отказе. В нашем сервере возвращается имя с владельцем dba., а не имя PHP-класса. Проверяйте полученную строку как метаданные базы. Для строк системных таблиц, например db_class, официальный контракт прямо не поддерживает эту функцию.
Применение 1: проверить тип карточки в административном просмотрщике
$class = cubrid_get_class_name($db, $oid);
if ($class === false) { throw new RuntimeException('Класс не прочитан'); }
// Для подготовленной таблицы products ожидается dba.products.
if (strtolower($class) !== 'dba.products') {
throw new RuntimeException('Этот экран работает только с товарами');
}Имя products в этом фрагменте заранее известно приложению. В самостоятельном просмотрщике таблица имеет своё случайное лабораторное имя, поэтому сравнивается именно с ним. Экран показывает логический результат «объект принадлежит учебному каталогу» и поля Кабель / 3; случайный OID не превращается в пользовательский маршрут.
Применение 2: выбрать явный набор редактируемых полей
$editors = ['dba.products' => ['title', 'note']];
$class = cubrid_get_class_name($db, $oid);
if ($class === false || !isset($editors[strtolower($class)])) {
throw new RuntimeException('Для класса нет редактора');
}
$allowedFields = $editors[strtolower($class)];
// Из формы выбираются только разрешённые ключи; права проверяются отдельно.Это полезное расширение правки карточки: цена и SKU не попадают в карту изменений только потому, что пользователь прислал одноимённые поля. Таблица разрешённых редакторов не даёт доступа ко всем экземплярам этого класса; принадлежность конкретной карточки пользователю проверяется отдельно.
Не подставляйте произвольное возвращённое имя в SQL как доказанно разрешённый идентификатор. Сравнение с собственным списком допустимых классов отличается от произвольной конкатенации строки. Для обзора колонок, ключей и наследования нужен справочник схемы, а не get_class_name для каждой строки системного каталога. Наличие класса не означает, что любой его экземпляр существует сейчас: это отдельная проверка cubrid_is_instance.
cubrid_is_instanceРазличить существующий экземпляр, его отсутствие и ошибку проверки.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_is_instance(resource $conn_identifier, string $oid): intРазличить существующий экземпляр, его отсутствие и ошибку проверки.
cubrid_is_instance(resource $conn_identifier, string $oid): intФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| conn_identifier | Живое соединение именно с той базой, где был получен OID. Ресурс request сюда не подходит. |
| oid | Строковый идентификатор существующего экземпляра базы, полученный через current_oid. Не SKU, не id из PRIMARY KEY и не произвольная строка из формы. |
Результат и отказ
Целое 1 — экземпляр существует, 0 — не существует, -1 — ошибка. Это не bool. В PHP число -1 считается истинным в обычном if, поэтому if(cubrid_is_instance(...)) ошибочно смешивает успех с отказом. Используйте === 1, === 0 и отдельную ветку ошибки.
Применение 1: объяснить, почему карточку нельзя открыть
$state = cubrid_is_instance($db, $oid);
if ($state === 1) {
echo 'Можно читать выбранный экземпляр';
} elseif ($state === 0) {
echo 'Карточка больше не существует';
} else {
$errorCode = cubrid_error_code();
throw new RuntimeException('Не удалось проверить карточку');
}Просмотрщик проверяет 1 перед get и затем всё равно обрабатывает результат самого чтения. Между проверкой и действием другой клиент может удалить запись; это классическая гонка «проверили — затем воспользовались». Существование не доказывает ни право просмотра, ни право удаления.
Применение 2: проверить отмену удаления черновика
// Для выбранного DRAFT, autocommit выключен, условия проверены:
if (!cubrid_drop($db, $draftOid)) { throw new RuntimeException('Не удалён'); }
$duringDelete = cubrid_is_instance($db, $draftOid); // В проекте 0.
if (!cubrid_rollback($db)) { throw new RuntimeException('Не подтверждена отмена'); }
$afterUndo = cubrid_is_instance($db, $draftOid); // В проекте 1.Полный цикл после rollback перечитывает название «Пробная карточка» и статус DRAFT через SQL, а после отдельного drop/commit показывает 0 и оставшийся опубликованный товар. Число само по себе не заменяет полезный результат: проверяется, какая карточка осталась в каталоге.
Не создавайте цикл с повторным is_instance до «удобного» значения и не проверяйте заранее все OID таблицы. Операция нужна для выбранного экземпляра. Для короткой критической правки применимы cubrid_lock_write и повторная проверка состояния, а для обычного веб-обновления — условный SQL с бизнес-ключом и версией. Ошибка -1 должна прервать действие, а не трактоваться как уже удалённый объект.
cubrid_putИзменить одно поле или явную карту полей выбранного экземпляра.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_put(resource $conn_identifier, string $oid, string $attr, mixed $value): bool
cubrid_put(resource $conn_identifier, string $oid, array $attr_value): boolИзменить одно поле или явную карту полей выбранного экземпляра.
cubrid_put(resource $conn_identifier, string $oid, string $attr, mixed $value): bool
cubrid_put(resource $conn_identifier, string $oid, array $attr_value): boolФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| conn_identifier | Живое соединение именно с той базой, где был получен OID. Ресурс request сюда не подходит. |
| oid | Строковый идентификатор существующего экземпляра базы, полученный через current_oid. Не SKU, не id из PRIMARY KEY и не произвольная строка из формы. |
| attr в форме из четырёх аргументов | Имя одного изменяемого атрибута. Имя задаётся разработчиком, а не выбирается бесконтрольно из POST. |
| value в форме из четырёх аргументов | Новое значение. Официальное описание перечисляет int, float и string; примеры также показывают коллекции. Закреплённая реализация поддерживает NULL и массив элементов для соответствующего SQL-типа. |
| attr_value в форме из трёх аргументов | Ассоциативная карта: строковый ключ — имя атрибута, значение — новое содержимое. Это не список без ключей. Передаётся вместо пары attr/value. |
Результат и отказ
true означает успешное выполнение изменения экземпляра, false — отказ. Возвращается не новая карточка, не её OID и не affected_rows. Подтверждение транзакции остаётся отдельным действием. Если autocommit выключен, чтение этим же соединением может видеть ещё неподтверждённую правку.
Применение 1: временно изменить название и отменить правку
if (!cubrid_put($db, $oid, 'title', 'Временное название')) {
$code = cubrid_error_code();
throw new RuntimeException('Название не принято');
}
// Здесь своё соединение уже прочитает временное название.
if (!cubrid_rollback($db)) { throw new RuntimeException('Не подтверждена отмена'); }
// Новый get/SELECT снова показывает прежнее название.В полной программе SQL показывает сначала «Временное название», после отмены — «Кабель». Затем начинается отдельная операция сохранения. Так видно различие между приёмом изменения сервером и окончательным commit; простое echo "Сохранено" после put было бы преждевременным.
Применение 2: сохранить разрешённую карту полей
$changes = ['title' => 'Кабель 2 м', 'qty' => '5', 'note' => null];
if (!cubrid_put($db, $oid, $changes)) {
$code = cubrid_error_code();
cubrid_rollback($db);
throw new RuntimeException('Карта изменений не принята');
}
if (!cubrid_commit($db)) { throw new RuntimeException('Нет подтверждения сохранения'); }
// SELECT title,qty,note,price_cents подтверждает 5, NULL и прежнюю цену 12900.В этом фрагменте новая qty задана строкой десятичных цифр, а сервер хранит её как INTEGER согласно схеме. Строка "NULL" записала бы текст в текстовый столбец; значение null передаёт SQL NULL. PHP-объект, ресурс или произвольный bool не являются универсально подходящим значением OID-атрибута. Для коллекций массив означает значение коллекции конкретного поля, а внешняя карта означает набор полей: это разные уровни.
Во втором самостоятельном применении резервирование под write lock вычисляет строку "2" из свежего остатка 3 и передаёт её в одно поле qty. Сначала выполняется проверка наличия товара, затем put и commit. Для денег сохраняйте точное представление и тип SQL; не превращайте стоимость без необходимости в двоичный float.
put не принимает WHERE и не проверяет автоматически статус DRAFT, владельца, диапазон остатка или версию формы. Если условие должно проверяться атомарно в самой команде, удобнее параметризованный UPDATE с WHERE и проверкой cubrid_affected_rows. Нельзя передавать $_POST как attr_value: список допустимых ключей и проверка значений — часть приложения. Для нескольких связанных действий используйте cubrid_commit / cubrid_rollback.
cubrid_dropУдалить один выбранный экземпляр с дальнейшим подтверждением или отменой транзакции.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_drop(resource $conn_identifier, string $oid): boolУдалить один выбранный экземпляр с дальнейшим подтверждением или отменой транзакции.
cubrid_drop(resource $conn_identifier, string $oid): boolФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| conn_identifier | Живое соединение именно с той базой, где был получен OID. Ресурс request сюда не подходит. |
| oid | Строковый идентификатор существующего экземпляра базы, полученный через current_oid. Не SKU, не id из PRIMARY KEY и не произвольная строка из формы. |
Результат и отказ
true — экземпляр удалён текущей операцией, false — отказ. Это удаление данных одного экземпляра, а не команда DROP TABLE и не освобождение PHP-request. При выключенном autocommit окончательный результат определяется последующим commit либо rollback; каждый из них тоже проверяют.
Применение 1: проверить сценарий отмены перед окончательным удалением
// Собственная тестовая карточка, права и DRAFT проверены заранее.
if (!cubrid_drop($db, $draftOid)) {
$code = cubrid_error_code();
throw new RuntimeException('Удаление не выполнено');
}
// В своей транзакции карточки уже нет.
if (!cubrid_rollback($db)) { throw new RuntimeException('Отмена не подтверждена'); }
// Повторный SELECT показывает возвращённую карточку.Самостоятельная программа делает отмену только в собственной лабораторной таблице и выводит вернувшееся название «Пробная карточка». Это демонстрация транзакции, а не рекомендация пробно удалять чужие производственные данные. Триггеры, ограничения и внешние эффекты требуют отдельного анализа.
Применение 2: удалить только всё ещё ненужный черновик
if (!cubrid_lock_write($db, $draftOid)) { throw new RuntimeException('Черновик занят'); }
$current = cubrid_get($db, $draftOid, ['title', 'note']);
if (!is_array($current) || $current['note'] !== 'DRAFT') {
cubrid_rollback($db);
throw new RuntimeException('Удалять можно только черновик');
}
if (!cubrid_drop($db, $draftOid)) {
$code = cubrid_error_code();
cubrid_rollback($db);
throw new RuntimeException('Не удалось удалить');
}
if (!cubrid_commit($db)) { throw new RuntimeException('Результат удаления не подтверждён'); }В подтверждённой ветке проекта после этой операции SQL оставляет «Опубликованный кабель / PUBLISHED», а cubrid_is_instance возвращает 0 для удалённого черновика. Статус повторно читается после захвата блокировки, чтобы не удалять карточку по старым данным формы. В настоящей схеме для статуса обычно предусмотрена отдельная колонка; note используется только в данной небольшой лаборатории.
Проверка статуса не заменяет право удаления. Требования внешних ключей, триггеры, блокировки и уже удалённый OID могут привести к отказу; сохраните исходные code/facility/message до rollback и не публикуйте сырой диагностический текст. Для массового удаления по условию нужен SQL DELETE с осмысленным WHERE; обход всех OID в PHP ради такого удаления усложняет задачу. Для закрытия request применяйте cubrid_close_request.
cubrid_lock_readПолучить разделяемую блокировку чтения конкретного экземпляра в короткой операции.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_lock_read(resource $conn_identifier, string $oid): boolПолучить разделяемую блокировку чтения конкретного экземпляра в короткой операции.
cubrid_lock_read(resource $conn_identifier, string $oid): boolФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| conn_identifier | Живое соединение именно с той базой, где был получен OID. Ресурс request сюда не подходит. |
| oid | Строковый идентификатор существующего экземпляра базы, полученный через current_oid. Не SKU, не id из PRIMARY KEY и не произвольная строка из формы. |
Результат и отказ
true — read lock получен, false — получить его не удалось. Возвращаемое значение не содержит данные объекта и не означает выдачу пользовательских прав. После успешного вызова нужные атрибуты читаются отдельно. Блокировка принадлежит транзакции соединения.
Применение 1: прочитать согласованную карточку в короткой операции
if (!cubrid_lock_read($db, $oid)) {
$code = cubrid_error_code();
throw new RuntimeException('Не удалось получить read lock');
}
$card = cubrid_get($db, $oid, ['title', 'qty']);
if (!is_array($card)) { cubrid_rollback($db); throw new RuntimeException('Нет карточки'); }
// Проверить/использовать Кабель и qty=3 внутри короткой операции.
if (!cubrid_commit($db)) { throw new RuntimeException('Не закончено чтение'); }Это вариант чтения карточки для ситуации, где явно нужна блокировка. Обычный экран не обязан брать её на каждую запись: изоляция и версия данных уже имеют собственную семантику. Держать read lock на всё время просмотра страницы человеком не следует.
Применение 2: понять конфликт двух редакторов
// Владелец уже держит read lock на этой карточке в другом соединении.
if (!cubrid_lock_read($db, $oid)) { throw new RuntimeException('Read lock не получен'); }
$snapshot = cubrid_get($db, $oid, ['title', 'qty']);
if (!cubrid_rollback($db)) { throw new RuntimeException('Не завершён свой участок'); }
// После окончания собственного read-участка другая сессия всё ещё держит свой lock.Реальные две сессии показывают: два read lock совместимы, но чужой read lock препятствует write lock. Свой rollback не освобождает блокировку другого соединения. Родитель посылает владельцу явную команду закончить его транзакцию и получает подтверждение; только потом начинается новая запись 3 → 2.
Закрыть ресурс SELECT и закончить транзакцию — разные действия. Освобождение request не является освобождением всех блокировок соединения. Ожидание занятости ограничивается параметром lock timeout; в современном CCI его единица — миллисекунда. Бесконечный режим не подходит для этой учебной координации.
Если после чтения точно планируется запись, заранее подумайте о cubrid_lock_write вместо одновременного повышения нескольких read lock до write lock. Это не гарантирует отсутствие всех deadlock: порядок захвата нескольких объектов и короткие транзакции также важны. При отказе не продолжайте действие со старым PHP-массивом как будто блокировка получена.
cubrid_lock_writeПолучить блокировку изменения, затем перечитать данные и выполнить согласованную правку.
PECL CUBRID >= 8.3.0 · cubrid
cubrid_lock_write(resource $conn_identifier, string $oid): boolПолучить блокировку изменения, затем перечитать данные и выполнить согласованную правку.
cubrid_lock_write(resource $conn_identifier, string $oid): boolФункция доступна начиная с PECL CUBRID 8.3.0. Здесь указана справочная форма вызова: resource и mixed описывают типы, а знак ? в необязательном attr означает, что аргумент можно не передавать; это не строка для объявления функции в PHP. Самостоятельные проекты закреплены на PHP 7.4.33, native-драйвере 11.2.0.0002 и CUBRID 11.3.5.
| Параметр | Значение и ограничения |
|---|---|
| conn_identifier | Живое соединение именно с той базой, где был получен OID. Ресурс request сюда не подходит. |
| oid | Строковый идентификатор существующего экземпляра базы, полученный через current_oid. Не SKU, не id из PRIMARY KEY и не произвольная строка из формы. |
Результат и отказ
true — write lock получен, false — отказ. Содержимое объекта само по себе не меняется, commit не выполняется. Ошибку следует зафиксировать немедленно: code/facility/message после следующей команды уже могут описывать другое действие.
Применение 1: уменьшить остаток на основе свежего значения
if (!cubrid_lock_write($db, $oid)) { throw new RuntimeException('Остаток занят'); }
$card = cubrid_get($db, $oid, ['qty']);
if (!is_array($card) || (int) $card['qty'] < 1) {
cubrid_rollback($db);
throw new RuntimeException('Резерв невозможен');
}
$qty = (string) ((int) $card['qty'] - 1);
if (!cubrid_put($db, $oid, 'qty', $qty)) {
cubrid_rollback($db);
throw new RuntimeException('Резерв не записан');
}
if (!cubrid_commit($db)) { throw new RuntimeException('Резерв не подтверждён'); }Полный проект сначала получает настоящий отказ -75/facility 1, когда первое соединение ещё держит read lock. После rollback первого соединения начинается отдельная операция: lock_write проходит, свежее чтение возвращает 3, SQL после commit показывает 2. Измеряется реальное время ожидания, но не обещается точное совпадение с заданной миллисекундой.
Применение 2: проверить статус непосредственно перед удалением
if (!cubrid_lock_write($db, $draftOid)) { throw new RuntimeException('Карточка занята'); }
$state = cubrid_get($db, $draftOid, ['note']);
if (!is_array($state) || $state['note'] !== 'DRAFT') {
cubrid_rollback($db);
throw new RuntimeException('Статус уже не допускает удаление');
}
// drop и проверяемый commit; полная обработка приведена в проекте удаления.В проекте черновика блокировка и повторная проверка относятся к одному экземпляру и одной короткой транзакции. Название и статус, прочитанные минуту назад в HTML-форме, не становятся свежими от одного успешного lock_write. Аналогично редактирование полей отделяет временную правку с rollback от окончательного сохранения.
У write lock нет аргумента «срок в секундах». Лимит ожидания относится к соединению; request timeout — другой механизм. При -75 в контролируемой подготовке действительно ожидался чужой lock; произвольную сетевую или серверную ошибку нельзя автоматически назвать тем же конфликтом. Rollback и закрытие собственного участка обязательны и при неуспехе.
Для веб-форм часто лучше оптимистическая проверка: UPDATE … WHERE id=? AND version=?, затем affected_rows. Тогда пользователь не держит живую транзакцию, пока редактирует текст. OID lock полезен для короткой работы сервера с уже выбранным экземпляром; он не является дополнительным паролем, общей блокировкой всей таблицы или защитой от некорректных бизнес-правил.
Источники и обсуждения
- PHP Manual: cubrid_current_oid
- PHP Manual: cubrid_get
- PHP Manual: cubrid_get_class_name
- PHP Manual: cubrid_is_instance
- PHP Manual: cubrid_put
- PHP Manual: cubrid_drop
- PHP Manual: cubrid_lock_read
- PHP Manual: cubrid_lock_write
- Native PHP-драйвер 11.2.0.0002
- CUBRID 11.3: таблица и повторное использование OID
- CUBRID 11.3: CCI, OID и транзакционные операции
- OWASP: доступ к объекту и проверка полномочий
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


