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

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

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

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

Глава 98 · После первых запросов CUBRID и транзакций

CUBRID: соединение между HTTP-запросами, транзакции и несколько результатов

Два HTTP-сценария, составной SQL-отчёт и явное завершение ресурсов с полным разбором пяти native-функций.

От карточки товара к границам соединения, запроса и транзакции

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

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

Перед началом прочитайте подключение и первые запросы и commit, rollback и autocommit. В коротком фрагменте $db обозначает уже открытое соединение. Самостоятельный проект содержит свои данные, ровно одну учебную таблицу, результат и очистку.

Профиль проверки: PHP 7.4.33, native cubrid 11.2.0.0002, CCI 11.2.0, сервер CUBRID 11.3.5.1274. Это отдельная среда совместимости. Обещание документации и поведение конкретного драйвера сравниваются явно: один вызов с именем pconnect сам по себе не доказывает повторное использование соединения между HTTP-запросами.

ПонятиеЧто это в наших примерахЧем заканчивается
HTTP-запросОдно открытие карточки O-RING-42Ответ200 с JSON; новый GET — новая операция
PHP-workerПроцесс, который обслуживает два GETОстановка процесса после двух наблюдений
Соединение $dbКанал для SQL; это не посетитель и не массив товаровЯвное закрытие владельцем либо механизм конца запроса конкретного драйвера
ТранзакцияНеподтверждённое изменение qty5→2commit сохраняет, rollback отменяет
Запрос $requestПодготовленная команда или несколько SQL-результатовclose_prepare освобождает request
PHP-массив $rowУже прочитанные sku/qtyОбычный срок жизни данных PHP; не живое соединение
Один понятный путь чтения
  1. Подключиться → $db
  2. Выполнить ограниченный SELECT → $request
  3. Прочитать строку → $row[sku], $row[qty]
  4. Освободить request → явно закончить транзакцию
  5. Отдать ответ → следующий HTTP-запрос начинает свою работу

Первый шаг: прочитать ровно нужную карточку

$request = cubrid_execute($db, 'SELECT sku,qty FROM stock WHERE id=42 LIMIT 1');
if ($request === false) { throw new RuntimeException('SQL не выполнен'); }
$row = cubrid_fetch_assoc($request);
if ($row !== false) { echo $row['sku'], ': ', $row['qty']; }
cubrid_close_prepare($request);

Здесь $db уже открыт, а stock содержит учебный товар. = сохраняет значение; скобки вызывают функцию; ['qty'] обращается к именованному полю строки. WHERE и LIMIT оставляют в ответе одну карточку. На исходных данных вывод — «O-RING-42: 5». В полном проекте ниже отдельные шаги создают таблицу, проверяют диагностику и освобождают ресурсы при ошибке; короткий фрагмент нужен для первого понимания действия.

Модель «канал → запрос → строка» полезна, но это не три файла на диске. Один запрос может иметь несколько результатов, а одна транзакция — несколько SQL-команд. Вызов close_prepare не означает ни rollback, ни закрытие соединения. Выборка и сортировка нужных товаров выполняются в SQL; большой каталог не загружается целиком ради одной карточки.

Для наблюдения вводится SQL-маркер: SET @c173_marker='catalog-request-one' задаёт временное значение внутри SQL-сессии, а SELECT @c173_marker читает его. Это не PHP-переменная $marker, не строка каталога и не cookie браузера. В проекте сначала подтверждается реальное чтение заданного значения, затем следующий GET проверяет его наличие. Отказ -1070 означает, что такого session-значения нет; другая ошибка не принимается за этот результат. Механика SQL-переменных описана в CUBRID SET: user variables.

PHP Manual описывает постоянный канал: повторный вызов с тем же адресом базы и пользователем ищет открытое соединение, а завершение запроса и close/disconnect не должны закрывать такой канал. Это документированный замысел API, а не подтверждённая оптимизация данного профиля. В PHP7-исходнике cubrid 11.2.0.0002 cache lookup выполняется только при !persistent, pconnect-ресурс попадает в regular_list, а его деструктор вызывает cci_disconnect. В отдельных HTTP-запросах одного worker SQL marker не сохранился, ручной режим снова стал autocommit=true, незавершённый резерв был отменён. В этой версии нельзя обещать PHP persistent cache на основании названия функции. Мы не измеряли глобальный пул CCI/CAS и не выдаём эти наблюдения за поведение всех версий расширения.

Два HTTP-проекта запускают один локальный worker до первого CCI-подключения родителя и выполняют ровно два GET. Это настоящая проверка request boundary в PHP cli-server, а не доказательство работы пула PHP-FPM/Apache. Продакшен-профиль и нагрузку проверяют отдельно. Основы HTTP-запроса и ответа и обычного подключения объясняют предшествующие понятия.

Открыть каталог дважды и увидеть новое подтверждённое количество

Кладовщик открыл остаток уплотнителя, подтвердил поступление двух штук и обновил страницу. Нужно увидеть 7 вместо 5, а заодно проверить, переносит ли pconnect состояние между запросами именно в установленном драйвере. Проверка использует два реальных GET одного PHP-процесса и ограниченную SQL-карточку, а не два запуска CLI.

Путь данных и полученный результат
  1. Таблица: O-RING-42 → qty5
  2. GET1: pconnect → SELECT → qty5 → commit
  3. Поставка: UPDATE qty7 → commit родителя
  4. GET2 того же worker: новый SELECT → qty7; отдельно наблюдаем SQL marker
  5. CSV фиксирует два пользовательских результата

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

$db = cubrid_pconnect($host, $port, $database, $user, $password);
if ($db === false) { throw new RuntimeException('Нет соединения'); }
$request = cubrid_execute($db, 'SELECT sku,qty FROM stock WHERE id=42 LIMIT 1');
if ($request === false) { throw new RuntimeException('Нет результата'); }
$row = cubrid_fetch_assoc($request);
cubrid_close_prepare($request);

Скобки передают адрес базы и учётные данные. $db — соединение, $request — отдельный SQL-запрос, $row — полученная строка. WHERE выбирает именно id42; LIMIT ограничивает максимум ответа. pconnect не кэширует строку товара: за актуальными данными всё равно нужен новый SELECT. Полный проект создаёт данные и проверяет транзакционные границы.

