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

Дерево руководства
Глава 85 · После параметризованного SELECT
CUBRID: строки для экрана, метаданные столбцов и размер страницы
CUBRID: четыре формы строки, SQL-метаданные и CSV, размер страницы и общий COUNT, освобождение буфера и request.
Из результата SQL в понятный экран и файл
Экрану каталога нужны понятные названия, нулевой остаток и подпись «показано 2 из 3». Экспорту нужны четыре поля в согласованном порядке. Эти требования решаются после точного SQL: PHP превращает уже выбранные строки в форму для пользователя и освобождает ресурсы после работы.
Здесь request — ресурс выполненного SQL, строка — отдельное значение PHP, а метаданные — описание колонок. Они отвечают на разные вопросы. SELECT определяет поля, фильтр и порядок; fetch получает очередную строку; column_names/types описывают поля; num_rows/num_cols измеряют текущий результат. Ни одна из этих функций не требует выгрузить и сортировать весь каталог в PHP.
- SELECT: id 1, Кабель, qty 0, note NULL
- CUBRID_ASSOC: имена ключей → значения; CUBRID_NUM: позиции → значения
- BOTH: обе системы ключей; OBJECT: те же значения как свойства
- Шаблон показывает Кабель / остаток 0; NULL не означает отсутствие товара
- После чтения освобождается request, соединение может обслужить следующую задачу
Представьте результат как небольшую накладную: у неё есть заголовки, строки и порядок ячеек. Это полезная модель для NUM и метаданных, но драйвер не обязан хранить весь большой ответ как лист PHP-массива. Ограничивайте данные в SQL и отличайте строки результата от буферов и ресурсов. Прежде полезно пройти подключение CUBRID, параметры и выборку каталога и ключи массива.
| Задача | Что человек получит | Самостоятельная программа |
|---|---|---|
| Карточка и способ доступа | Кабель / 0 / NULL без удвоенного вывода | Четыре формы строки |
| Согласованный экспорт | Заголовок и две конкретные CSV-строки | Имена, типы и порядок |
| Страница каталога | Розетка и Клемма; показано 2 из 3 | Два разных счётчика |
| Изменение предпросмотра | Кабель 0 → фильтр от 5 → Клемма 7 | Буфер и срок жизни request |
Показать одну карточку четырьмя способами без двойного вывода
Карточке товара нужны название, остаток и примечание. Если перебрать все значения режима BOTH, название появится дважды: по номеру и по имени. Разберём на «Кабель, остаток 0, примечания нет», что именно приходит в PHP и какой способ удобен для экрана, файла или объекта.
- SQL выбрал четыре поля: item_id / item_title / available / comment_text
- Одна строка: 1 / Кабель / 0 / SQL NULL
- NUM → [0,1,2,3]; ASSOC → четыре именованных ключа
- BOTH → два ключа для каждого поля; OBJECT → свойства с именами aliases
- На экране один Кабель и остаток 0; отсутствие примечания не теряет свойство
Минимальная запись и её смысл
$row = cubrid_fetch($request, CUBRID_ASSOC);
$error = cubrid_error_code(); // сразу после fetch
if ($row === null || $error !== 0) { throw new RuntimeException("Ошибка чтения"); }
if ($row !== false) { echo $row["item_title"], " — ", $row["available"], "\n"; }$request — уже успешно выполненный SELECT, не подключение. Второй аргумент выбирает форму одной строки. Квадратные скобки читают значение ключа массива; у объекта тот же доступ выглядит как $item->item_title. Каждый fetch продвигается к следующей строке: поэтому полная программа выполняет один и тот же ограниченный SELECT отдельно для каждого режима. Она не получает четыре представления повторными fetch одного курсора.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| NUM: значение позиции 1 | Кабель |
| ASSOC: значение item_title | Кабель |
| BOTH: SQL-полей | 4 |
| BOTH: ключей PHP | 8 |
| OBJECT: свойство available | 0 |
| NULL: свойство существует | да |
| NULL: isset вернул true | нет |
| Четыре режима получили одну и ту же карточку | да |
Полный код, файлы и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);
$name=c166CreateTable($db,$owned,'shapes',[
['id'=>1,'sku'=>'CAB-166','title'=>'Кабель','category'=>'cables','price_cents'=>12900,'qty'=>0,'note'=>null]
]);$table=c166Qualified($name);
$sql='SELECT id AS item_id,title AS item_title,qty AS available,note AS comment_text FROM '.$table.' WHERE id=1';
$views=[];$ended=[];
foreach(['NUM'=>CUBRID_NUM,'ASSOC'=>CUBRID_ASSOC,'BOTH'=>CUBRID_BOTH,'OBJECT'=>CUBRID_OBJECT] as $label=>$mode) {
$request=cubrid_execute($db,$sql);
if($request===false){c166Failure('Карточка не выбрана');}
try {
$value=cubrid_fetch($request,$mode);
if($value===false||$value===null){throw new RuntimeException('Ожидалась одна карточка.');}
$views[$label]=$value;
$end=cubrid_fetch($request,$mode);$endCode=cubrid_error_code();
if($end===null||$endCode!==0){throw new RuntimeException('Чтение карточки завершилось ошибкой.');}
$ended[$label]=$end===false;
} finally {c166Release($request);}
}
$request=cubrid_execute($db,$sql);
if($request===false){c166Failure('Не проверен режим по умолчанию');}
try {$default=cubrid_fetch($request);} finally {c166Release($request);}
$numeric=['1','Кабель','0',null];
$assoc=['item_id'=>'1','item_title'=>'Кабель','available'=>'0','comment_text'=>null];
$object=$views['OBJECT'];$both=$views['BOTH'];
$sameBoth=count($both)===8;
foreach(array_keys($assoc) as $position=>$key){$sameBoth=$sameBoth&&array_key_exists($position,$both)&&array_key_exists($key,$both)&&$both[$position]===$numeric[$position]&&$both[$key]===$assoc[$key];}
$rows=[['NUM: значение позиции 1',$views['NUM'][1]],['ASSOC: значение item_title',$views['ASSOC']['item_title']],
['BOTH: SQL-полей',4],['BOTH: ключей PHP',count($both)],['OBJECT: свойство available',$object->available],
['NULL: свойство существует',property_exists($object,'comment_text')],['NULL: isset вернул true',isset($object->comment_text)],
['Четыре режима получили одну и ту же карточку',$views['NUM']===$numeric&&$views['ASSOC']===$assoc&&get_object_vars($object)===$assoc]];
$checks=['numeric_shape'=>$views['NUM']===$numeric,'associative_shape'=>$views['ASSOC']===$assoc,
'both_keys'=>$sameBoth,'default_is_both'=>$default===$both,'object_shape'=>$object instanceof stdClass&&get_object_vars($object)===$assoc,
'null_distinguished'=>property_exists($object,'comment_text')&&!isset($object->comment_text),'zero_retained'=>$object->available==='0',
'eof_each'=>!in_array(false,$ended,true),'requests_released'=>$request===null];
$observations=['SQL_columns'=>array_keys($assoc),'NUM'=>$views['NUM'],'ASSOC'=>$views['ASSOC'],'BOTH'=>$both,
'OBJECT_properties'=>get_object_vars($object),'OBJECT_class'=>get_class($object),'default'=>$default,
'end_of_result'=>$ended,'independent_queries_for_same_row'=>5];
} finally {
if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}
c166Report('cub166-fetch-shapes','Показать одну карточку четырьмя способами без двойного вывода',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
NUM: значение позиции 1 — Кабель
ASSOC: значение item_title — Кабель
BOTH: SQL-полей — 4
BOTH: ключей PHP — 8
OBJECT: свойство available — 0
NULL: свойство существует — да
NULL: isset вернул true — нет
Четыре режима получили одну и ту же карточку — да
Распакуйте архив целиком и выполните example.php в изолированном native-окружении по README.txt. lab.php проверяет собственный стенд, создаёт только принадлежащую примеру c166-таблицу, формирует result.html/result.json и удаляет таблицу в finally. metrics.json содержит проверки и факт очистки. Настройки соединения и секреты остаются вне публичных результатов. CSV этой главы показан как точный текст в отчёте; он не требует отдельной загрузки файла.
Открыть фактический результат · Открыть данные JSON.
Ограничения и ошибки
BOTH не означает две SQL-строки и не умножает остаток в базе. Это две системы адресации полей в одном PHP-массиве. Для обычного шаблона выбирайте ASSOC, для позиционного экспорта — NUM. PHP-строка "0" не пропадает: не применяйте empty как проверку наличия товара. isset свойства с NULL вернёт false, а property_exists — true. Объект stdClass здесь только контейнер данных; собственные бизнес-методы у него не появляются.
В обсуждении PDO одинаковые значения неожиданно появились под числовыми и именованными ключами. Исходное обсуждение. Проблема помогает объяснить BOTH: это два способа доступа к полям одной строки. Нормативные режимы и код здесь относятся к native CUBRID, а не к PDO.
Небольшое изменение задачи
Нужно вывести каждое поле ровно один раз через foreach. Какой режим выбрать и что поменяется при перестановке столбцов SELECT?
Разбор решения
ASSOC даёт по одному именованному ключу на поле; NUM — по одной позиции. В NUM смысл позиции зависит от порядка SELECT, поэтому перестановка меняет row[1]. Именованный доступ item_title устойчив к перестановке, пока alias сохранён. BOTH при переборе всех значений для такого экрана не нужен.
Собрать заголовки и CSV в том же порядке, что SQL-столбцы
Для выгрузки склада нужны четыре колонки в договорённом порядке: артикул, название, цена в копейках и остаток. Получим имена именно результата SELECT, соединим их с позиционными строками и увидим готовый CSV. Кабель с нулём не исчезнет, а 12900 останется ценой в копейках.
- SELECT sku AS article, title AS label, price_cents AS unit_cents, qty AS in_stock
- column_names → article / label / unit_cents / in_stock
- column_types → varchar / varchar / integer / integer; num_cols → 4
- fetch NUM → CAB-166 / Кабель / 12900 / 0 в том же порядке
- fputcsv → заголовок и две настоящие строки, показанные в отчёте
Минимальная запись и её смысл
$names = cubrid_column_names($request);
$types = cubrid_column_types($request);
$count = cubrid_num_cols($request);
if ($names === false || $types === false || $count === false) {
throw new RuntimeException("Нет описания столбцов");
}
echo $names[0], " / ", $types[0], "\n"; // article / varcharМетаданные — описание полей, а не ещё одна товарная строка. Позиция 0 в names, types и NUM-строке относится к одному выбранному столбцу. AS задаёт его имя в результате, не переименовывает колонку исходной таблицы. num_cols считает поля SELECT; семь колонок учебной таблицы не мешают выбрать только четыре. fputcsv получает массив полей и отдельно отвечает за разделитель и кавычки.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Столбцов в результате | 4 |
| Порядок имён | article, label, unit_cents, in_stock |
| SQL-типы | varchar, varchar, integer, integer |
| CSV: заголовок | article;label;unit_cents;in_stock |
| CSV: первая строка | CAB-166;Кабель;12900;0 |
| CSV: вторая строка | CLP-166;Клемма;900;7 |
| PHP-тип выбранного остатка | string |
Полный код, файлы и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);
$name=c166CreateTable($db,$owned,'metadata',[
['id'=>1,'sku'=>'CAB-166','title'=>'Кабель','category'=>'cables','price_cents'=>12900,'qty'=>0,'note'=>null],
['id'=>2,'sku'=>'CLP-166','title'=>'Клемма','category'=>'mounting','price_cents'=>900,'qty'=>7,'note'=>'']
]);$table=c166Qualified($name);
$request=cubrid_execute($db,'SELECT sku AS article,title AS label,price_cents AS unit_cents,qty AS in_stock FROM '.$table.' ORDER BY id LIMIT 2');
if($request===false){c166Failure('Отчёт не выбран');}
$values=[];$csv=fopen('php://temp','w+');
if($csv===false){c166Release($request);throw new RuntimeException('Не открыт буфер CSV.');}
try {
$names=cubrid_column_names($request);$types=cubrid_column_types($request);$columnCount=cubrid_num_cols($request);
if(!is_array($names)||!is_array($types)||$columnCount===false){throw new RuntimeException('Метаданные не прочитаны.');}
if(count($names)!==$columnCount||count($types)!==$columnCount){throw new RuntimeException('Описание столбцов не согласовано.');}
if(fputcsv($csv,$names,';','"','')===false){throw new RuntimeException('Заголовок CSV не записан.');}
while(true){
$item=cubrid_fetch($request,CUBRID_NUM);
$fetchCode=cubrid_error_code();
if($item===null||$fetchCode!==0){throw new RuntimeException('CSV прочитан не полностью.');}
if($item===false){break;}
if($item===null||count($item)!==$columnCount){throw new RuntimeException('Строка не соответствует столбцам.');}
$values[]=$item;
if(fputcsv($csv,$item,';','"','')===false){throw new RuntimeException('Строка CSV не записана.');}
}
rewind($csv);$csvText=stream_get_contents($csv);
if($csvText===false){throw new RuntimeException('CSV не прочитан.');}
} finally {fclose($csv);c166Release($request);}
$expectedNames=['article','label','unit_cents','in_stock'];$expectedTypes=['varchar','varchar','integer','integer'];
$expectedValues=[['CAB-166','Кабель','12900','0'],['CLP-166','Клемма','900','7']];
$expectedCsv="article;label;unit_cents;in_stock\nCAB-166;Кабель;12900;0\nCLP-166;Клемма;900;7\n";
$rows=[['Столбцов в результате',$columnCount],['Порядок имён',implode(', ',$names)],['SQL-типы',implode(', ',$types)],
['CSV: заголовок',rtrim(explode("\n",$csvText)[0],"\r")],['CSV: первая строка',rtrim(explode("\n",$csvText)[1],"\r")],
['CSV: вторая строка',rtrim(explode("\n",$csvText)[2],"\r")],['PHP-тип выбранного остатка',gettype($values[0][3])]];
$checks=['names_order'=>$names===$expectedNames,'SQL_types'=>$types===$expectedTypes,'column_count'=>$columnCount===4,
'selected_values'=>$values===$expectedValues,'CSV_exact'=>$csvText===$expectedCsv,'request_released'=>$request===null];
$observations=['columns'=>$names,'SQL_types'=>$types,'numeric_rows'=>$values,'csv'=>$csvText,
'csv_separator'=>';','csv_encoding'=>'UTF-8','csv_newline'=>'LF','csv_buffer'=>'php://temp',
'schema_description_is_not_PHP_type_cast'=>true];
} finally {
if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}
c166Report('cub166-result-metadata','Собрать заголовки и CSV в том же порядке, что SQL-столбцы',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Столбцов в результате — 4
Порядок имён — article, label, unit_cents, in_stock
SQL-типы — varchar, varchar, integer, integer
CSV: заголовок — article;label;unit_cents;in_stock
CSV: первая строка — CAB-166;Кабель;12900;0
CSV: вторая строка — CLP-166;Клемма;900;7
PHP-тип выбранного остатка — string
Распакуйте архив целиком и выполните example.php в изолированном native-окружении по README.txt. lab.php проверяет собственный стенд, создаёт только принадлежащую примеру c166-таблицу, формирует result.html/result.json и удаляет таблицу в finally. metrics.json содержит проверки и факт очистки. Настройки соединения и секреты остаются вне публичных результатов. CSV этой главы показан как точный текст в отчёте; он не требует отдельной загрузки файла.
Открыть фактический результат · Открыть данные JSON.
Ограничения и ошибки
SQL-тип integer не обещает PHP-int: в этом native-профиле остаток приходит строкой "0". Типы метаданных помогают выбрать формат, но денежные decimal нельзя автоматически превращать во float, а идентификатор varchar — в число. Не собирайте CSV простым implode для произвольных значений: названия могут содержать разделитель, кавычку или перенос строки. Этот пример показывает точный небольшой текст; не измеряет память на многомиллионной выгрузке и не решает политику формул электронных таблиц.
В результате JOIN одинаковые имена колонок мешали однозначно обратиться к значениям. Исходное обсуждение. Собственный экспорт задаёт явные уникальные AS и проверяет порядок имён перед записью. Ответы с SELECT * и кодом другого драйвера не переносятся в решение.
Небольшое изменение задачи
После JOIN обе стороны содержат id. Почему заголовок id, id неудобен даже при четырёх правильных позициях?
Разбор решения
В ассоциативном массиве одинаковый ключ не может хранить два независимых значения. Выберите только нужные поля и дайте разные aliases, например product_id и supplier_id. Для экспорта держите уникальные имена и тот же порядок names/NUM-строк. Не заменяйте понятные aliases механическим SELECT *.
Показать «2 из 3», не выгружая все подходящие товары
Экран наличия должен показать две позиции и подпись «2 из 3». Считать все подходящие товары, предварительно выгрузив их в PHP, для этого не нужно. Один запрос вернёт страницу, другой — одно значение COUNT(*). Заодно увидим, почему пустой результат имеет ноль строк, но по-прежнему три столбца.
- Товары: Кабель 0, Розетка 3, Клемма 7, Изолента 2
- WHERE qty > 0 → три подходящих товара; LIMIT 2 → Розетка и Клемма
- num_rows страницы → 2; COUNT(*) с тем же WHERE → значение 3
- У SELECT COUNT(*) одна строка с total=3, поэтому её num_rows равен 1
- WHERE qty < 0 → строк 0, описание трёх выбранных столбцов сохраняется
Минимальная запись и её смысл
$pageCount = cubrid_num_rows($request);
if ($pageCount === false || $pageCount < 0) {
throw new RuntimeException("Количество строк недоступно");
}
echo "Строк в полученной странице: ", $pageCount, "\n";Счётчик относится к готовому синхронному результату $request. Он не выполняет новый COUNT по таблице и не отменяет LIMIT. COUNT(*) — SQL-выражение, вычисляющее число подходящих строк; его результат лежит в поле total одной строки. В COUNT(*) звёздочка означает подсчёт строк, а не выгрузку всех полей, как в SELECT *. Это разные операции.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Первая страница | Розетка, Клемма |
| Строк в этой странице | 2 |
| Всего подходит условию | 3 |
| Подпись экрана | 2 из 3 |
| Строк в результате COUNT(*) | 1 |
| Пустая страница: строк | 0 |
| Пустая страница: столбцов | 3 |
| Пустая страница: fetch вернул false | да |
Полный код, файлы и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);
$name=c166CreateTable($db,$owned,'page_count',[
['id'=>1,'sku'=>'CAB-166','title'=>'Кабель','category'=>'cables','price_cents'=>12900,'qty'=>0,'note'=>null],
['id'=>2,'sku'=>'SOC-166','title'=>'Розетка','category'=>'electric','price_cents'=>8000,'qty'=>3,'note'=>null],
['id'=>3,'sku'=>'CLP-166','title'=>'Клемма','category'=>'mounting','price_cents'=>900,'qty'=>7,'note'=>''],
['id'=>4,'sku'=>'TAP-166','title'=>'Изолента','category'=>'electric','price_cents'=>5900,'qty'=>2,'note'=>null]
]);$table=c166Qualified($name);
$request=cubrid_execute($db,'SELECT id,title,qty FROM '.$table.' WHERE qty>0 ORDER BY id LIMIT 2');
if($request===false){c166Failure('Страница не выбрана');}
try {
$pageRows=cubrid_num_rows($request);$pageColumns=cubrid_num_cols($request);$page=[];
if($pageRows===false||$pageRows<0||$pageColumns===false){throw new RuntimeException('Размер страницы неизвестен.');}
while(true){
$row=cubrid_fetch($request,CUBRID_ASSOC);$fetchCode=cubrid_error_code();
if($row===null||$fetchCode!==0){throw new RuntimeException('Ошибка страницы.');}
if($row===false){break;}$page[]=$row;
}
} finally {c166Release($request);}
$request=cubrid_execute($db,'SELECT COUNT(*) AS total FROM '.$table.' WHERE qty>0');
if($request===false){c166Failure('Количество не посчитано');}
try {$aggregateRows=cubrid_num_rows($request);$countRow=cubrid_fetch($request,CUBRID_ASSOC);
if(!is_array($countRow)){throw new RuntimeException('Не получен total.');}$total=(int)$countRow['total'];
} finally {c166Release($request);}
$request=cubrid_execute($db,'SELECT id,title,qty FROM '.$table.' WHERE qty<0 ORDER BY id LIMIT 2');
if($request===false){c166Failure('Пустая страница не выбрана');}
try {$emptyRows=cubrid_num_rows($request);$emptyColumns=cubrid_num_cols($request);$empty=cubrid_fetch($request,CUBRID_ASSOC);
$emptyCode=cubrid_error_code();if($empty===null||$emptyCode!==0){throw new RuntimeException('Пустой результат завершился ошибкой.');}}
finally {c166Release($request);}
$names=array_column($page,'title');
$rows=[['Первая страница',implode(', ',$names)],['Строк в этой странице',$pageRows],['Всего подходит условию',$total],
['Подпись экрана',$pageRows.' из '.$total],['Строк в результате COUNT(*)',$aggregateRows],
['Пустая страница: строк',$emptyRows],['Пустая страница: столбцов',$emptyColumns],['Пустая страница: fetch вернул false',$empty===false]];
$checks=['page_names'=>$names===['Розетка','Клемма'],'page_values'=>$page===[['id'=>'2','title'=>'Розетка','qty'=>'3'],['id'=>'3','title'=>'Клемма','qty'=>'7']],
'limited_count'=>$pageRows===2&&$pageColumns===3,'filtered_total'=>$total===3,'aggregate_is_one_row'=>$aggregateRows===1,
'empty_has_schema'=>$emptyRows===0&&$emptyColumns===3&&$empty===false,'request_released'=>$request===null];
$observations=['page'=>$page,'page_rows'=>$pageRows,'total_value'=>$total,'COUNT_result_rows'=>$aggregateRows,
'empty_rows'=>$emptyRows,'empty_columns'=>$emptyColumns,'empty_fetch'=>$empty,
'fixture_size'=>4,'all_matching_rows_downloaded'=>false,'queries_are_synchronous'=>true];
} finally {
if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}
c166Report('cub166-page-count','Показать «2 из 3», не выгружая все подходящие товары',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Первая страница — Розетка, Клемма
Строк в этой странице — 2
Всего подходит условию — 3
Подпись экрана — 2 из 3
Строк в результате COUNT(*) — 1
Пустая страница: строк — 0
Пустая страница: столбцов — 3
Пустая страница: fetch вернул false — да
Распакуйте архив целиком и выполните example.php в изолированном native-окружении по README.txt. lab.php проверяет собственный стенд, создаёт только принадлежащую примеру c166-таблицу, формирует result.html/result.json и удаляет таблицу в finally. metrics.json содержит проверки и факт очистки. Настройки соединения и секреты остаются вне публичных результатов. CSV этой главы показан как точный текст в отчёте; он не требует отдельной загрузки файла.
Открыть фактический результат · Открыть данные JSON.
Ограничения и ошибки
Два запроса не гарантируют единый снимок при параллельных изменениях. Если экрану нужна строгая согласованность, выбирайте подходящую транзакцию и уровень изоляции; в маленькой лаборатории конкурентность не измеряется. Для бесконечной прокрутки можно запрашивать на одну строку больше и показывать «есть ещё» без точного общего количества. Асинхронный num_rows по документированному API даёт 0, и это не доказательство пустой базы. Здесь все запросы синхронные.
Автор пытался совместить ограниченную страницу и общее число строк, рассматривая подсчёт всего массива в PHP. Исходное обсуждение. Здесь сервер отдельно считает COUNT с тем же WHERE, а PHP получает только текущую LIMIT-страницу. MySQL-специфичные SQL_CALC_FOUND_ROWS и FOUND_ROWS не используются.
Небольшое изменение задачи
Почему num_rows результата SELECT COUNT(*) вернул 1, хотя total равен 3? Как узнать число подходящих строк?
Разбор решения
База вернула одну строку-ответ, содержащую число 3. Прочитайте поле total через fetch, а не считайте строки самого агрегатного ответа. Сохраните одинаковое условие WHERE у страницы и COUNT; иначе подпись будет описывать другой набор товаров.
Освободить буфер предпросмотра и выполнить новый фильтр
Пользователь начал предпросмотр каталога и сразу изменил фильтр. Старый клиентский буфер больше не нужен, но подготовленный шаблон ещё пригодится. Первый просмотр покажет «Кабель — 0», новый фильтр от пяти единиц — «Клемма — 7». После закрытия request то же соединение всё ещё прочитает Кабель.
- prepare: SELECT нужных полей WHERE qty >= ? ORDER BY id LIMIT 2
- bind 0 → execute → первая строка Кабель 0, вторая пока не прочитана
- free_result → освобождён клиентский fetch-буфер, request остаётся
- bind 5 → execute того же шаблона → Клемма 7
- free_result → close_request → новое чтение прежним соединением
Минимальная запись и её смысл
if (!cubrid_free_result($request)) {
throw new RuntimeException("Буфер результата не освобождён");
}
// Шаблон request остаётся; при завершении работы:
cubrid_close_request($request);
$request = null;В этом API free_result и close_request — разные действия. Первое очищает клиентский fetch-буфер; второе завершает сам request. Перепривязка нового порога и повторное execute создают новое выполнение шаблона. Уже сохранённый $first является обычным массивом PHP и продолжает существовать. Наличие is_resource само по себе не доказательство рабочего запроса; в полной программе это подтверждает действительное повторное выполнение.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Первый предпросмотр | Кабель — 0 |
| Первый буфер освобождён | да |
| Request после free_result существует | да |
| Новый фильтр qty >= 5 | Клемма — 7 |
| Второй буфер освобождён | да |
| Request окончательно закрыт | да |
| Новое чтение через прежнее соединение | Кабель — 0 |
Полный код, файлы и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c166Config();$db=c166Connect();$runtime=c166Runtime($db);
$name=c166CreateTable($db,$owned,'release',[
['id'=>1,'sku'=>'CAB-166','title'=>'Кабель','category'=>'cables','price_cents'=>12900,'qty'=>0,'note'=>null],
['id'=>2,'sku'=>'SOC-166','title'=>'Розетка','category'=>'electric','price_cents'=>8000,'qty'=>3,'note'=>null],
['id'=>3,'sku'=>'CLP-166','title'=>'Клемма','category'=>'mounting','price_cents'=>900,'qty'=>7,'note'=>'']
]);$table=c166Qualified($name);
$request=cubrid_prepare($db,'SELECT id,title,qty FROM '.$table.' WHERE qty>=? ORDER BY id LIMIT 2');
if($request===false){c166Failure('Фильтр не подготовлен');}
try {
if(cubrid_bind($request,1,0,'number')!==true||cubrid_execute($request)!==true){c166Failure('Первый фильтр не выполнен');}
$first=cubrid_fetch($request,CUBRID_ASSOC);
if(!is_array($first)){throw new RuntimeException('Нет первого предпросмотра.');}
$freedFirst=cubrid_free_result($request);
if($freedFirst!==true){c166Failure('Буфер не освобождён');}
$requestSurvives=is_resource($request);
if(cubrid_bind($request,1,5,'number')!==true||cubrid_execute($request)!==true){c166Failure('Второй фильтр не выполнен');}
$second=cubrid_fetch($request,CUBRID_ASSOC);
if(!is_array($second)){throw new RuntimeException('Второй фильтр не вернул карточку.');}
$end=cubrid_fetch($request,CUBRID_ASSOC);$endCode=cubrid_error_code();
if($end!==false||$endCode!==0){throw new RuntimeException('Второй фильтр дал неожиданные строки или ошибку.');}
$freedSecond=cubrid_free_result($request);
if($freedSecond!==true){c166Failure('Второй буфер не освобождён');}
} finally {c166Release($request);}
$stillConnected=c166Read($db,'SELECT title,qty FROM '.$table.' WHERE id=1');
$rows=[['Первый предпросмотр',$first['title'].' — '.$first['qty']],['Первый буфер освобождён',$freedFirst],
['Request после free_result существует',$requestSurvives],['Новый фильтр qty >= 5',$second['title'].' — '.$second['qty']],
['Второй буфер освобождён',$freedSecond],['Request окончательно закрыт',$request===null],
['Новое чтение через прежнее соединение',$stillConnected[0]['title'].' — '.$stillConnected[0]['qty']]];
$checks=['first_preview'=>$first===['id'=>'1','title'=>'Кабель','qty'=>'0'],'first_buffer_freed'=>$freedFirst===true,
'request_retained'=>$requestSurvives,'reexecuted_filtered'=>$second===['id'=>'3','title'=>'Клемма','qty'=>'7']&&$end===false,
'second_buffer_freed'=>$freedSecond===true,'request_closed'=>$request===null,
'connection_still_usable'=>$stillConnected===[['title'=>'Кабель','qty'=>'0']]];
$observations=['first_preview'=>$first,'second_filter'=>$second,'first_buffer_freed'=>$freedFirst,
'second_buffer_freed'=>$freedSecond,'same_prepared_request_before_final_close'=>$requestSurvives,
'after_close_read'=>$stillConnected,'first_result_not_fully_consumed'=>true,
'business_changes'=>0,'PHP_memory_or_server_cancel_not_measured'=>true];
} finally {
if (is_resource($db)) {try {$removed=c166Cleanup($db,$owned);} finally {c166Close($db);}}
}
c166Report('cub166-result-release','Освободить буфер предпросмотра и выполнить новый фильтр',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Первый предпросмотр — Кабель — 0
Первый буфер освобождён — да
Request после free_result существует — да
Новый фильтр qty >= 5 — Клемма — 7
Второй буфер освобождён — да
Request окончательно закрыт — да
Новое чтение через прежнее соединение — Кабель — 0
Распакуйте архив целиком и выполните example.php в изолированном native-окружении по README.txt. lab.php проверяет собственный стенд, создаёт только принадлежащую примеру c166-таблицу, формирует result.html/result.json и удаляет таблицу в finally. metrics.json содержит проверки и факт очистки. Настройки соединения и секреты остаются вне публичных результатов. CSV этой главы показан как точный текст в отчёте; он не требует отдельной загрузки файла.
Открыть фактический результат · Открыть данные JSON.
Ограничения и ошибки
free_result не обещает удалить все данные сервера, отменить SQL или сбросить курсор к началу. Не читайте старый результат после очистки в расчёте на автоматическую перемотку. Это также не commit и не rollback. Закрывайте request в finally при завершении его владения, а соединение — отдельно. Пример демонстрирует поведение закреплённого native-профиля; количество освобождённых байтов и ускорение памяти не измеряются.
Небольшое изменение задачи
Вместо нового фильтра пользователь ушёл со страницы. Нужны ли ещё bind и execute, чтобы правильно закончить работу?
Разбор решения
Нет. Освободите принадлежащий вам request через close_request и обнулите переменную. Повторное выполнение нужно только для новой полезной операции. Если были изменения данных, сначала явно решите commit или rollback; освобождение буфера не решает судьбу транзакции.
Шесть функций от строки до освобождения буфера
| Функция | Для чего нужна |
|---|---|
| cubrid_fetch | Получить следующую строку как числовой или именованный массив либо объект. |
| cubrid_column_names | Узнать имена выбранных колонок и сохранить порядок заголовков. |
| cubrid_column_types | Узнать SQL-типы результата и выбрать осмысленный формат данных. |
| cubrid_num_cols | Посчитать поля SELECT, в том числе у пустого результата. |
| cubrid_num_rows | Посчитать строки выполненной выборки, отделив страницу от общего количества. |
| cubrid_free_result | Освободить клиентский буфер чтения, сохранив сам request до отдельного закрытия. |
Связать форму результата с задачей человека
Выбирайте форму строки под потребителя: именованные ключи для понятного шаблона, позиции для согласованного экспорта, объект для доступа к свойствам. Отдельно проверяйте метаданные и счётчики. Ноль товаров, SQL NULL, строковый ноль и ошибка чтения — разные состояния, и интерфейс должен различать их.
Следующий шаг — добавить параметризованный фильтр к нужной странице и продумать транзакционные границы для записей. Счётчик результата не заменяет COUNT всей подходящей выборки, а освобождение request не решает, подтверждать ли изменения. Реальные отчёты каждого проекта позволяют увидеть данные, а не только успешный возврат функции.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
cubrid_fetchПолучить следующую строку как числовой или именованный массив либо объект.
PECL CUBRID >= 8.3.0; совместимость PHP и драйвера проверяется отдельно. · cubrid
cubrid_fetch(resource $result, int $type = CUBRID_BOTH): mixedПосле SQL нужно превратить конкретную строку в данные для экрана или файла. Кабель с остатком 0 может быть доступен по имени available, по позиции 2 или как свойство объекта. Меняется форма PHP-значения, а не число товаров и не содержимое базы.
Базовая запись
cubrid_fetch(resource $result, int $type = CUBRID_BOTH): mixedЭто справочная сигнатура: resource означает ресурс расширения, а | перечисляет возможные виды результата. Её не нужно вставлять в example.php. Все шесть функций описаны с PECL CUBRID 8.3.0; это номер расширения, не требование PHP 8.3. Рабочие проекты используют отдельный профиль native CUBRID из README, без изменения PHP действующего сайта.
| Параметр | Что передать |
|---|---|
| result | Живой request успешно выполненного SELECT. Подготовленный execute возвращает true/false; передают сам request, а не этот bool. |
| type = CUBRID_BOTH | Режим представления строки. NUM — индексы с 0; ASSOC — имена; BOTH — обе системы ключей; OBJECT — объект со свойствами. Дополнительный CUBRID_LOB относится к получению LOB-объектов. |
| Результат | Как понимать |
|---|---|
| array или object | Одна строка; после получения курсор автоматически продвигается. SQL NULL находится внутри строки как null. |
| false | По документации, больше нет строк. Перед выводом «пусто» нужно знать, что это успешный SELECT, и учитывать немедленную диагностику. |
| null | Документированный отказ получения строки. Не смешивайте его с null в ячейке успешно полученного массива. |
| Режим | Для чего выбрать |
|---|---|
| CUBRID_ASSOC | Шаблон карточки: $item["item_title"]. Перестановка колонок SELECT не меняет имя ключа. |
| CUBRID_NUM | Позиционный экспорт: поля строки идут в том же порядке, что заголовки результата. |
| CUBRID_BOTH | Нужны оба способа доступа. Обычный foreach по всем значениям повторит каждое поле дважды. |
| CUBRID_OBJECT | Контейнер свойств: $item->item_title. Это не экземпляр вашей модели товара с её методами. |
| CUBRID_LOB | Дополнительная опция для BLOB/CLOB. Не заменяет жизненный цикл LOB и не означает чтение произвольного файла. Эти проекты используют обычные поля. |
Применение 1: карточка с нулевым остатком
$item = cubrid_fetch($request, CUBRID_ASSOC);
$error = cubrid_error_code(); // до другой операции драйвера
if ($item === null || $error !== 0) {
throw new RuntimeException('Карточка не прочитана');
}
if ($item === false) {
echo 'Товар не найден';
} else {
echo htmlspecialchars($item['item_title'], ENT_QUOTES, 'UTF-8');
echo ' / остаток ', $item['available'];
}Фрагмент предполагает SELECT title AS item_title, qty AS available ... WHERE id = ?. В первом проекте результат — Кабель / остаток 0. Проверка empty($item["available"]) здесь неверна как проверка существования товара: нулевой остаток является полезным значением. Для объекта ключи должны подходить в качестве имён свойств PHP; выражения вроде COUNT(*) снабжайте явным AS total.
Применение 2: строка для позиционного CSV
$fields = cubrid_fetch($request, CUBRID_NUM);
$error = cubrid_error_code();
if ($fields === null || $error !== 0) {
throw new RuntimeException('Выгрузка прочитана не полностью');
}
if ($fields !== false) {
fputcsv($stream, $fields, ';', '"', '');
}Поток $stream уже открыт, а SELECT перечисляет только экспортируемые поля. Полный проект CSV формирует строку CAB-166;Кабель;12900;0 и сохраняет порядок четырёх полей. ASSOC удобен для адресного шаблона, NUM — для сопоставления с заголовками; BOTH без специального выбора ключей удвоит поля CSV.
Значения имён в JOIN могут совпасть. Записывайте уникальные AS, например item_title и category_title, иначе один ассоциативный ключ не представит два различных столбца. В закреплённом профиле INT приходит PHP-строкой; автоматическая типизация всего массива не является контрактом fetch. Отсутствие примечания проверяйте через array_key_exists либо property_exists, когда нужно отличить существующее поле с null от отсутствующего.
Документация различает false и null, но неправильный вид request нельзя диагностировать только этими значениями. В подготовке драйвер 11.2.0.0002 вернул false и код 0 при fetch для UPDATE, хотя строк там вообще не предполагалось. Поэтому функция применяется к известному успешному SELECT; при false/null сразу сохраняется код ошибки. Для обычного конца таких SELECT наблюдён false с кодом 0. Не вызывайте API на уже закрытом ресурсе ради проверки поведения. После чтения: cubrid_free_result и cubrid_close_request.
cubrid_column_namesУзнать имена выбранных колонок и сохранить порядок заголовков.
PECL CUBRID >= 8.3.0; совместимость PHP и драйвера проверяется отдельно. · cubrid
cubrid_column_names(resource $req_identifier): array|falseТаблица на экране и выгрузка должны договориться, что означает каждое поле. Функция возвращает имена именно текущего SELECT, включая AS. Например, статья каталога имеет заголовки article, label, unit_cents, in_stock в точном порядке значений строки.
Базовая запись
cubrid_column_names(resource $req_identifier): array|falseЭто справочная сигнатура: resource означает ресурс расширения, а | перечисляет возможные виды результата. Её не нужно вставлять в example.php. Все шесть функций описаны с PECL CUBRID 8.3.0; это номер расширения, не требование PHP 8.3. Рабочие проекты используют отдельный профиль native CUBRID из README, без изменения PHP действующего сайта.
| Параметр | Что передать |
|---|---|
| req_identifier | Request успешно выполненного запроса с результатом. Метаданные берут до закрытия request. |
| Результат | Как понимать |
|---|---|
| array | Числовой массив имён колонок в порядке результата; первый индекс 0. |
| false | Описания получить не удалось. Это не пустой список полей, который можно незаметно вывести. |
Применение 1: заголовок HTML-таблицы
$names = cubrid_column_names($request);
if ($names === false) { throw new RuntimeException('Нет заголовков'); }
echo '<tr>';
foreach ($names as $name) {
echo '<th>', htmlspecialchars($name, ENT_QUOTES, 'UTF-8'), '</th>';
}
echo '</tr>';Предварительный SELECT явно задаёт доступные экрану столбцы; этот цикл не выбирает все поля таблицы. Даже если фильтр не нашёл товары, названия полей результата остаются полезны: интерфейс может показать структуру и сообщение «ничего не найдено». Пустая страница отдельно показывает ноль строк при трёх полях.
Применение 2: защитить порядок обменного файла
$names = cubrid_column_names($request);
$required = ['article', 'label', 'unit_cents', 'in_stock'];
if ($names !== $required) {
throw new RuntimeException('SELECT не соответствует формату выгрузки');
}
fputcsv($stream, $names, ';', '"', '');
// Следующие записи читаются с CUBRID_NUM в том же порядке.В полном проекте получается заголовок article;label;unit_cents;in_stock, затем две конкретные товарные строки. Сравнение имён защищает от незаметной перестановки цены и остатка при изменении SELECT. Это контракт нашего формата, а не проверка всех свойств таблицы.
AS меняет имя поля результата, не DDL таблицы. При JOIN выбирайте явные уникальные псевдонимы: повторяющиеся id не станут уникальными сами. Имена метаданных не являются разрешением вставлять их из внешнего запроса в произвольный SQL. Если пользователь выбирает сортировку или колонки, сопоставляйте выбор со своим разрешённым набором. Для SQL-типа берите cubrid_column_types, для количества — cubrid_num_cols; они описывают те же позиции.
cubrid_column_typesУзнать SQL-типы результата и выбрать осмысленный формат данных.
PECL CUBRID >= 8.3.0; совместимость PHP и драйвера проверяется отдельно. · cubrid
cubrid_column_types(resource $req_identifier): array|falseВ отчёте цена, остаток и артикул выглядят как текст, но имеют разный смысл. Метаданные позволяют понять SQL-тип выбранных полей и проверить формат выгрузки. Они не превращают каждую строку PHP в автоматически правильное число.
Базовая запись
cubrid_column_types(resource $req_identifier): array|falseЭто справочная сигнатура: resource означает ресурс расширения, а | перечисляет возможные виды результата. Её не нужно вставлять в example.php. Все шесть функций описаны с PECL CUBRID 8.3.0; это номер расширения, не требование PHP 8.3. Рабочие проекты используют отдельный профиль native CUBRID из README, без изменения PHP действующего сайта.
| Параметр | Что передать |
|---|---|
| req_identifier | Request выполненного запроса, описание столбцов которого требуется. |
| Результат | Как понимать |
|---|---|
| array | Числовой массив строк с SQL-типами по порядку колонок. |
| false | Метаданные типов получить не удалось; обработку формата следует остановить. |
В нашем SELECT получаются varchar, varchar, integer, integer. В закреплённом исходнике также предусмотрены char/nchar/varnchar, bit/varbit, numeric, smallint, monetary, float/double, date/time/timestamp/datetime, bigint, object, blob/clob, enum и записи коллекционных типов. Формат приходит от драйвера. Это не полный паспорт поля: функция не даёт отдельные precision, scale, nullable, значение по умолчанию или ограничения ключа.
Применение 1: форматировать известные поля карточки
$names = cubrid_column_names($request);
$types = cubrid_column_types($request);
if ($names === false || $types === false || count($names) !== count($types)) {
throw new RuntimeException('Нет описания полей');
}
$position = array_search('in_stock', $names, true);
if ($position === false || $types[$position] !== 'integer') {
throw new RuntimeException('Остаток должен быть INT');
}
// После получения NUM-строки:
$displayQuantity = (int)$fields[$position];Здесь cast допустим для ограниченного INT-остатка нашей схемы, включая 0. Для денежного numeric/decimal такой подход нельзя механически заменить на float: точность и масштаб нужно выбрать по задаче. Артикул VARCHAR "0012" остаётся строкой, иначе потеряет ведущие нули. SQL NULL также обрабатывается до форматирования значения, а не превращается в пустое число.
Применение 2: проверить схему строк CSV
$types = cubrid_column_types($request);
$expected = ['varchar', 'varchar', 'integer', 'integer'];
if ($types !== $expected) {
throw new RuntimeException('Типы выгрузки изменились');
}
// Теперь строки экспортируются без неявной переделки всех значений.Проект с метаданными показывает одновременно SQL integer и PHP string у нулевого остатка. Проверка сравнивает согласованный формат четырёх столбцов. Она не сертифицирует правильность бизнес-данных и не гарантирует одинаковые строки типов у всех версий расширения.
Не выводите коллекции, LOB и даты через универсальный «всё в строку» без выбранного представления. Для неизвестного типа полезнее явно отказаться от неподдержанного экспорта, чем получить незаметно испорченный файл. Позиции связываются через cubrid_column_names и cubrid_num_cols; данные берутся через cubrid_fetch. Метаданные относятся к выбранным выражениям, поэтому CAST и вычисления SQL могут изменить тип результата относительно исходной колонки.
cubrid_num_colsПосчитать поля SELECT, в том числе у пустого результата.
PECL CUBRID >= 8.3.0; совместимость PHP и драйвера проверяется отдельно. · cubrid
cubrid_num_cols(resource $result): int|falseНужно знать, сколько ячеек должно быть в каждой строке отчёта. Функция считает поля результата SELECT: выбор четырёх нужных колонок даёт 4 даже у таблицы с семью колонками. Ноль товаров не отменяет описание выбранных полей.
Базовая запись
cubrid_num_cols(resource $result): int|falseЭто справочная сигнатура: resource означает ресурс расширения, а | перечисляет возможные виды результата. Её не нужно вставлять в example.php. Все шесть функций описаны с PECL CUBRID 8.3.0; это номер расширения, не требование PHP 8.3. Рабочие проекты используют отдельный профиль native CUBRID из README, без изменения PHP действующего сайта.
| Параметр | Что передать |
|---|---|
| result | Request успешно выполненного SELECT. Не соединение, не массив уже прочитанной строки и не результат UPDATE. |
| Результат | Как понимать |
|---|---|
| int | Количество колонок этого результата. |
| false | Отказ, включая неподходящий вид SQL в закреплённом драйвере; проверяется строго, не как числовой ноль. |
Применение 1: проверить ширину экспортируемой строки
$width = cubrid_num_cols($request);
if ($width === false || $width !== 4) {
throw new RuntimeException('Ожидалось четыре поля выгрузки');
}
$fields = cubrid_fetch($request, CUBRID_NUM);
$error = cubrid_error_code();
if ($fields === null || $error !== 0) { throw new RuntimeException('Ошибка чтения'); }
if ($fields !== false && count($fields) !== $width) {
throw new RuntimeException('Нарушен порядок полей');
}В проекте CSV это article, label, unit_cents и in_stock. Для BOTH сравнивать count(row) с шириной нельзя: четыре SQL-поля могут дать восемь ключей PHP. Для позиционного формата сознательно выбран NUM.
Применение 2: показать пустой результат внутри таблицы
$width = cubrid_num_cols($request);
$count = cubrid_num_rows($request);
if ($width === false || $count === false) {
throw new RuntimeException('Нет размера результата');
}
if ($count === 0) {
echo '<tr><td colspan="', $width, '">Товары не найдены</td></tr>';
}Для синхронного SELECT id, title, qty ... WHERE qty < 0 самостоятельная программа получает три колонки и ноль строк. colspan использует количество выбранных полей, а не число товаров. Заголовки можно получить независимо через cubrid_column_names.
Функция предназначена для SELECT. В подготовке закреплённый драйвер вернул false, код -30002 и facility 4 при передаче request UPDATE. Такая ошибка не означает «в UPDATE ноль колонок»; для количества изменённых строк нужен cubrid_affected_rows. Количество физически объявленных полей таблицы — другая задача метаданных схемы, а не число текущего результата.
cubrid_num_rowsПосчитать строки выполненной выборки, отделив страницу от общего количества.
PECL CUBRID >= 8.3.0; совместимость PHP и драйвера проверяется отдельно. · cubrid
cubrid_num_rows(resource $result): int|falseПодпись «показано 2 из 3» состоит из двух разных чисел. num_rows даёт размер выполненной страницы LIMIT 2; общий размер подходящего каталога вычисляется отдельным COUNT с тем же условием. Так PHP получает только данные текущего экрана.
Базовая запись
cubrid_num_rows(resource $result): int|falseЭто справочная сигнатура: resource означает ресурс расширения, а | перечисляет возможные виды результата. Её не нужно вставлять в example.php. Все шесть функций описаны с PECL CUBRID 8.3.0; это номер расширения, не требование PHP 8.3. Рабочие проекты используют отдельный профиль native CUBRID из README, без изменения PHP действующего сайта.
| Параметр | Что передать |
|---|---|
| result | Request результата SELECT. Для надёжного счётчика используются завершённые синхронные запросы. |
| Результат | Как понимать |
|---|---|
| int для SELECT | Количество строк в этом результате; ограничение LIMIT уже применено. |
| 0 в асинхронном режиме | PHP Manual отдельно оговаривает 0; это нельзя безусловно объявлять отсутствием товаров. |
| -1 для не-SELECT по PHP Manual | Документированная граница API, но поведение конкретной закреплённой версии отличается — ниже приведён факт. |
| false | Отказ функции. В драйвере 11.2.0.0002 так же возвращается неподходящий вид запроса. |
Применение 1: подписать выбранную страницу
// request: SELECT id, title, qty FROM products
// WHERE qty > 0 ORDER BY id LIMIT 2
$shown = cubrid_num_rows($request);
if ($shown === false || $shown < 0) {
throw new RuntimeException('Размер страницы неизвестен');
}
echo 'На этой странице: ', $shown;Результат полного проекта содержит Розетку и Клемму. Изолента тоже имеет остаток, но не входит в первые две строки ORDER BY id. Получать все товары и затем считать/сортировать их в PHP для этой подписи не нужно.
Применение 2: отличить COUNT-значение от числа его строк
$countRequest = cubrid_execute($db,
'SELECT COUNT(*) AS total FROM products WHERE qty > 0');
if ($countRequest === false) { throw new RuntimeException('Нет счётчика'); }
try {
$resultRows = cubrid_num_rows($countRequest); // 1
$row = cubrid_fetch($countRequest, CUBRID_ASSOC);
if (!is_array($row)) { throw new RuntimeException('Нет поля total'); }
$total = (int)$row['total']; // 3 в нашем наборе
} finally {
cubrid_close_request($countRequest);
$countRequest = null;
}COUNT(*) здесь не выгружает SELECT *: сервер вычисляет число подходящих строк и отдаёт один результат с total. Даже если total равен 0, агрегат без GROUP BY возвращает одну строку. У COUNT с группировкой строк будет столько, сколько групп; нельзя обобщать наблюдение на все агрегатные запросы.
Сохраните одинаковые условия WHERE и привязанные значения в запросе списка и подсчёта. Два отдельных запроса под конкурентными изменениями не обещают один снимок; для такой гарантии требуется осознанный транзакционный режим. Если интерфейсу нужна только кнопка «ещё», иногда достаточно получить page_size + 1 строку без отдельного общего COUNT.
Версионное различие сохранено явно: официальный PHP Manual описывает -1 для не-SELECT. Закреплённый исходник 11.2.0.0002 вместо этого сообщает ошибку типа SQL и возвращает false. В реальной подготовке UPDATE дал false / -30002 / facility 4. Не стройте обработчик только на сравнении с -1. Для UPDATE используйте cubrid_affected_rows, а для пустого синхронного SELECT проекта наблюдены 0 и нормальный EOF без ошибки.
cubrid_free_resultОсвободить клиентский буфер чтения, сохранив сам request до отдельного закрытия.
PECL CUBRID >= 8.3.0; совместимость PHP и драйвера проверяется отдельно. · cubrid
cubrid_free_result(resource $req_identifier): boolПредпросмотр уже не нужен, а подготовленный шаблон ещё пригодится для нового фильтра. Функция освобождает клиентский буфер чтения результата; затем можно явно выполнить следующий запрос. Окончательное закрытие request остаётся отдельной обязанностью.
Базовая запись
cubrid_free_result(resource $req_identifier): boolЭто справочная сигнатура: resource означает ресурс расширения, а | перечисляет возможные виды результата. Её не нужно вставлять в example.php. Все шесть функций описаны с PECL CUBRID 8.3.0; это номер расширения, не требование PHP 8.3. Рабочие проекты используют отдельный профиль native CUBRID из README, без изменения PHP действующего сайта.
| Параметр | Что передать |
|---|---|
| req_identifier | Живой request, буфер результата которого больше не используется этим участком кода. |
| Результат | Как понимать |
|---|---|
| true | Буфер успешно освобождён. |
| false | Освобождение не выполнено. Не выдавайте результат за завершённую очистку; сохраните диагностику. |
В закреплённом драйвере вызывается cci_fetch_buffer_clear, а сам PHP-ресурс request здесь не удаляется. Это отличается от cubrid_close_request. Наличие живого ресурса само по себе не доказывает пригодность конкретного следующего вызова, поэтому самостоятельный проект проверяет настоящее повторное execute и новую строку.
Применение 1: сменить фильтр предпросмотра
if (cubrid_free_result($request) !== true) {
throw new RuntimeException('Предыдущий буфер не освобождён');
}
if (!cubrid_bind($request, 1, 5, 'number') ||
cubrid_execute($request) !== true) {
throw new RuntimeException('Новый фильтр не выполнен');
}
$item = cubrid_fetch($request, CUBRID_ASSOC);
if (!is_array($item)) { throw new RuntimeException('Нет ожидаемой карточки'); }Шаблон заранее подготовлен как SELECT id,title,qty ... WHERE qty >= ? ORDER BY id LIMIT 2. Полный сценарий сначала читает Кабель / 0, затем очищает старый буфер, связывает порог 5 и получает Клемму / 7. Сохранённый массив первой карточки остаётся обычным значением PHP. Очистка буфера не редактирует его и не удаляет товар.
Применение 2: закончить экспорт и закрыть запрос
try {
// Здесь читаются строки известного SELECT и записываются в CSV.
if (cubrid_free_result($request) !== true) {
throw new RuntimeException('Буфер экспорта не освобождён');
}
} finally {
if (is_resource($request)) {
cubrid_close_request($request);
$request = null;
}
}Это вариант явного разделения сроков жизни: строки уже преобразованы в файл, буфер больше не нужен, а request окончательно закрывается при выходе. В проекте CSV промежуточный free_result не обязателен: там request сразу закрывается в finally. Не нужно добавлять оба вызова механически в каждый фрагмент ради количества функций.
free_result не равен отмене SQL на сервере, перемотке курсора, закрытию соединения, commit или rollback. После очистки не рассчитывайте продолжить прежнее чтение в конкретной позиции без проверенного контракта; здесь новый bind/execute явно создаёт новый результат. Примеры не измеряют число освобождённых байтов, расход всей серверной памяти или выигрыш времени. Для записей судьба данных определяется транзакцией, а не освобождением fetch-буфера.
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


