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

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

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

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

Глава 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.

Одна понятная карточка из базы
  1. SELECT: id 1, Кабель, qty 0, note NULL
  2. CUBRID_ASSOC: имена ключей → значения; CUBRID_NUM: позиции → значения
  3. BOTH: обе системы ключей; OBJECT: те же значения как свойства
  4. Шаблон показывает Кабель / остаток 0; NULL не означает отсутствие товара
  5. После чтения освобождается request, соединение может обслужить следующую задачу

Представьте результат как небольшую накладную: у неё есть заголовки, строки и порядок ячеек. Это полезная модель для NUM и метаданных, но драйвер не обязан хранить весь большой ответ как лист PHP-массива. Ограничивайте данные в SQL и отличайте строки результата от буферов и ресурсов. Прежде полезно пройти подключение CUBRID, параметры и выборку каталога и ключи массива.

ЗадачаЧто человек получитСамостоятельная программа
Карточка и способ доступаКабель / 0 / NULL без удвоенного выводаЧетыре формы строки
Согласованный экспортЗаголовок и две конкретные CSV-строкиИмена, типы и порядок
Страница каталогаРозетка и Клемма; показано 2 из 3Два разных счётчика
Изменение предпросмотраКабель 0 → фильтр от 5 → Клемма 7Буфер и срок жизни request

Показать одну карточку четырьмя способами без двойного вывода

Карточке товара нужны название, остаток и примечание. Если перебрать все значения режима BOTH, название появится дважды: по номеру и по имени. Разберём на «Кабель, остаток 0, примечания нет», что именно приходит в PHP и какой способ удобен для экрана, файла или объекта.

Путь конкретных данных
  1. SQL выбрал четыре поля: item_id / item_title / available / comment_text
  2. Одна строка: 1 / Кабель / 0 / SQL NULL
  3. NUM → [0,1,2,3]; ASSOC → четыре именованных ключа
  4. BOTH → два ключа для каждого поля; OBJECT → свойства с именами aliases
  5. На экране один Кабель и остаток 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: ключей PHP8
OBJECT: свойство available0
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 останется ценой в копейках.

Путь конкретных данных
  1. SELECT sku AS article, title AS label, price_cents AS unit_cents, qty AS in_stock
  2. column_names → article / label / unit_cents / in_stock
  3. column_types → varchar / varchar / integer / integer; num_cols → 4
  4. fetch NUM → CAB-166 / Кабель / 12900 / 0 в том же порядке
  5. 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
Полный код, файлы и архив для запуска
Собрать заголовки и CSV в том же порядке, что SQL-столбцыСамостоятельный пример
<?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(*). Заодно увидим, почему пустой результат имеет ноль строк, но по-прежнему три столбца.

Путь конкретных данных
  1. Товары: Кабель 0, Розетка 3, Клемма 7, Изолента 2
  2. WHERE qty > 0 → три подходящих товара; LIMIT 2 → Розетка и Клемма
  3. num_rows страницы → 2; COUNT(*) с тем же WHERE → значение 3
  4. У SELECT COUNT(*) одна строка с total=3, поэтому её num_rows равен 1
  5. 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да
Полный код, файлы и архив для запуска
Показать «2 из 3», не выгружая все подходящие товарыСамостоятельный пример
<?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 то же соединение всё ещё прочитает Кабель.

Путь конкретных данных
  1. prepare: SELECT нужных полей WHERE qty >= ? ORDER BY id LIMIT 2
  2. bind 0 → execute → первая строка Кабель 0, вторая пока не прочитана
  3. free_result → освобождён клиентский fetch-буфер, request остаётся
  4. bind 5 → execute того же шаблона → Клемма 7
  5. 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_identifierRequest успешно выполненного запроса с результатом. Метаданные берут до закрытия 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_identifierRequest выполненного запроса, описание столбцов которого требуется.
РезультатКак понимать
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 действующего сайта.

ПараметрЧто передать
resultRequest успешно выполненного 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 действующего сайта.

ПараметрЧто передать
resultRequest результата 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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.