Что получится после запуска

НаблюдениеРезультат
Первое открытиеO-RING-42: 5
После подтверждения поставкиO-RING-42: 7
Один HTTP-workerда
Маркер прошлого запроса сохранённет
Файлpersistent-catalog.csv
Самостоятельный проект, исходник и архив
Открыть каталог дважды и увидеть новое подтверждённое количествоСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
require __DIR__.'/http-lab.php';
startHttp173();
try {
    $config=c173Config();$db=c173Connect();$runtime=c173Runtime($db);

function checked173(callable $action) {
    $call=c173Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c173Failure('Действие не завершено',$call);}
    return $call['value'];
}
function stock173($db,array &$owned,string $label): string {
    return c173CreateTable($db,$owned,$label,[
        ['id'=>42,'sku'=>'O-RING-42','title'=>'Уплотнитель №42','category'=>'parts','price_cents'=>190,'qty'=>5,'note'=>null],
        ['id'=>43,'sku'=>'CLAMP-43','title'=>'Хомут 43','category'=>'parts','price_cents'=>90,'qty'=>2,'note'=>'']]);
}
function csv173(string $name,array $records): void {
    $fp=fopen(__DIR__.'/'.$name,'wb');if (!is_resource($fp)) {throw new RuntimeException('CSV не открыт');}
    try {foreach($records as $row) {if (fputcsv($fp,$row)===false) {throw new RuntimeException('CSV не записан');}}} finally {fclose($fp);}
}

    $name=stock173($db,$owned,'http_catalog');$table=c173Qualified($name);
    saveHttpProof173('http-handoff.json',['scenario'=>'catalog','table'=>$name]);
    // Runtime metadata opened a parent read transaction: end it before the HTTP reader.
    c173Commit($db);
    $first=getHttp173(1);
    $affected=c173Command($db,'UPDATE '.$table.' SET qty=7 WHERE id=42');c173Commit($db);
    $second=getHttp173(2);$httpProof=stopHttp173();
    $a=$first['data'];$b=$second['data'];
    $records=[['request','sku','qty'],[1,$a['before'][0]['sku'],(int)$a['before'][0]['qty']],[2,$b['before'][0]['sku'],(int)$b['before'][0]['qty']]];
    csv173('persistent-catalog.csv',$records);
    $rows=[['Первое открытие','O-RING-42: 5'],['После подтверждения поставки','O-RING-42: 7'],['Один HTTP-worker',true],['Маркер прошлого запроса сохранён',false],['Файл','persistent-catalog.csv']];
    $checks=['first_catalog'=>(int)$a['before'][0]['qty']===5,'changed_one'=>$affected===1,
        'fresh_catalog'=>(int)$b['before'][0]['qty']===7,'sku_same'=>$a['before'][0]['sku']==='O-RING-42'&&$b['before'][0]['sku']==='O-RING-42',
        'same_worker'=>$a['worker_pid']===$b['worker_pid']&&$b['worker_pid']===$httpProof['pid'],
        'first_marker'=>$a['marker']==='catalog-request-one','marker_not_carried'=>$b['marker']===null&&($b['marker_error']['code']??null)===-1070&&($b['marker_error']['facility']??null)===1&&($b['marker_error']['warning']??null)===true&&($b['marker_error']['message_present']??null)===true,
        'both_finished'=>$a['explicit_finish']===true&&$b['explicit_finish']===true,
        'two_http'=>$httpProof['http_requests']===2,'worker_stopped'=>$httpProof['stopped']&&!$httpProof['forced_stop']&&!$httpProof['timed_out']];
    $observations=['http_responses'=>[$a,$b],'http_worker'=>['pid'=>$httpProof['pid'],'stopped'=>$httpProof['stopped'],'http_requests'=>$httpProof['http_requests']],
        'receipt_files'=>['http-worker.json','http-response-1.json','http-response-2.json'],'catalog'=>$records,'session_identity_claimed'=>false];

} finally {
    $httpProof=stopHttp173();
    if (is_resource($db)) {try {$removed=c173Cleanup($db,$owned);} finally {c173Close($db);}}
}

c173Report('cub173-persistent-catalog','Открыть каталог дважды и увидеть новое подтверждённое количество',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Первое открытие — O-RING-42: 5
После подтверждения поставки — O-RING-42: 7
Один HTTP-worker — да
Маркер прошлого запроса сохранён — нет
Файл — persistent-catalog.csv

Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.

Открыть результат выполнения · Посмотреть данные JSON · Скачать два состояния каталога

Границы решения

Одинаковый PID доказывает общий процесс PHP, но не идентичность SQL-соединения. Отсутствие session marker само по себе может объясняться очисткой состояния; вместе с исходником проверенного PHP7-порта оно не даёт обещать persistent cache. Здесь не измеряется скорость и не делается вывод о других версиях расширения. Соединение базы нельзя прикреплять к посетителю через $_SESSION.

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

Что будет, если поставка не вызовет commit?

Разбор

Не следует ожидать, что независимый читатель увидит неподтверждённое изменение; возможны также ожидания блокировки. Явно закончите транзакцию поставки, прежде чем обновлять экран. Для приложения этот порядок важнее имени connect/pconnect.

Отменить незавершённый резерв и начать следующий запрос с явного режима

Покупатель начал резервировать три уплотнителя, но запрос не дошёл до подтверждения. Другая операция не должна продолжать его незавершённую транзакцию. Первый GET специально показывает границу окончания запроса; второй показывает прикладной порядок: явный режим, своё изменение, rollback в finally и чтение восстановленного остатка.

Путь данных и полученный результат
  1. До запроса: qty5
  2. GET1: ручной режим → резерв3 → внутри qty2 → конец запроса без commit
  3. GET2: наблюдаем qty5 и режим; затем задаём false явно
  4. Пробный резерв1 → finally rollback → qty5
  5. Родитель перечитывает qty5 после остановки worker

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

$url = 'CUBRID:127.0.0.1:33000:shop:::?login_timeout=3000';
$db = cubrid_pconnect_with_url($url, $user, $password);
if ($db === false || !cubrid_set_autocommit($db, false)) { throw new RuntimeException('Нет ручного режима'); }
try {
    // Своё изменение и проверка бизнес-условия.
} finally {
    cubrid_rollback($db); // Этот вариант всегда отменяет пробный резерв.
}

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

Что получится после запуска

НаблюдениеРезультат
Начальный остаток5
Внутри незавершённого резерва2
Следующий HTTP-запрос5
После явного rollback5
Файлpersistent-reset.csv
Самостоятельный проект, исходник и архив
Отменить незавершённый резерв и начать следующий запрос с явного режимаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
require __DIR__.'/http-lab.php';
startHttp173();
try {
    $config=c173Config();$db=c173Connect();$runtime=c173Runtime($db);

function checked173(callable $action) {
    $call=c173Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c173Failure('Действие не завершено',$call);}
    return $call['value'];
}
function stock173($db,array &$owned,string $label): string {
    return c173CreateTable($db,$owned,$label,[
        ['id'=>42,'sku'=>'O-RING-42','title'=>'Уплотнитель №42','category'=>'parts','price_cents'=>190,'qty'=>5,'note'=>null],
        ['id'=>43,'sku'=>'CLAMP-43','title'=>'Хомут 43','category'=>'parts','price_cents'=>90,'qty'=>2,'note'=>'']]);
}
function csv173(string $name,array $records): void {
    $fp=fopen(__DIR__.'/'.$name,'wb');if (!is_resource($fp)) {throw new RuntimeException('CSV не открыт');}
    try {foreach($records as $row) {if (fputcsv($fp,$row)===false) {throw new RuntimeException('CSV не записан');}}} finally {fclose($fp);}
}

    $name=stock173($db,$owned,'http_reset');$table=c173Qualified($name);
    saveHttpProof173('http-handoff.json',['scenario'=>'reset','table'=>$name]);c173Commit($db);
    $first=getHttp173(1);$second=getHttp173(2);$httpProof=stopHttp173();
    $a=$first['data'];$b=$second['data'];
    $final=c173Read($db,'SELECT qty FROM '.$table.' WHERE id=42 LIMIT 1');c173Commit($db);
    $records=[['phase','qty'],['before',5],['pending',(int)$a['pending_qty']],['next_request',(int)$b['before'][0]['qty']],['explicit_rollback',(int)$b['after'][0]['qty']]];
    csv173('persistent-reset.csv',$records);
    $rows=[['Начальный остаток',5],['Внутри незавершённого резерва',2],['Следующий HTTP-запрос',5],['После явного rollback',5],['Файл','persistent-reset.csv']];
    $checks=['initial_five'=>(int)$a['before'][0]['qty']===5,'pending_two'=>(int)$a['pending_qty']===2,
        'deliberate_boundary'=>$a['explicit_finish']===false,'request_end_restored'=>(int)$b['before'][0]['qty']===5,
        'explicit_rollback_restored'=>(int)$b['after'][0]['qty']===5,'parent_readback'=>(int)$final[0]['qty']===5,
        'new_mode_observed'=>$b['initial_autocommit']===true,'marker_not_carried'=>$b['marker']===null&&($b['marker_error']['code']??null)===-1070&&($b['marker_error']['facility']??null)===1&&($b['marker_error']['warning']??null)===true&&($b['marker_error']['message_present']??null)===true,
        'same_worker'=>$a['worker_pid']===$b['worker_pid']&&$b['worker_pid']===$httpProof['pid'],
        'second_finished'=>$b['explicit_finish']===true,'two_http'=>$httpProof['http_requests']===2,
        'worker_stopped'=>$httpProof['stopped']&&!$httpProof['forced_stop']&&!$httpProof['timed_out']];
    $observations=['http_responses'=>[$a,$b],'http_worker'=>['pid'=>$httpProof['pid'],'stopped'=>$httpProof['stopped'],'http_requests'=>$httpProof['http_requests']],
        'reservation'=>$records,'parent_readback'=>$final,'recommendation'=>'explicit transaction finish in each request'];

} finally {
    $httpProof=stopHttp173();
    if (is_resource($db)) {try {$removed=c173Cleanup($db,$owned);} finally {c173Close($db);}}
}

c173Report('cub173-persistent-reset','Отменить незавершённый резерв и начать следующий запрос с явного режима',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Начальный остаток — 5
Внутри незавершённого резерва — 2
Следующий HTTP-запрос — 5
После явного rollback — 5
Файл — persistent-reset.csv

Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.

Открыть результат выполнения · Посмотреть данные JSON · Скачать журнал резерва

Границы решения

Первый GET — контролируемое наблюдение одной версии драйвера, а не рекомендуемый обработчик заказа. Его незавершённая работа в этом профиле отменяется на границе запроса; это не обещание чистого session state во всех драйверах и SAPI. Никогда не растягивайте транзакцию на несколько HTTP-запросов. Пользовательский черновик храните как явную запись с владельцем и статусом.

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

Как сохранить резерв между страницами оформления?

Разбор

Создайте отдельную запись резерва с бизнес-ключом, владельцем, сроком и состоянием, подтвердите её транзакцией. Следующая страница снова проверит права и актуальный статус. Нельзя хранить открытое соединение или незавершённую транзакцию в PHP-сессии.

Получить список, пустую подборку и итог одним заранее составленным SQL-пакетом

Экран склада показывает две строки каталога, отдельно отрицательные остатки и компактный итог. Каждый SELECT имеет собственный набор столбцов. Пустая подборка отрицательных остатков — полезный результат, её нельзя принять за конец всего пакета и потерять итог.

Путь данных и полученный результат
  1. SELECT1 → два SKU с qty5/2
  2. next_result true → SELECT2 с нулём строк
  3. next_result true → SELECT3: COUNT2/SUM7
  4. next_result false + чистая диагностика → конец пакета
  5. close_prepare → commit → один понятный CSV

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

$request = cubrid_execute($db, 'SELECT 5 AS qty; SELECT 7 AS total', CUBRID_EXEC_QUERY_ALL);
if ($request === false) { throw new RuntimeException('Пакет не выполнен'); }
$first = cubrid_fetch_assoc($request);
if (cubrid_next_result($request) === true) {
    $second = cubrid_fetch_assoc($request);
}
cubrid_close_prepare($request);

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

Что получится после запуска

НаблюдениеРезультат
Первый результатO-RING-42: 5; CLAMP-43: 2
Второй результат0 строк — подборка пуста
Третий результат2 позиции, 7 единиц
Переходыtrue → true → false, код0
Файлmultiple-results.csv
Самостоятельный проект, исходник и архив
Получить список, пустую подборку и итог одним заранее составленным SQL-пакетомСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

try {
    $config=c173Config();$db=c173Connect();$runtime=c173Runtime($db);

function checked173(callable $action) {
    $call=c173Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c173Failure('Действие не завершено',$call);}
    return $call['value'];
}
function stock173($db,array &$owned,string $label): string {
    return c173CreateTable($db,$owned,$label,[
        ['id'=>42,'sku'=>'O-RING-42','title'=>'Уплотнитель №42','category'=>'parts','price_cents'=>190,'qty'=>5,'note'=>null],
        ['id'=>43,'sku'=>'CLAMP-43','title'=>'Хомут 43','category'=>'parts','price_cents'=>90,'qty'=>2,'note'=>'']]);
}
function csv173(string $name,array $records): void {
    $fp=fopen(__DIR__.'/'.$name,'wb');if (!is_resource($fp)) {throw new RuntimeException('CSV не открыт');}
    try {foreach($records as $row) {if (fputcsv($fp,$row)===false) {throw new RuntimeException('CSV не записан');}}} finally {fclose($fp);}
}

    $name=stock173($db,$owned,'batch_results');$table=c173Qualified($name);
    $sql='SELECT sku,qty FROM '.$table.' WHERE qty>0 ORDER BY id LIMIT 2; '
        .'SELECT sku FROM '.$table.' WHERE qty<0 ORDER BY id LIMIT 2; '
        .'SELECT COUNT(*) AS positions,SUM(qty) AS units FROM '.$table.' WHERE qty>0';
    $request=checked173(static function() use ($db,$sql) {return cubrid_execute($db,$sql,CUBRID_EXEC_QUERY_ALL);});
    $sets=[];$transitions=[];
    try {
        do {
            $sets[]=c173Rows($request);
            $next=c173Capture(static function() use ($request) {return cubrid_next_result($request);});
            $transitions[]=$next;
            if ($next['warning'] || $next['code']!==0 || $next['facility']!==0) {c173Failure('Следующий результат не получен',$next);}
            if (count($sets)>3) {throw new RuntimeException('Неожиданное число результатов');}
        } while ($next['value']===true);
    } finally {checked173(static function() use ($request) {return cubrid_close_prepare($request);});$request=null;}
    c173Commit($db);
    $records=[['section','sku','qty'],['stock',$sets[0][0]['sku'],(int)$sets[0][0]['qty']],['stock',$sets[0][1]['sku'],(int)$sets[0][1]['qty']],['negative','none',0],['total','positions',(int)$sets[2][0]['positions']],['total','units',(int)$sets[2][0]['units']]];
    csv173('multiple-results.csv',$records);
    $rows=[['Первый результат','O-RING-42: 5; CLAMP-43: 2'],['Второй результат','0 строк — подборка пуста'],['Третий результат','2 позиции, 7 единиц'],['Переходы','true → true → false, код0'],['Файл','multiple-results.csv']];
    $checks=['three_results'=>count($sets)===3,'bounded_catalog'=>count($sets[0])===2,'first_sku'=>$sets[0][0]['sku']==='O-RING-42',
        'second_sku'=>$sets[0][1]['sku']==='CLAMP-43','empty_is_result'=>$sets[1]===[],
        'positions'=>(int)$sets[2][0]['positions']===2,'units'=>(int)$sets[2][0]['units']===7,
        'next_sequence'=>array_column($transitions,'value')===[true,true,false],'terminal_clean'=>$next['code']===0&&$next['facility']===0&&!$next['warning']];
    $observations=['result_sets'=>$sets,'next_results'=>$transitions,'csv'=>$records];

} finally {

    if (is_resource($db)) {try {$removed=c173Cleanup($db,$owned);} finally {c173Close($db);}}
}

c173Report('cub173-multiple-results','Получить список, пустую подборку и итог одним заранее составленным SQL-пакетом',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Первый результат — O-RING-42: 5; CLAMP-43: 2
Второй результат — 0 строк — подборка пуста
Третий результат — 2 позиции, 7 единиц
Переходы — true → true → false, код0
Файл — multiple-results.csv

Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.

Открыть результат выполнения · Посмотреть данные JSON · Скачать составной отчёт

Границы решения

Пакет заранее составлен приложением из собственных имён; пользовательский SQL не склеивается с точками с запятой. Для пользовательских значений выбирайте подготовленные запросы. QUERY_ALL не означает автоматическую бизнес-транзакцию и не заменяет commit/rollback. На больших данных COUNT/SUM выполняются в SQL, а страницы каталога ограничиваются WHERE/ORDER BY/LIMIT.

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

Какой шаг сломается, если завершить весь цикл при первой пустой выборке?

Разбор

Будет потерян третий SELECT с итогом. Завершать чтение строк текущего результата и переходить к следующему следует отдельно. Конец набора строк — не конец списка результатов.

Освободить подготовленный запрос, отменить пробу и перечитать подтверждённое изменение

Оператор пробует изменить остаток с5 на4, затем отменяет пробу. Подготовленный запрос уже не нужен, но его закрытие не отменяет SQL-изменение. После правильного rollback возвращается5; новая подтверждённая поставка даёт7, что проверяет отдельное вновь открытое соединение.

Путь данных и полученный результат
  1. qty5 → prepare/bind/execute qty4
  2. close_prepare: запрос закончен, транзакция ещё содержит qty4
  3. rollback: SQL вернулся к qty5
  4. Новая команда qty7 → commit → close соединения
  5. Новое соединение читает подтверждённые7

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

$request = cubrid_prepare($db, 'UPDATE stock SET qty=? WHERE id=42');
if ($request === false) { throw new RuntimeException('Нет запроса'); }
try {
    cubrid_bind($request, 1, 4, 'number');
    cubrid_execute($request);
} finally {
    cubrid_close_prepare($request);
    $request = null;
}
cubrid_rollback($db); // Отмена данных — отдельное действие.

prepare возвращает ресурс SQL-запроса. finally освобождает именно его; присваивание null убирает ссылку на закрытый ресурс. rollback действует на транзакцию соединения $db, а close — на всё соединение. Полный проект проверяет результаты bind/execute/close, число изменённых строк и перечитывает данные после каждой смысловой границы.

Что получится после запуска

НаблюдениеРезультат
После close_prepare4
После rollback5
После commit и нового соединения7
Запрос и соединение завершеныда
Файлresource-lifetime.csv
Самостоятельный проект, исходник и архив
Освободить подготовленный запрос, отменить пробу и перечитать подтверждённое изменениеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

try {
    $config=c173Config();$db=c173Connect();$runtime=c173Runtime($db);

function checked173(callable $action) {
    $call=c173Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c173Failure('Действие не завершено',$call);}
    return $call['value'];
}
function stock173($db,array &$owned,string $label): string {
    return c173CreateTable($db,$owned,$label,[
        ['id'=>42,'sku'=>'O-RING-42','title'=>'Уплотнитель №42','category'=>'parts','price_cents'=>190,'qty'=>5,'note'=>null],
        ['id'=>43,'sku'=>'CLAMP-43','title'=>'Хомут 43','category'=>'parts','price_cents'=>90,'qty'=>2,'note'=>'']]);
}
function csv173(string $name,array $records): void {
    $fp=fopen(__DIR__.'/'.$name,'wb');if (!is_resource($fp)) {throw new RuntimeException('CSV не открыт');}
    try {foreach($records as $row) {if (fputcsv($fp,$row)===false) {throw new RuntimeException('CSV не записан');}}} finally {fclose($fp);}
}

    $name=stock173($db,$owned,'resource_order');$table=c173Qualified($name);
    $request=checked173(static function() use ($db,$table) {return cubrid_prepare($db,'UPDATE '.$table.' SET qty=? WHERE id=42');});
    try {
        checked173(static function() use ($request) {return cubrid_bind($request,1,4,'number');});
        checked173(static function() use ($request) {return cubrid_execute($request);});
        $affected=cubrid_affected_rows($request);
    } finally {$released=checked173(static function() use ($request) {return cubrid_close_prepare($request);});$request=null;}
    $pending=c173Read($db,'SELECT qty FROM '.$table.' WHERE id=42 LIMIT 1');
    c173Rollback($db);
    $restored=c173Read($db,'SELECT qty FROM '.$table.' WHERE id=42 LIMIT 1');c173Commit($db);
    $saved=c173Command($db,'UPDATE '.$table.' SET qty=? WHERE id=42',[7]);c173Commit($db);
    $closed=checked173(static function() use ($db) {return cubrid_close($db);});$db=null;
    $db=c173Connect();$fresh=c173Read($db,'SELECT qty FROM '.$table.' WHERE id=42 LIMIT 1');c173Commit($db);
    csv173('resource-lifetime.csv',[['stage','qty'],['request_closed',(int)$pending[0]['qty']],['rollback',(int)$restored[0]['qty']],['reconnected',(int)$fresh[0]['qty']]]);
    $rows=[['После close_prepare',4],['После rollback',5],['После commit и нового соединения',7],['Запрос и соединение завершены',true],['Файл','resource-lifetime.csv']];
    $checks=['one_pending_change'=>$affected===1,'request_closed'=>$released===true,'close_not_commit'=>(int)$pending[0]['qty']===4,
        'rollback_restores'=>(int)$restored[0]['qty']===5,'saved_one'=>$saved===1,'connection_closed'=>$closed===true,
        'fresh_saved_read'=>(int)$fresh[0]['qty']===7,'connection_available'=>is_resource($db)];
    $observations=['pending'=>$pending,'restored'=>$restored,'fresh_connection_read'=>$fresh,'close_prepare'=>$released,'close_connection'=>$closed];

} finally {

    if (is_resource($db)) {try {$removed=c173Cleanup($db,$owned);} finally {c173Close($db);}}
}

c173Report('cub173-resource-lifetime','Освободить подготовленный запрос, отменить пробу и перечитать подтверждённое изменение',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
После close_prepare — 4
После rollback — 5
После commit и нового соединения — 7
Запрос и соединение завершены — да
Файл — resource-lifetime.csv

Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.

Открыть результат выполнения · Посмотреть данные JSON · Скачать границы завершения

Границы решения

Не вызывайте API повторно на закрытом ресурсе ради проверки: это другая ошибка, а не способ убедиться в commit. Сначала освобождайте запросы, затем явно заканчивайте транзакцию, затем закрывайте соединение. В библиотеке роль владельца ресурса должна быть известна; чужой общий $db нельзя закрывать из функции, которая получила его для одного SELECT.

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

Можно ли заменить rollback на close_prepare?

Разбор

Нет. Сохранённое наблюдение qty4 после close_prepare показывает, что закрытие запроса не отменяет изменение. Только rollback вернул5. Так же commit подтверждает данные, но не означает, что все PHP-ресурсы уже освобождены.

Найти действие по его границе

ФункцияДля чего нужна
cubrid_pconnectПодключение через persistent API и проверка реального срока жизни
cubrid_pconnect_with_urlURL-параметры persistent-подключения
cubrid_closeЗакрыть соединение после явного завершения транзакции
cubrid_close_prepareОсвободить request handle, не подменяя commit/rollback
cubrid_next_resultПерейти к следующему результату SQL-пакета

Следующий шаг: сопровождать совместимые вызовы без потери данных

Вопрос о сохранении соединения между вызовами PHP ставит задачу уменьшения connect/disconnect. Мы разделяем реальный worker, request boundary и исходник драйвера; сокетное решение из ответа не подменяет CUBRID API.

Вопрос о транзакции без commit/rollback и вопрос о конце или ошибке next_result относятся к MySQLi. Они дают реальные затруднения читателя; механика CUBRID здесь объяснена и проверена отдельно, без переноса чужих настроек mysqlnd или more_results.

Следующий шаг — совместимые query/unbuffered_query, ошибки и ID новой записи. Для новых пользовательских фильтров сохраняйте prepare/bind, а для ограничений ожидания используйте сроки запроса и блокировки.

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

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

cubrid_pconnectПодключение через persistent API и проверка реального срока жизни

PECL CUBRID >= 8.3.1; поведение PHP7-порта 11.2.0.0002 явно отделено от замысла persistent API. · cubrid

cubrid_pconnect(string $host, int $port, string $dbname, string $userid = ?, string $passwd = ?): resource|false

Подключить запрос каталога через документированный persistent API и отдельно проверить фактический срок жизни подключения.

ПараметрЧто передать
$hostСтрока: имя или IP сервера CAS/broker. Не адрес HTML-страницы.
$portЦелое число BROKER_PORT из cubrid_broker.conf, не порт HTTP.
$dbnameИмя выбранной базы. Должно поступать из конфигурации или разрешённого списка.
$useridНеобязательное имя пользователя базы. В проектах передаётся явно.
$passwdНеобязательный пароль этого пользователя. В проектах берётся из закрытого окружения.

Результат: Ресурс соединения при успехе; false при отказе. Предупреждение и код нужно сохранить сразу. Успешный ресурс не доказывает ни повторное использование прежнего канала, ни права на конкретную таблицу.

При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c173Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте результат через === false: пустой набор строк и число 0 сами по себе не означают ошибку.

Доступна с PECL CUBRID 8.3.1. Три первых аргумента обязательны; у двух последних PHP Manual показывает необязательность без универсального значения по умолчанию. Для воспроизводимого приложения задавайте все пять. Не переносите дополнительные аргументы cubrid_connect в публичный контракт pconnect только потому, что одна реализация делит внутренний обработчик.

Минимальная запись

$db = cubrid_pconnect($host, $port, $database, $user, $password);
if ($db === false) { throw new RuntimeException('Подключение не создано'); }

Соединение позволяет отправлять SQL, но не содержит автоматически полученных товаров. Следующий шаг — ограниченный SELECT. Пароль не следует печатать вместе с URL при диагностике.

Применение 1: обновляемая карточка товара

$request = cubrid_execute($db, 'SELECT sku,qty FROM stock WHERE id=42 LIMIT 1');
if ($request === false) { throw new RuntimeException('Карточка не прочитана'); }
try { $row = cubrid_fetch_assoc($request); }
finally { cubrid_close_prepare($request); }

Два HTTP-открытия каталога показывают остаток5, затем7 после отдельного commit поставки. Читатель каждый раз получает нужную строку SQL; сохранённый ресурс не является кэшем ответа SELECT.

Применение 2: отдельная транзакция каждого запроса

if (!cubrid_set_autocommit($db, false)) { throw new RuntimeException('Режим не задан'); }
try {
    // Пробный расчёт/резерв внутри одного HTTP-запроса.
} finally {
    cubrid_rollback($db);
}

Этот порядок применим и к URL-варианту в проекте отмены резерва. Если операция должна сохраниться, успешная ветка явно вызывает commit, а ошибочная — rollback. Сохранённые session-параметры, уровни изоляции, временные состояния и открытые запросы нельзя считать пользовательским хранилищем.

PHP Manual описывает постоянный канал: повторный вызов с тем же адресом базы и пользователем ищет открытое соединение, а завершение запроса и close/disconnect не должны закрывать такой канал. Это документированный замысел API, а не подтверждённая оптимизация данного профиля. В PHP7-исходнике cubrid 11.2.0.0002 cache lookup выполняется только при !persistent, pconnect-ресурс попадает в regular_list, а его деструктор вызывает cci_disconnect. В отдельных HTTP-запросах одного worker SQL marker не сохранился, ручной режим снова стал autocommit=true, незавершённый резерв был отменён. В этой версии нельзя обещать PHP persistent cache на основании названия функции. Мы не измеряли глобальный пул CCI/CAS и не выдаём эти наблюдения за поведение всех версий расширения.

Даже корректно реализованный persistent cache ограничен worker-процессом: другой процесс не обязан выдать тот же канал. Обычный CLI-запуск завершается целиком, а однажды выданный DB-канал нельзя закрепить за конкретным браузером. Постоянные каналы расходуют серверные подключения; их число зависит от числа workers и комбинаций реквизитов. Для решения о производительности нужна отдельная нагрузочная проверка, которой эти учебные GET не являются. При отказе сначала проверяют адрес, порт, доступность broker и учётные данные, затем SQL-права; диагностика подключения объясняет это отдельно.

cubrid_pconnect_with_urlURL-параметры persistent-подключения

PECL CUBRID >= 8.3.1; поведение PHP7-порта 11.2.0.0002 явно отделено от замысла persistent API. · cubrid

cubrid_pconnect_with_url(string $conn_url, string $userid = ?, string $passwd = ?): resource|false

Задать persistent-подключение строкой конфигурации и ограничить ожидание входа, сохранив пароль отдельным аргументом.

ПараметрЧто передать
$conn_urlСтрока CUBRID:host:port:database:user:password:[?properties]. Пустые поля сохраняют разделяющие двоеточия.
$useridНеобязательное имя пользователя; удобно передать отдельно от URL.
$passwdНеобязательный пароль; отдельно передаваемая строка допускает символы, конфликтующие с разделителями URL.

Результат: Ресурс соединения либо false при отказе. URL не превращает подключение в веб-запрос: это строка параметров native CCI.

При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c173Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте результат через === false: пустой набор строк и число 0 сами по себе не означают ошибку.

PECL CUBRID >=8.3.1. На странице PHP Manual сокращённая грамматика пропускает порт в одной строке, но примеры и CCI используют host:port:database. В этой главе показана рабочая полная форма. Сама возможность failover не означает, что прикладную транзакцию можно продолжить без проверки после смены сервера.

Минимальная запись

$url = 'CUBRID:127.0.0.1:33000:shop:::?login_timeout=3000';
$db = cubrid_pconnect_with_url($url, $user, $password);
if ($db === false) { throw new RuntimeException('Вход не выполнен'); }
Свойство URL из PHP ManualСмысл и единицы
alhosts=host:port[,host:port]Резервные broker-адреса; при невозможности активного входа пробуются по порядку.
rctime=600Интервал попыток вернуться к активному broker, секунды; документированное значение по умолчанию600. Переключение связано с завершением транзакционной работы; не является прозрачным продолжением заказа.
login_timeout=3000Предел ожидания входа, миллисекунды. Документированный0 означает отсутствие такого ограничения.
query_timeout=5000Предел запроса в миллисекундах. Запрос отмены не гарантирует, что сервер ещё не успел успешно завершить действие.
disconnect_on_query_timeout=falseДокументированное значение по умолчаниюfalse; регулирует немедленный возврат ошибки при timeout. Отказ клиента нельзя без проверки считать доказательством отсутствия SQL-эффекта.

В старом примере Manual встречается опечатка althost; в таблице используется имя alhosts из описания и CCI. В этом пакете не поднимаются резервные серверы и не исполняется failover. Реально проверенные временные ограничения запросов находятся в главе о настройках и сроках запросов.

Применение 1: резерв без утечки пароля в URL

$url = 'CUBRID:127.0.0.1:33000:shop:::?login_timeout=3000';
$db = cubrid_pconnect_with_url($url, $user, $password);
// $password может содержать ?: — они не встроены в строку URL.
if ($db === false) { throw new RuntimeException('Нет соединения для резерва'); }
cubrid_set_autocommit($db, false);

Резерв с двумя реальными HTTP-запросами демонстрирует URL-вызов, режим на входе и отмену неподтверждённых данных. Встраивать ?: в пароль внутри URL нельзя: это структурные разделители. Удалять лишние на вид двоеточия из пустых полей тоже нельзя.

Применение 2: один адрес конфигурации для каталога

$catalogUrl = 'CUBRID:127.0.0.1:33000:shop:::?login_timeout=3000&query_timeout=5000';
$catalogDb = cubrid_pconnect_with_url($catalogUrl, $user, $password);
if ($catalogDb === false) { throw new RuntimeException('Каталог недоступен'); }
// Затем SELECT только необходимых полей и явное завершение транзакции.

Так можно заменить способ задания адреса в каталоге товара, сохранив SQL-проекцию и явные границы. Код фрагмента объясняет параметры; проверенный полный URL-проект выше использует только login_timeout. Само добавление URL не обеспечивает повторное использование соединения.

PHP Manual описывает постоянный канал: повторный вызов с тем же адресом базы и пользователем ищет открытое соединение, а завершение запроса и close/disconnect не должны закрывать такой канал. Это документированный замысел API, а не подтверждённая оптимизация данного профиля. В PHP7-исходнике cubrid 11.2.0.0002 cache lookup выполняется только при !persistent, pconnect-ресурс попадает в regular_list, а его деструктор вызывает cci_disconnect. В отдельных HTTP-запросах одного worker SQL marker не сохранился, ручной режим снова стал autocommit=true, незавершённый резерв был отменён. В этой версии нельзя обещать PHP persistent cache на основании названия функции. Мы не измеряли глобальный пул CCI/CAS и не выдаём эти наблюдения за поведение всех версий расширения.

Никогда не принимайте готовый connection URL от посетителя. Конфигурация задаёт сервер назначения и реквизиты; доступ к конкретному SKU проверяется отдельно. Таймауты входа, запроса и ожидания блокировки — разные ограничения.

cubrid_closeЗакрыть соединение после явного завершения транзакции

PECL CUBRID >= 8.3.1; поведение PHP7-порта 11.2.0.0002 явно отделено от замысла persistent API. · cubrid

cubrid_close(resource $conn_identifier = ?): bool

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

ПараметрЧто передать
$conn_identifierНеобязательный ресурс соединения. При отсутствии аргумента PHP Manual подразумевает последнее соединение, открытое cubrid_connect. Явный аргумент надёжнее в коде с несколькими базами.

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

При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c173Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте результат через === false: пустой набор строк и число 0 сами по себе не означают ошибку.

PECL CUBRID >=8.3.1. Manual описывает завершение текущей транзакции, закрытие connection handle, отключение сервера и оставшихся request handles; функция похожа на cubrid_disconnect. Формулировка «завершение» не заменяет ваше решение commit или rollback. Перед close явно закончите транзакцию с нужным смыслом.

Минимальная запись

if (!cubrid_commit($db)) { throw new RuntimeException('Данные не подтверждены'); }
if (!cubrid_close($db)) { throw new RuntimeException('Соединение не закрыто'); }
$db = null;

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

// UPDATE уже проверен, request освобождён.
cubrid_commit($db);
cubrid_close($db);
$db = null;
$fresh = cubrid_connect($host, $port, $database, $user, $password, true);
// Новый SELECT проверяет сохранённое количество.

Проект со сроками ресурсов подтверждает7, закрывает соединение и читает7 заново. Это проверка сохранённых данных, а не повторный вызов по недействительному ресурсу.

Применение 2: завершить отменённую операцию

try {
    // Операция завершилась бизнес-отказом; её запрос уже освобождён.
    if (!cubrid_rollback($db)) { throw new RuntimeException('Отмена не подтверждена'); }
} finally {
    cubrid_close($db);
    $db = null;
}

Отмена резерва в HTTP-проекте учит явному rollback; такой шаблон закрытия нужен владельцу обычного короткого соединения. HTTP-наблюдение специально не закрывает pconnect в первом запросе, чтобы исследовать request boundary; не переносите это исключение в произвольную бизнес-операцию.

Есть важная версия границы: страницы pconnect обещают, что close/disconnect не закрывают persistent link. В проверенном PHP7-порте close действительно проходит через zend_list_close, а persistent-деструктор вызывает cci_disconnect; реальная подготовка получила true и недействительный ресурс. Поэтому проект не полагается на историческое обещание сохранения pconnect. Подробность — контракт pconnect и фактический профиль.

Close соединения и close_prepare запроса решают разные задачи. Библиотечная функция, которая получила чужой $db ради чтения, обычно освобождает свой запрос и возвращает строку, не закрывая канал владельца. При нескольких соединениях используйте явные переменные и закрывайте каждое отдельно; «последнее» может оказаться уже другим.

cubrid_close_prepareОсвободить request handle, не подменяя commit/rollback

PECL CUBRID >= 8.3.0; поведение PHP7-порта 11.2.0.0002 явно отделено от замысла persistent API. · cubrid

cubrid_close_prepare(resource $req_identifier): bool

Освободить закончившийся request handle: подготовленный запрос или результат обычного execute.

ПараметрЧто передать
$req_identifierОбязательный ресурс запроса от prepare/execute. Это не ресурс соединения $db и не массив полученных строк.

Результат: PHP Manual описывает true при успехе. Alias выполняет то же закрытие, что close_request; недопустимый или уже закрытый ресурс находится вне поддержанного контракта. Проверенный исходник не содержит надёжной проверки такого аргумента перед обращением к ресурсу, поэтому безопасный false на нём не обещается. Передавайте только действующий request, принадлежащий вашему коду, и не проверяйте закрытие повторным вызовом.

При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c173Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте результат через === false: пустой набор строк и число 0 сами по себе не означают ошибку.

PECL CUBRID >=8.3.0. Это alias cubrid_close_request: другое имя того же действия, не особая разновидность commit. Освобождается связанная с запросом память и request handle. Параметры подготовленного запроса после этого не переиспользуются; для повторного выполнения держат его открытым до конца серии или создают новый.

Минимальная запись

if (!cubrid_close_prepare($request)) { throw new RuntimeException('Запрос не освобождён'); }
$request = null;

Применение 1: закончить подготовленное изменение

$request = cubrid_prepare($db, 'UPDATE stock SET qty=? WHERE id=42');
if ($request === false) { throw new RuntimeException('Нет запроса'); }
try {
    if (!cubrid_bind($request, 1, 4, 'number') || !cubrid_execute($request)) {
        throw new RuntimeException('Изменение не выполнено');
    }
} finally { cubrid_close_prepare($request); $request = null; }
// commit/rollback ещё требуется отдельно.

Пробное изменение остатка оставляет4 после закрытия запроса и возвращает5 только после rollback. Полученный ранее PHP-массив можно читать после close_prepare: это уже данные приложения, а не живой курсор.

Применение 2: закончить весь список результатов

try {
    // Читать текущий набор, затем cubrid_next_result($request).
    // Сохранить только необходимые массивы строк/итоги.
} finally {
    cubrid_close_prepare($request);
    $request = null;
}

В составном отчёте request остаётся один, хотя внутри него меняются три набора. Освободить его после первого набора означало бы потерять доступ к следующим. В HTTP-каталоге каждый короткий SELECT освобождается в том запросе HTTP, где был открыт.

Освобождение request не гарантирует освобождения всех блокировок транзакции и не заканчивает канал к серверу. Для LOB-ресурсов, выданных запросом, дополнительно действуют ограничения времени жизни порционного LOB-чтения. Не сохраняйте request resource для следующего HTTP-запроса и не передавайте его в JSON.

cubrid_next_resultПерейти к следующему результату SQL-пакета

PECL CUBRID >= 8.4.0; поведение PHP7-порта 11.2.0.0002 явно отделено от замысла persistent API. · cubrid

cubrid_next_result(resource $result): bool

Переключить один request handle на результат следующей SQL-команды заранее составленного пакета.

ПараметрЧто передать
$resultОбязательный ресурс от cubrid_execute, выполненного с флагом CUBRID_EXEC_QUERY_ALL. Первый результат уже текущий.

Результат: true — следующий результат доступен; false — переход не состоялся. PHP Manual называет false отказом; pinned source также возвращает false при обычном конце CAS_ER_NO_MORE_RESULT_SET, не устанавливая ошибку. Поэтому сразу сохраняют код, facility и предупреждение, чтобы отличить конец от ошибки.

При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c173Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте результат через === false: пустой набор строк и число 0 сами по себе не означают ошибку.

PECL CUBRID >=8.4.0. Функция меняет текущие столбцы, число строк и тип команды внутри существующего запроса; она не возвращает новый массив строк. fetch читает строки текущего SELECT; affected_rows описывает INSERT/UPDATE/DELETE. Не вызывайте fetch как универсальное чтение любой SQL-команды.

Минимальная запись

$r = cubrid_execute($db, 'SELECT 5 AS qty; SELECT 7 AS total', CUBRID_EXEC_QUERY_ALL);
if ($r === false) { throw new RuntimeException('Нет пакета'); }
$first = cubrid_fetch_assoc($r);
$next = cubrid_next_result($r);
$code = cubrid_error_code(); // Сразу, до следующего обращения к драйверу.
if ($next === true) { $second = cubrid_fetch_assoc($r); }
elseif ($code !== 0) { throw new RuntimeException('Следующий результат ошибочен'); }
cubrid_close_prepare($r);

Применение 1: каталог, пустая подборка и сводка

Полный составной отчёт получает две позиции, пустую подборку отрицательного остатка и итог7. Между результатами получается true/true, после третьего — false с чистой диагностикой. Пустой массив строк не является ошибкой и не завершает весь пакет.

// После чтения первого набора:
if (cubrid_next_result($r)) {
    $emptyRow = cubrid_fetch_assoc($r); // false: в этом SELECT нет строк.
    // Диагностика fetch и переход к третьему набору — отдельные действия.
}

Применение 2: квитанция изменения и итог

$sql = 'UPDATE stock SET qty=qty+1 WHERE id=42; SELECT qty FROM stock WHERE id=42';
$r = cubrid_execute($db, $sql, CUBRID_EXEC_QUERY_ALL);
if ($r === false) { throw new RuntimeException('Пакет изменения не выполнен'); }
$changed = cubrid_affected_rows($r);
if (cubrid_next_result($r) !== true) { throw new RuntimeException('Нет подтверждающей строки'); }
$row = cubrid_fetch_assoc($r);
// Проверить changed и row, дочитать переходы, закрыть request, затем commit/rollback.

В реальной подготовке проверен переход SELECT → пустой SELECT → UPDATE → сводка и rollback всего изменения. Короткий вариант выше показывает применение той же механики к квитанции; это не отдельный опубликованный самостоятельный проект. Правила разделения request и транзакции подробно видны в проекте закрытия ресурсов и главе о транзакциях.

QUERY_ALL не является защитой от SQL-инъекции и не превращает набор команд в независимые транзакции. Не собирайте такой SQL из произвольного ввода; значения пользовательского фильтра передаются через prepare/bind. Результаты имеют разные столбцы: нельзя заранее применить одну структуру строки ко всем. При ошибке позднего шага сохраняют диагностику и явно отменяют неподтверждённые изменения. Успешный первый SELECT ещё не подтверждает успех всего пакета.

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

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

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

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

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