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

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

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

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

Глава 83 · После первого подключения и основ SQL

CUBRID: параметры каталога, точный остаток и страницы результата

Родной CUBRID: параметры каталога, NULL и текст, условное списание, ограниченные страницы и срок жизни запросов.

Сначала нужные данные, затем PHP-вывод

На странице каталога нужно показать подходящие товары, сохранить правку карточки и отпустить только доступное количество. База умеет отбирать, сортировать и ограничивать строки до передачи в PHP. В этой главе результатом станут Розетка за 8000 копеек, сохранённый Кабель O'Reilly с нулевым остатком, отказ списать лишние четыре единицы и две готовые страницы по два товара.

Для начала достаточно понять три разных значения: $db — соединение с базой, $request — подготовленная или выполненная SQL-команда, $item — один PHP-массив уже прочитанной строки. Они не взаимозаменяемы. Особенно легко ошибиться с execute: прямой вызов возвращает request, а выполнение подготовленного — только true/false.

От фильтра посетителя до строки экрана
  1. Категория электрика и предел 10000 копеек — данные PHP
  2. prepare: SELECT нужных полей WHERE category = ? AND price_cents <= ?
  3. bind: №1 → электрика, №2 → 10000; execute запускает SQL
  4. CUBRID отбирает Розетку 8000; fetch_assoc возвращает её поля
  5. Экран показывает название и цену; close_request завершает работу с запросом

Удобная модель — бланк заказа с отдельными полями. prepare задаёт форму, bind заполняет поля, execute передаёт заполненный бланк в работу. Однако это только аналогия: request является ресурсом драйвера, а не распечатанной строкой с подставленными кавычками. Его нельзя вывести как SQL с секретами и нельзя сохранять между независимыми PHP-запросами как обычный массив.

Предварительно разберите первое подключение и диагностика, ключи и значения массивов, циклы и условия и параметры функций. Текущие шаги объясняются ниже на минимальных вызовах; полные программы добавляют собственную таблицу, проверку окружения, отчёт и очистку. Здесь используется родное расширение cubrid, не PDO и не mysqli.

Лабораторный профиль отдельно описывает совместимые версии PHP, расширения, CCI и сервера. В данном выпуске код сохраняет совместимость с PHP 7.4, необходимую выбранному нативному драйверу; это не рекомендация менять PHP действующего сайта. Современная версия сервера сама по себе не означает, что старый драйвер поддерживает любой PHP. Условия установки и изоляции приведены в README каждого архива.

ПотребностьПонятный результатПроект
Фильтр каталогаРозетка 8000, затем Молоток 25000Повторное выполнение
Редактирование заметкиАпостроф и кириллица сохранены; NULL, пустая строка и ноль различимыЗначения и типы
Отпуск со склада5 → 3; попытка взять ещё 4 оставляет 3Условное изменение
Следующая страницаid 1,2 затем 3,4; запросы освобожденыСтроки и срок жизни

Один SQL-шаблон: два полезных фильтра каталога

Посетитель выбирает категорию и максимальную цену. Для первого экрана нужны товары категории «электрика» не дороже 100 рублей, для второго — инструменты до 300 рублей. Из нашего набора первому условию соответствует Розетка. В PHP должны попасть уже отобранные товары: Розетка за 8000 копеек и Молоток за 25000 копеек. Структура SELECT остаётся одной, меняются два значения.

Что происходит с данными
  1. SQL: category = ? AND price_cents <= ?
  2. bind №1: электрика; bind №2: 10000 → SELECT → Розетка 8000
  3. Тот же request: инструменты + 30000 → новое выполнение → Молоток 25000
  4. fetch_assoc получает выбранные поля; close_request освобождает запрос

Минимальная запись по шагам

$request = cubrid_prepare($db,
    'SELECT title FROM products WHERE category = ?');
if ($request === false) { throw new RuntimeException('Нет запроса'); }
if (!cubrid_bind($request, 1, 'электрика', 'string')) {
    throw new RuntimeException('Нет параметра');
}
if (!cubrid_execute($request)) { throw new RuntimeException('Нет результата'); }
// Строки теперь читают из $request, а не из true.

products — учебное имя уже созданной таблицы; $db содержит открытое соединение. Знак вопроса не берут в кавычки: это место для значения. Число 1 у bind указывает первый маркер. Подготовка создаёт ресурс $request, bind передаёт значение отдельно от текста SQL, execute запускает запрос. Последний вызов вернёт true; массив с товаром появится только при fetch_assoc. В полной программе каждый результат дочитывается до конца до следующего выполнения того же request.

Результат самостоятельной программы

НаблюдениеРезультат
Электрика до 10000 коп.Розетка / 8000 коп.
Инструменты до 30000 коп.Молоток / 25000 коп.
Подготовленных SQL-шаблонов1
Выполнений с разными значениями2
Запрос освобождёнда
Полная программа, файлы и архив для запуска
Один SQL-шаблон: два полезных фильтра каталогаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c165Config();$db=c165Connect();$runtime=c165Runtime($db);

    $name = c165CreateTable($db, $owned, 'filter', [
        ['id'=>1,'sku'=>'CAB-01','title'=>'Кабель','category'=>'электрика','price_cents'=>12000,'qty'=>5,'note'=>null],
        ['id'=>2,'sku'=>'SOC-02','title'=>'Розетка','category'=>'электрика','price_cents'=>8000,'qty'=>3,'note'=>'Белая'],
        ['id'=>3,'sku'=>'HAM-03','title'=>'Молоток','category'=>'инструменты','price_cents'=>25000,'qty'=>2,'note'=>null]
    ]);
    $table = c165Qualified($name);
    $request = null;
    $results = [];
    $executionReturns = [];
    $closed = false;
    try {
        $request = cubrid_prepare($db, 'SELECT id, title, price_cents FROM '.$table.
            ' WHERE category = ? AND price_cents <= ? ORDER BY price_cents, id');
        if ($request === false) { throw new RuntimeException('Фильтр не подготовлен'); }
        foreach ([['электрика',10000], ['инструменты',30000]] as $filter) {
            if (cubrid_bind($request, 1, $filter[0], 'string') !== true ||
                cubrid_bind($request, 2, $filter[1], 'number') !== true) {
                throw new RuntimeException('Параметры фильтра не переданы');
            }
            $executed = cubrid_execute($request);
            if ($executed !== true) { throw new RuntimeException('Фильтр не выполнен'); }
            $executionReturns[] = $executed;
            $items = [];
            while (true) {
                $item = cubrid_fetch_assoc($request);
                if ($item === false) { break; }
                if ($item === null) { throw new RuntimeException('Чтение фильтра прервано'); }
                $items[] = ['id'=>(int)$item['id'], 'title'=>$item['title'], 'price_cents'=>(int)$item['price_cents']];
            }
            $results[] = $items;
        }
    } finally {
        if (is_resource($request)) {
            $closed = cubrid_close_request($request);
            $request = null;
        }
    }
    $rows = [['Электрика до 10000 коп.',$results[0][0]['title'].' / '.$results[0][0]['price_cents'].' коп.'],
        ['Инструменты до 30000 коп.',$results[1][0]['title'].' / '.$results[1][0]['price_cents'].' коп.'],
        ['Подготовленных SQL-шаблонов',1],['Выполнений с разными значениями',count($executionReturns)],['Запрос освобождён',$closed]];
    $checks = ['first_filter'=>$results[0] === [['id'=>2,'title'=>'Розетка','price_cents'=>8000]],
        'second_filter'=>$results[1] === [['id'=>3,'title'=>'Молоток','price_cents'=>25000]],
        'prepared_execute_returns_bool'=>$executionReturns === [true,true],
        'request_closed'=>$closed === true && $request === null];
    $observations = ['filters'=>[['category'=>'электрика','max_price_cents'=>10000],['category'=>'инструменты','max_price_cents'=>30000]],
        'selected_rows'=>$results,'execute_returns'=>$executionReturns,'one_prepare_two_executions'=>true];

} finally {
    if (is_resource($db)) {try {$removed=c165Cleanup($db,$owned);} finally {c165Close($db);}}
}

c165Report('cub165-prepared-filter','Один SQL-шаблон: два полезных фильтра каталога',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Электрика до 10000 коп. — Розетка / 8000 коп.
Инструменты до 30000 коп. — Молоток / 25000 коп.
Подготовленных SQL-шаблонов — 1
Выполнений с разными значениями — 2
Запрос освобождён — да

Распакуйте все файлы и запустите example.php в отдельном окружении по README.txt. lab.php проверяет маркер изолированного стенда, создаёт только собственную c165-таблицу и удаляет её в finally. result.html содержит видимые значения, result.json — данные и наблюдения, metrics.json — проверки и факт очистки. Параметры подключения берутся из закрытого окружения и не записываются в публичные результаты.

Открыть фактический результат · Открыть данные JSON.

Ограничения, ошибки и развитие задачи

Параметр не подставляет имя таблицы, столбца или ASC/DESC. Если интерфейс выбирает сортировку, сопоставьте разрешённый режим с заранее написанным ORDER BY. Подготовка не обещает ускорение любого единичного запроса, а повторное выполнение не сохраняет предыдущие строки. Цены здесь — ограниченные целые копейки; правила для больших DECIMAL и денежных округлений требуют отдельного решения. Для большого каталога проверяют подходящий индекс и план, а не выгружают все товары для array_filter/usort.

В вопросе про mysqli имена колонок попытались передать через ?, после чего не проверили неуспешный prepare. Исходное обсуждение. Здесь используется другой драйвер, но полезна сама граница задачи: значения передаются bind, структура SQL выбирается из разрешённых вариантов. Контракт CUBRID проверяется по его документации и исходнику.

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

Как изменить второй фильтр, чтобы показать товары категории «электрика» до 15000 копеек, сохранив тот же SQL?

Разбор решения

Передайте электрика и 15000 во втором наборе bind. SQL вернёт Розетку 8000, затем Кабель 12000 согласно ORDER BY price_cents, id. Меняется массив ожидаемых строк, а prepare по-прежнему выполняется один раз.

Сохранить апостроф, пустую заметку и настоящее отсутствие значения

В форме редактирования название содержит апостроф, а заметка может отсутствовать или быть намеренно пустой. Сохраним «Кабель O'Reilly» без ручного добавления слешей. Затем прочитаем три разных состояния: NULL, строку длины 0 и буквальный текст NULL. Остаток 0 останется числом, а не потеряется при проверке empty().

Что происходит с данными
  1. PHP: title = Кабель O'Reilly, note = null, qty = 0
  2. bind передаёт значения отдельно от UPDATE
  3. Вторая карточка получает note = пустая строка; третья — четыре буквы NULL
  4. SELECT возвращает точные значения; WHERE note IS NULL выбирает только id = 1

Минимальная запись по шагам

$note = null;
if (!cubrid_bind($request, 2, $note, 'null')) {
    throw new RuntimeException('Заметка не передана');
}
// Второй маркер подготовленного UPDATE получает SQL NULL.
// Строка 'NULL' передала бы четыре буквы.

В PHP null — специальное значение отсутствия данных; SQL NULL играет похожую роль в таблице. Параметр 2 относится ко второму ? в UPDATE title = ?, note = ?. В закреплённом драйвере PHP null обрабатывается отдельно от преобразования строки. Для проверки отсутствия в SQL пишут IS NULL, а не = NULL и не = ''. Пустая заметка содержит строку, пусть и длины 0. В публичном HTML данные экранирует отчёт: безопасный SQL-параметр сам по себе не делает текст безопасным HTML.

Результат самостоятельной программы

НаблюдениеРезультат
Карточка 1Кабель O'Reilly / остаток 0
Заметка 1NULL: значение отсутствует
Заметка 2Пустая строка: 0 символов
Заметка 3NULL: четыре буквы
IS NULL: найденный id1
Обновлено карточек3
Полная программа, файлы и архив для запуска
Сохранить апостроф, пустую заметку и настоящее отсутствие значенияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c165Config();$db=c165Connect();$runtime=c165Runtime($db);

    $name = c165CreateTable($db, $owned, 'nulltext', [
        ['id'=>1,'sku'=>'NOTE-01','title'=>'Черновик 1','category'=>'каталог','price_cents'=>12900,'qty'=>1,'note'=>'проверить'],
        ['id'=>2,'sku'=>'NOTE-02','title'=>'Черновик 2','category'=>'каталог','price_cents'=>900,'qty'=>1,'note'=>'проверить'],
        ['id'=>3,'sku'=>'NOTE-03','title'=>'Черновик 3','category'=>'каталог','price_cents'=>8000,'qty'=>1,'note'=>'проверить']
    ]);
    $table = c165Qualified($name);
    $input = [[1,"Кабель O'Reilly",null,0], [2,'Клемма','',3], [3,'Розетка','NULL',1]];
    $request = null;
    $counts = [];
    try {
        $request = cubrid_prepare($db, 'UPDATE '.$table.' SET title = ?, note = ?, qty = ? WHERE id = ?');
        if ($request === false) { throw new RuntimeException('Обновление не подготовлено'); }
        foreach ($input as $item) {
            $noteType = $item[2] === null ? 'null' : 'string';
            if (!cubrid_bind($request, 1, $item[1], 'string') ||
                !cubrid_bind($request, 2, $item[2], $noteType) ||
                !cubrid_bind($request, 3, $item[3], 'number') ||
                !cubrid_bind($request, 4, $item[0], 'number')) {
                throw new RuntimeException('Значения карточки не переданы');
            }
            if (cubrid_execute($request) !== true) { throw new RuntimeException('Карточка не сохранена'); }
            $affected = cubrid_affected_rows($request);
            if ($affected !== 1) { throw new RuntimeException('Нужна одна обновлённая карточка'); }
            $counts[] = $affected;
        }
    } finally {
        if (is_resource($request)) { cubrid_close_request($request); $request = null; }
    }
    c165Commit($db);
    $items = c165Read($db, 'SELECT id, title, note, qty FROM '.$table.' ORDER BY id');
    $missing = c165Read($db, 'SELECT id FROM '.$table.' WHERE note IS NULL ORDER BY id');
    $normalized = [];
    foreach ($items as $item) {
        $normalized[] = ['id'=>(int)$item['id'],'title'=>$item['title'],'note'=>$item['note'],'qty'=>(int)$item['qty']];
    }
    $rows = [['Карточка 1',$normalized[0]['title'].' / остаток '.$normalized[0]['qty']],
        ['Заметка 1',$normalized[0]['note'] === null ? 'NULL: значение отсутствует' : 'ошибка'],
        ['Заметка 2',$normalized[1]['note'] === '' ? 'Пустая строка: 0 символов' : 'ошибка'],
        ['Заметка 3',$normalized[2]['note'].': четыре буквы'],
        ['IS NULL: найденный id',(int)$missing[0]['id']],['Обновлено карточек',array_sum($counts)]];
    $checks = ['apostrophe_utf8'=>$normalized[0]['title'] === "Кабель O'Reilly",
        'null_is_null'=>$normalized[0]['note'] === null,'empty_is_empty'=>$normalized[1]['note'] === '',
        'null_word_is_text'=>$normalized[2]['note'] === 'NULL','zero_quantity'=>$normalized[0]['qty'] === 0,
        'is_null_filter'=>count($missing) === 1 && (int)$missing[0]['id'] === 1,
        'three_targeted_updates'=>$counts === [1,1,1]];
    $observations = ['stored_rows'=>$normalized,'affected_each'=>$counts,
        'null_filter_ids'=>array_map('intval',array_column($missing,'id')),'sql_built_without_input_concatenation'=>true];

} finally {
    if (is_resource($db)) {try {$removed=c165Cleanup($db,$owned);} finally {c165Close($db);}}
}

c165Report('cub165-null-text','Сохранить апостроф, пустую заметку и настоящее отсутствие значения',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Карточка 1 — Кабель O'Reilly / остаток 0
Заметка 1 — NULL: значение отсутствует
Заметка 2 — Пустая строка: 0 символов
Заметка 3 — NULL: четыре буквы
IS NULL: найденный id — 1
Обновлено карточек — 3

Распакуйте все файлы и запустите example.php в отдельном окружении по README.txt. lab.php проверяет маркер изолированного стенда, создаёт только собственную c165-таблицу и удаляет её в finally. result.html содержит видимые значения, result.json — данные и наблюдения, metrics.json — проверки и факт очистки. Параметры подключения берутся из закрытого окружения и не записываются в публичные результаты.

Открыть фактический результат · Открыть данные JSON.

Ограничения, ошибки и развитие задачи

Bind не проверяет длину, допустимость цены или бизнес-правила формы. Перед ним определите, означает ли пустое поле «удалить заметку» или «оставить пустую строку». Не превращайте все пустые значения через empty(): она также считает 0 пустым. CUBRID имеет настройки совместимости, способные влиять на трактовку пустой строки; результат этого проекта относится к указанной в README лабораторной конфигурации. Сервер и клиент должны согласовать UTF-8, одного заголовка HTML недостаточно.

В вопросе про MySQL поле выглядело пустым, но содержало NULL; условие = пустой строке не выбрало ничего. Исходное обсуждение. Мы сохраняем и читаем все состояния отдельно. Решение не копирует драйверный код ответа; для CUBRID используются собственные bind и IS NULL.

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

Как выбрать две карточки: с отсутствующей заметкой и с пустой заметкой, не включая текст NULL?

Разбор решения

Добавьте WHERE note IS NULL OR note = '' и явный ORDER BY id. В данных проекта это id 1 и 2. Значение 'NULL' у третьей строки является обычным текстом и не совпадает ни с одной из этих проверок.

Списать остаток одним условным UPDATE и отличить отказ от ошибки

На складе пять единиц Кабеля. Первому покупателю нужны две: сохраняется остаток 3. Следующему нужны четыре: такая операция должна оставить 3. Проверку достаточного количества и уменьшение объединяем в одном UPDATE. Успешный SQL при этом может изменить ноль строк — это важный бизнес-результат, а не повод печатать «заказ принят».

Что происходит с данными
  1. Кабель: qty = 5; wanted = 2
  2. UPDATE qty = qty - 2 WHERE id = 1 AND qty >= 2 → affected = 1 → qty = 3
  3. wanted = 4 → условие qty >= 4 не выполняется → affected = 0
  4. SELECT конкретной карточки подтверждает qty = 3

Минимальная запись по шагам

if (cubrid_execute($request) !== true) {
    throw new RuntimeException('Ошибка SQL');
}
$changed = cubrid_affected_rows($request);
if ($changed === 1) { echo 'Товар зарезервирован'; }
elseif ($changed === 0) { echo 'Строка не подошла под условие'; }
else { throw new RuntimeException('Не удалось определить изменение'); }

execute отвечает на вопрос «выполнилась ли команда», affected_rows — «сколько строк она изменила». Это разные вопросы. === сравнивает также тип: 0 нельзя объединять с false. В полной команде три вопросительных знака: число для вычитания, id товара и то же число для условия достаточности. Каждый маркер получает собственный bind. Количество проверяется положительным до SQL: отрицательное число иначе увеличило бы остаток.

Результат самостоятельной программы

НаблюдениеРезультат
До списанияКабель / остаток 5
Запрос 2 шт.Изменено 1; осталось 3
Запрос ещё 4 шт.Изменено 0; осталось 3
Второй SQL выполнен успешнода
Второе списание принятонет
Полная программа, файлы и архив для запуска
Списать остаток одним условным UPDATE и отличить отказ от ошибкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c165Config();$db=c165Connect();$runtime=c165Runtime($db);

    $name = c165CreateTable($db, $owned, 'stock', [
        ['id'=>1,'sku'=>'CAB-01','title'=>'Кабель','category'=>'электрика','price_cents'=>12000,'qty'=>5,'note'=>null]
    ]);
    $table = c165Qualified($name);
    $request = null;
    $outcomes = [];
    try {
        $request = cubrid_prepare($db, 'UPDATE '.$table.' SET qty = qty - ? WHERE id = ? AND qty >= ?');
        if ($request === false) { throw new RuntimeException('Списание не подготовлено'); }
        foreach ([2,4] as $wanted) {
            if ($wanted <= 0) { throw new InvalidArgumentException('Количество должно быть положительным'); }
            if (!cubrid_bind($request, 1, $wanted, 'number') ||
                !cubrid_bind($request, 2, 1, 'number') ||
                !cubrid_bind($request, 3, $wanted, 'number')) {
                throw new RuntimeException('Параметры списания не переданы');
            }
            $executed = cubrid_execute($request);
            if ($executed !== true) { throw new RuntimeException('SQL списания не выполнен'); }
            // Счётчик снимается до любого следующего SQL на этом соединении.
            $affected = cubrid_affected_rows($request);
            if ($affected !== 0 && $affected !== 1) { throw new RuntimeException('Неожиданный счётчик изменения'); }
            c165Commit($db);
            $current = c165Read($db, 'SELECT title, qty FROM '.$table.' WHERE id = ?', [1]);
            $outcomes[] = ['wanted'=>$wanted,'execute'=>$executed,'affected'=>$affected,'qty'=>(int)$current[0]['qty']];
        }
    } finally {
        if (is_resource($request)) { cubrid_close_request($request); $request = null; }
    }
    $rows = [['До списания','Кабель / остаток 5'],['Запрос 2 шт.','Изменено '.$outcomes[0]['affected'].'; осталось '.$outcomes[0]['qty']],
        ['Запрос ещё 4 шт.','Изменено '.$outcomes[1]['affected'].'; осталось '.$outcomes[1]['qty']],
        ['Второй SQL выполнен успешно',$outcomes[1]['execute']],['Второе списание принято',$outcomes[1]['affected'] === 1]];
    $checks = ['first_change'=>$outcomes[0] === ['wanted'=>2,'execute'=>true,'affected'=>1,'qty'=>3],
        'insufficient_stock'=>$outcomes[1] === ['wanted'=>4,'execute'=>true,'affected'=>0,'qty'=>3],
        'no_negative_stock'=>$outcomes[1]['qty'] >= 0,'zero_not_query_failure'=>$outcomes[1]['execute'] === true && $outcomes[1]['affected'] === 0];
    $observations = ['initial'=>['title'=>'Кабель','qty'=>5],'operations'=>$outcomes,
        'predicate_and_decrement_in_one_sql'=>true,'affected_captured_before_readback'=>true];

} finally {
    if (is_resource($db)) {try {$removed=c165Cleanup($db,$owned);} finally {c165Close($db);}}
}

c165Report('cub165-conditional-stock','Списать остаток одним условным UPDATE и отличить отказ от ошибки',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
До списания — Кабель / остаток 5
Запрос 2 шт. — Изменено 1; осталось 3
Запрос ещё 4 шт. — Изменено 0; осталось 3
Второй SQL выполнен успешно — да
Второе списание принято — нет

Распакуйте все файлы и запустите example.php в отдельном окружении по README.txt. lab.php проверяет маркер изолированного стенда, создаёт только собственную c165-таблицу и удаляет её в finally. result.html содержит видимые значения, result.json — данные и наблюдения, metrics.json — проверки и факт очистки. Параметры подключения берутся из закрытого окружения и не записываются в публичные результаты.

Открыть фактический результат · Открыть данные JSON.

Ограничения, ошибки и развитие задачи

Ноль также возможен, если id вообще не существует: один счётчик не различает все причины. В нашей фиксированной таблице товар существует, а последующий адресный SELECT показывает его остаток. Для заказа с несколькими товарами, платежом и журналом потребуется общая транзакция и обработка конфликтов; один UPDATE не делает всю покупку атомарной. Подготовленный запрос не защищает от повторного бизнес-запроса клиента: для повторов нужен отдельный ключ операции. Здесь нет теста параллельных клиентов и утверждения о его измеренной производительности.

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

Что произойдёт, если второй покупатель запросит ровно три единицы?

Разбор решения

Условие qty >= 3 выполнится. affected_rows станет 1, новый qty будет 0. Это допустимый результат; показ нулевого остатка нельзя пропускать через if ($qty). Измените второй набор параметров и ожидаемую вторую строку отчёта.

Получить две страницы готовых строк и закрыть запросы

Экран показывает по два товара и кнопку «Дальше». Не требуется загружать весь каталог, затем сортировать и вырезать PHP-массив. Первая SQL-страница вернёт Кабель и Розетку, следующая — Клемму и Молоток. Псевдонимы колонок дадут понятные ключи массива, а отдельное закрытие каждого запроса покажет его срок жизни.

Что происходит с данными
  1. afterId = 0 → WHERE id > 0 ORDER BY id LIMIT 2 → id 1, 2
  2. fetch_assoc: item_title = Кабель, available = 0 → строка экрана
  3. Последний id = 2 → следующий SQL выбирает id 3, 4
  4. Оба request закрыты; отдельный SELECT id = 4 по прежнему connection работает

Минимальная запись по шагам

$item = cubrid_fetch_assoc($request);
if ($item === false) { echo 'Больше строк нет'; }
elseif ($item === null) { throw new RuntimeException('Ошибка чтения'); }
else { echo $item['item_title']; }
// item_title — AS item_title в SELECT, а не произвольное имя PHP.

Ассоциативный массив связывает имя колонки со значением. Выражение $item['item_title'] читает одну ячейку текущей строки. Каждый fetch передвигается дальше; возврат false заканчивает цикл. Возврат null требует диагностики, это не обычный конец. В полном SELECT id AS item_id переименовывает столбец только в результате. CAST в PHP применяется к известным небольшим целым из нашей схемы, а не ко всем полям подряд.

Результат самостоятельной программы

НаблюдениеРезультат
Страница 1, id 1Кабель / остаток 0
Страница 1, id 2Розетка / остаток 3
Страница 2, id 3Клемма / остаток 7
Страница 2, id 4Молоток / остаток 2
Освобождённых запросов2
Соединение после close_requestМолоток / остаток 2
Полная программа, файлы и архив для запуска
Получить две страницы готовых строк и закрыть запросыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
    $config=c165Config();$db=c165Connect();$runtime=c165Runtime($db);

    $name = c165CreateTable($db, $owned, 'pages', [
        ['id'=>1,'sku'=>'CAB-01','title'=>'Кабель','category'=>'электрика','price_cents'=>12000,'qty'=>0,'note'=>null],
        ['id'=>2,'sku'=>'SOC-02','title'=>'Розетка','category'=>'электрика','price_cents'=>8000,'qty'=>3,'note'=>null],
        ['id'=>3,'sku'=>'CLP-03','title'=>'Клемма','category'=>'электрика','price_cents'=>900,'qty'=>7,'note'=>null],
        ['id'=>4,'sku'=>'HAM-04','title'=>'Молоток','category'=>'инструменты','price_cents'=>25000,'qty'=>2,'note'=>null]
    ]);
    $table = c165Qualified($name);
    $afterId = 0;
    $pages = [];
    $closed = [];
    $eof = [];
    for ($page = 0; $page < 2; $page++) {
        $request = null;
        try {
            $request = cubrid_prepare($db, 'SELECT id AS item_id, title AS item_title, qty AS available FROM '.$table.
                ' WHERE id > ? ORDER BY id LIMIT 2');
            if ($request === false) { throw new RuntimeException('Страница не подготовлена'); }
            if (!cubrid_bind($request, 1, $afterId, 'number') || cubrid_execute($request) !== true) {
                throw new RuntimeException('Страница не получена');
            }
            $items = [];
            while (true) {
                $item = cubrid_fetch_assoc($request);
                if ($item === false) { $eof[] = true; break; }
                if ($item === null) { throw new RuntimeException('Ошибка чтения страницы'); }
                $items[] = ['id'=>(int)$item['item_id'],'title'=>$item['item_title'],'qty'=>(int)$item['available']];
                $afterId = (int)$item['item_id'];
            }
            $pages[] = $items;
        } finally {
            if (is_resource($request)) { $closed[] = cubrid_close_request($request); $request = null; }
        }
    }
    $last = c165Read($db, 'SELECT title, qty FROM '.$table.' WHERE id = ?', [4]);
    $rows = [];
    foreach ($pages as $index => $items) {
        foreach ($items as $item) {
            $rows[] = ['Страница '.($index+1).', id '.$item['id'],$item['title'].' / остаток '.$item['qty']];
        }
    }
    $rows[] = ['Освобождённых запросов',count(array_filter($closed))];
    $rows[] = ['Соединение после close_request',$last[0]['title'].' / остаток '.(int)$last[0]['qty']];
    $checks = ['first_page'=>$pages[0] === [['id'=>1,'title'=>'Кабель','qty'=>0],['id'=>2,'title'=>'Розетка','qty'=>3]],
        'second_page'=>$pages[1] === [['id'=>3,'title'=>'Клемма','qty'=>7],['id'=>4,'title'=>'Молоток','qty'=>2]],
        'native_eof_false'=>$eof === [true,true],'requests_closed'=>$closed === [true,true],
        'connection_remains_usable'=>count($last) === 1 && $last[0]['title'] === 'Молоток' && (int)$last[0]['qty'] === 2,
        'unique_page_ids'=>array_merge(array_column($pages[0],'id'),array_column($pages[1],'id')) === [1,2,3,4]];
    $observations = ['pages'=>$pages,'last_id'=>$afterId,'request_close_returns'=>$closed,
        'eof_observed'=>$eof,'where_order_limit_executed_in_database'=>true];

} finally {
    if (is_resource($db)) {try {$removed=c165Cleanup($db,$owned);} finally {c165Close($db);}}
}

c165Report('cub165-page-results','Получить две страницы готовых строк и закрыть запросы',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Страница 1, id 1 — Кабель / остаток 0
Страница 1, id 2 — Розетка / остаток 3
Страница 2, id 3 — Клемма / остаток 7
Страница 2, id 4 — Молоток / остаток 2
Освобождённых запросов — 2
Соединение после close_request — Молоток / остаток 2

Распакуйте все файлы и запустите example.php в отдельном окружении по README.txt. lab.php проверяет маркер изолированного стенда, создаёт только собственную c165-таблицу и удаляет её в finally. result.html содержит видимые значения, result.json — данные и наблюдения, metrics.json — проверки и факт очистки. Параметры подключения берутся из закрытого окружения и не записываются в публичные результаты.

Открыть фактический результат · Открыть данные JSON.

Ограничения, ошибки и развитие задачи

SQL LIMIT ограничивает размер ответа, но сам по себе не доказывает использование индекса; план и объём просматриваемых строк изучаются отдельно. Уникальный id задаёт однозначный порядок. Между независимыми запросами данные могут меняться: такая навигация не равна единому снимку базы, а смена сортировки на цену требует курсора из цены и id. close_request освобождает именно запрос; disconnect закрывает соединение. Не вызывайте fetch или close повторно для освобождённого ресурса, не закрывайте чужую копию дескриптора.

В SQL-галерее строки с одинаковым числом голосов меняли взаимный порядок и попадали на разные страницы. Исходное обсуждение. Собственный каталог использует уникальный id и условие продолжения. Это объясняет однозначность порядка, но не обещает неизменный снимок между запросами.

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

Для карточки нужны только название и остаток. Можно ли убрать id из SELECT второй страницы?

Разбор решения

Для показа — да, но приложению всё равно нужен ключ продолжения. Оставьте id в служебных данных результата и не показывайте его пользователю, если интерфейсу он не нужен. Курсор следующей страницы берётся из реально полученной последней строки, а не вычисляется как номер страницы × 2.

Шесть функций от SQL-шаблона до закрытия запроса

ФункцияПолезное действие
cubrid_prepareОтделить постоянный SQL от значений и создать запрос для повторного выполнения.
cubrid_bindПередать значение конкретному маркеру, сохранив его смысл и тип.
cubrid_executeВыполнить прямой SQL либо подготовленный запрос и проверить его результат.
cubrid_affected_rowsОтличить изменённые строки от случая, когда условие не выбрало ни одной.
cubrid_fetch_assocПолучить следующую строку с именованными ячейками и отличить EOF от ошибки.
cubrid_close_requestОсвободить запрос после работы, не закрывая всё соединение.

Строить запрос под текущую задачу экрана

Сначала решите, что действительно должен показать или изменить текущий экран. Затем выберите нужные поля, WHERE, однозначный порядок и допустимое количество строк. Значения передайте отдельно через bind. Сохраните affected_rows сразу после записи, различайте конец чтения и ошибку, освобождайте принадлежащий вам request. Эти шаги дают понятное поведение даже тогда, когда таблица гораздо больше учебного набора.

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

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

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

cubrid_prepareОтделить постоянный SQL от значений и создать запрос для повторного выполнения.

PECL CUBRID >= 8.3.0; профили PHP и сервера проверяются отдельно. · cubrid

cubrid_prepare(resource $conn_identifier, string $prepare_stmt, int $option = 0): resource|false

Фильтр каталога должен меняться от запроса пользователя, но текст SELECT остаётся понятным и контролируемым. prepare отделяет этот шаблон от его значений. Результат — ресурс подготовленного запроса: с ним можно связать параметры, выполнить SQL и прочитать найденные строки.

Базовая запись

cubrid_prepare(resource $conn_identifier, string $prepare_stmt, int $option = 0): resource|false

Это справочная сигнатура, а не строка для выполнения. resource обозначает ресурс расширения; знак | перечисляет возможные виды результата. Метка PECL CUBRID 8.3.0 относится к версии расширения, не к PHP 8.3. Для запуска используется отдельный профиль native CUBRID, описанный в README проектов; основной PHP сайта не меняется.

ПараметрЧто передать
conn_identifierОткрытое соединение native cubrid, полученное через cubrid_connect либо connect_with_url.
prepare_stmtОдна SQL-команда. Для изменяемых значений пишут маркеры ?; данные передают отдельными bind.
option = 0Дополнительная опция CUBRID_INCLUDE_OID включает поддержку OID результата. Для обычного каталога она не нужна. Это не размер страницы и не параметр транзакции.
РезультатКак понимать
resourceПодготовленный request. Это ещё не товар, не массив и не число найденных строк.
falseПодготовка не выполнена: например, ошибка SQL, недоступный объект или соединение. Сохраните диагностику до следующего вызова и не передавайте false в bind/execute.

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

$request = cubrid_prepare($db,
    'SELECT id, title, qty FROM products WHERE sku = ?');
if ($request === false) { throw new RuntimeException('Поиск не подготовлен'); }
try {
    if (!cubrid_bind($request, 1, 'CAB-01', 'string') ||
        cubrid_execute($request) !== true) {
        throw new RuntimeException('Поиск не выполнен');
    }
    $item = cubrid_fetch_assoc($request);
    if ($item === null) { throw new RuntimeException('Ошибка чтения'); }
    // $item === false: артикул не найден; массив: поля товара.
} finally {
    cubrid_close_request($request);
    $request = null;
}

В контекстном фрагменте products уже создана, а $db открыт. CAB-01 передаётся как строковое значение, даже если в нём есть знаки SQL. Первая позиция означает первый ?, а не номер столбца таблицы. Полный проект двух фильтров показывает ту же последовательность с двумя параметрами и видимыми результатами Розетка 8000 / Молоток 25000.

Применение 2: подготовить адресное изменение остатка

$request = cubrid_prepare($db,
    'UPDATE products SET qty = qty - ? WHERE id = ? AND qty >= ?');
if ($request === false) { throw new RuntimeException('Списание не подготовлено'); }
// Маркеры 1 и 3 получают количество, маркер 2 — id товара.
// Выполнение и affected_rows определят, было ли принято списание.

Подготовка сама не уменьшит остаток. Самостоятельное списание передаёт 2/1/2, выполняет запрос и получает остаток 3. Следующая попытка 4/1/4 использует тот же подготовленный ресурс и изменяет 0 строк. Все параметры привязываются заново явно: изменение PHP-переменной само по себе не является вызовом bind.

Маркеры предназначены для значений, а не частей синтаксиса. SELECT ? вернёт переданную величину, а не выберет колонку с таким именем. Для разных столбцов сортировки используйте разрешённое соответствие вроде name → title и price → price_cents; значение посетителя не вставляют напрямую в SQL. Имена собственных таблиц в полных проектах формирует и проверяет лабораторный helper, а не HTTP-параметр.

Подготовка не создаёт таблицу, не проверяет бизнес-правила и не делает запрос автоматически оптимальным. До следующего выполнения дочитайте либо корректно освободите предыдущий результат по правилам драйвера. После cubrid_close_request prepare вызывается заново для нового request. Соседние шаги: cubrid_bind, cubrid_execute, cubrid_fetch_assoc.

cubrid_bindПередать значение конкретному маркеру, сохранив его смысл и тип.

PECL CUBRID >= 8.3.0; профили PHP и сервера проверяются отдельно. · cubrid

cubrid_bind(resource $req_identifier, int $bind_index, mixed $bind_value, string $bind_value_type = "string"): bool

Название с апострофом, максимальная цена и отсутствие заметки должны попасть в нужные места SQL как данные. bind связывает одно значение с одной позицией уже подготовленного запроса. Он не выполняет команду: для этого нужен execute.

Базовая запись

cubrid_bind(resource $req_identifier, int $bind_index, mixed $bind_value, string $bind_value_type = "string"): bool

Это справочная сигнатура, а не строка для выполнения. resource обозначает ресурс расширения; знак | перечисляет возможные виды результата. Метка PECL CUBRID 8.3.0 относится к версии расширения, не к PHP 8.3. Для запуска используется отдельный профиль native CUBRID, описанный в README проектов; основной PHP сайта не меняется.

ПараметрЧто передать
req_identifierРесурс успешного cubrid_prepare. Обычный массив данных или resource соединения сюда не подходят.
bind_indexПорядковый номер ? от 1 до числа маркеров. Каждое вхождение имеет отдельную позицию, даже если значение повторяется.
bind_valuePHP-значение, которое нужно передать. PHP null передаёт SQL NULL; текст NULL остаётся текстом при строковом типе.
bind_value_typeНеобязательное имя типа; по умолчанию STRING. Это строка вроде string, number или null, а не PHP-константа. Регистр имени несущественен в закреплённом драйвере.
РезультатКак понимать
trueЗначение связано с указанной позицией. Это не подтверждение INSERT/UPDATE.
falseСвязать не удалось. Проверяйте каждый вызов: вне диапазона позиций отказ может не сопровождаться серверным текстом ошибки.

Документированные типы и особые случаи

ИмяНазначение и ограничение
STRINGСимвольные CHAR/VARCHAR; тип по умолчанию. Апостроф передаётся без ручного экранирования SQL.
NCHARНациональные символьные типы NCHAR/NVARCHAR: укажите тип явно.
BITBIT/VARBIT: явное указание типа важно; не смешивайте строку битов с любым файловым содержимым.
NUMERIC / NUMBERТочное числовое значение. Для учебных целых копеек и количества передаётся ограниченное целое; произвольный пользовательский текст сначала проверяют.
FLOAT / DOUBLEПриблизительные числовые типы; не заменяют точное денежное представление.
TIME / DATE / TIMESTAMPДата и время в допустимом для сервера представлении; строковый тип не проверяет нужную бизнес-зону времени.
OBJECTСсылка на объект CUBRID; это не PHP-объект произвольного класса.
ENUMСтроковое имя элемента перечисления.
BLOB / CLOBЯвный тип; ресурс трактуется как PHP-поток, иное значение — как имя читаемого клиентского файла. Это не способ передать литеральное тело файла простой строкой. Поддержка добавлена в PECL CUBRID 8.3.1.
NULLОтсутствующее SQL-значение, которое проверяют IS NULL.
SET / MULTISET / SEQUENCEКоллекции не поддерживаются этим вызовом bind; нужны другие средства соответствующего API.

В режиме shard документация требует указывать тип параметра. В исходнике 11.2.0.0002 также есть INT, SHORT, BIGINT, DATETIME и другие внутренние соответствия. Не выводите их имена из SQL наугад: например, строка integer встречается в описании метаданных, но не является именем bind в его таблице типов. Проекты используют документированные string/number/null, а общий helper может использовать подтверждённый int для значений PHP int.

Применение 1: категория и цена из фильтра

if (!cubrid_bind($request, 1, 'электрика', 'string')) {
    throw new RuntimeException('Не передана категория');
}
if (!cubrid_bind($request, 2, 10000, 'number')) {
    throw new RuntimeException('Не передана максимальная цена');
}

Для WHERE category = ? AND price_cents <= ? эти вызовы означают категорию «электрика» и предел 10000 копеек. Сама цена 10000 не становится частью текста SQL. Полный фильтр вернёт Розетку за 8000, затем повторит bind с другой категорией и ценой.

Применение 2: убрать заметку, сохранив нулевой остаток

// Подготовлен UPDATE products SET note = ?, qty = ? WHERE id = ?.
if (!cubrid_bind($request, 1, null, 'null') ||
    !cubrid_bind($request, 2, 0, 'number') ||
    !cubrid_bind($request, 3, 1, 'number')) {
    throw new RuntimeException('Не переданы поля карточки');
}

Первый аргумент-значение отсутствует, второй равен нулю, третий выбирает конкретную запись. Это три разных назначения. В проекте с заметками дополнительно сохраняются пустая строка и четыре буквы NULL; обратное чтение показывает разницу. Если цель — пустая заметка, передают '' с типом string, а не null.

Не добавляйте кавычки или обратные слеши к bind-значению заранее: это изменит сами данные. Проверка допустимой длины, знака, диапазона, прав на карточку и кодировки остаётся отдельным шагом. Bind не защищает HTML-вывод и не интерпретирует оператор IS NULL из строки. Ошибку параметра обрабатывают до cubrid_execute; сохранённый request освобождают через cubrid_close_request.

cubrid_executeВыполнить прямой SQL либо подготовленный запрос и проверить его результат.

PECL CUBRID >= 8.3.0; профили PHP и сервера проверяются отдельно. · cubrid

cubrid_execute(resource $conn_identifier, string $sql, int $option = 0): resource|false
cubrid_execute(resource $req_identifier, int $option = 0): bool

Команда становится действием только при выполнении: фильтр начинает возвращать строки, UPDATE изменяет подходящие записи. У execute есть два режима. Их важно различать, иначе код попытается читать строку из true вместо ресурса запроса.

Базовая запись

cubrid_execute(resource $conn_identifier, string $sql, int $option = 0): resource|false
cubrid_execute(resource $req_identifier, int $option = 0): bool

Это справочная сигнатура, а не строка для выполнения. resource обозначает ресурс расширения; знак | перечисляет возможные виды результата. Метка PECL CUBRID 8.3.0 относится к версии расширения, не к PHP 8.3. Для запуска используется отдельный профиль native CUBRID, описанный в README проектов; основной PHP сайта не меняется.

ПараметрЧто передать
conn_identifier, sqlПрямой вариант: открытое соединение и полный SQL. Для постоянного SQL без входных значений удобно; данные посетителя в строку не склеивают.
req_identifierПодготовленный request. Все требуемые позиции заранее получили значения через bind.
option = 0Битовые опции. Прямой вариант: CUBRID_INCLUDE_OID, CUBRID_ASYNC, CUBRID_EXEC_QUERY_ALL. Для подготовленного варианта документация описывает только CUBRID_ASYNC.
РезультатКак понимать
Прямой вариант: resource / falseRequest для чтения и освобождения либо отказ. Это относится и к командам без SELECT-строк: возвращаемый handle нужно закрыть.
Подготовленный вариант: true / falseВыполнен ли SQL. Читать результат нужно из исходного request, а не из этого bool.

Применение 1: выполнить постоянный SELECT

$request = cubrid_execute($db,
    'SELECT id, title, qty FROM products WHERE id = 1');
if ($request === false) { throw new RuntimeException('Нет результата'); }
try {
    $item = cubrid_fetch_assoc($request);
    if ($item === null) { throw new RuntimeException('Ошибка чтения'); }
    // Здесь массив либо false, если id = 1 отсутствует.
} finally {
    cubrid_close_request($request);
    $request = null;
}

Фрагмент предполагает подготовленные products и $db. Он выбирает три поля одной строки. Если id приходит из формы, перейдите на prepare и bind; ручное добавление id в SQL не требуется. Полный путь от подключения до первой таблицы находится в первое подключение и диагностика.

Применение 2: выполнить подготовленное списание

$executed = cubrid_execute($request);
if ($executed !== true) { throw new RuntimeException('Списание не выполнено'); }
$changed = cubrid_affected_rows($request);
if ($changed === 0) {
    echo 'Ни одна строка не подошла под условие';
} elseif ($changed === 1) {
    echo 'Изменена одна карточка';
} else {
    throw new RuntimeException('Неожиданный результат изменения');
}

Запрос подготовлен как UPDATE с условием достаточного остатка. Во второй попытке полного проекта execute возвращает true, а changed — 0. Следовательно, успешный SQL нельзя приравнивать к принятому заказу. Подтверждение транзакции — отдельный шаг; execute не заменяет выбранную политику commit/rollback.

Опции, несколько команд и границы

ОпцияЗначение для программы
0Обычный синхронный вызов, используемый в этих проектах.
CUBRID_INCLUDE_OIDЗапрос поддержки OID для прямого выполнения. Это не дополнительная произвольная колонка PHP-массива.
CUBRID_ASYNCАсинхронный режим драйвера; само наличие флага не превращает PHP-код в очередь заданий и не отменяет проверку результатов.
CUBRID_EXEC_QUERY_ALLВыполняет все команды переданного SQL, возвращая первый результат. Добавлен в PECL CUBRID 8.4.0; работа синхронная даже при ASYNC. Последующие результаты читаются соответствующим API next_result.

При EXEC_QUERY_ALL ошибка в поздней команде не означает автоматический откат уже выполненных ранних команд. Не создавайте пакет произвольного пользовательского SQL и не называйте такой режим транзакцией. Для связанной бизнес-операции задайте явную транзакционную границу и обрабатывайте её отказ отдельно. В этих четырёх проектах ASYNC, OID и наборы нескольких SQL описаны как контракт, но не заявлены реально испытанными сценариями.

У prepare может быть успешный результат, а у execute — отказ из-за ограничений, типов, состояния сервера или отсутствующих bind. Сохраните код и сообщение сразу после отказа, затем освобождайте ресурсы. Повторный execute означает новое выполнение с возможными новыми изменениями: автоматически повторять списание после непонятного сетевого ответа опасно для бизнес-логики. Дальше: cubrid_affected_rows, cubrid_fetch_assoc, cubrid_close_request.

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

PECL CUBRID >= 8.3.0; профили PHP и сервера проверяются отдельно. · cubrid

cubrid_affected_rows(resource $conn_identifier = ?): int|false
cubrid_affected_rows(resource $req_identifier = ?): int|false

Сервер принял UPDATE, но подошла ли нужная строка? Счётчик позволяет отличить сохранённое изменение от случая, когда условие ничего не выбрало. Для нашего склада это разница между остатком 3 после продажи и отказом продать ещё четыре единицы.

Базовая запись

cubrid_affected_rows(resource $conn_identifier = ?): int|false
cubrid_affected_rows(resource $req_identifier = ?): int|false

Это справочная сигнатура, а не строка для выполнения. resource обозначает ресурс расширения; знак | перечисляет возможные виды результата. Метка PECL CUBRID 8.3.0 относится к версии расширения, не к PHP 8.3. Для запуска используется отдельный профиль native CUBRID, описанный в README проектов; основной PHP сайта не меняется.

ПараметрЧто передать
conn_identifier или req_identifierМожно передать открытое соединение либо request. Без аргумента используется неявный последний контекст, что неудобно при нескольких соединениях. В учебном коде аргумент явный.
РезультатКак понимать
Целое 0 и большеКоличество строк предыдущего INSERT/UPDATE/DELETE. 0 — допустимый счётчик, который интерпретируют по задаче.
-1Последняя команда не является INSERT/UPDATE/DELETE, либо её тип не подходит этому API. Это не «минус одна строка».
false / отказ контекстаНет требуемого контекста запроса/соединения; PHP Manual отдельно оговаривает отсутствие последнего request. Нельзя считать результат успешным нулём.

В исходнике 11.2.0.0002 даже вариант с request читает поля его соединения: sql_type и affected_rows. Поэтому сохраните счётчик сразу после успешного изменения, до SELECT, другой команды или переданной дальше работы. Request не является независимым архивом старого счётчика. Если execute вернул false, сначала разберите его отказ; счётчик предыдущей успешной операции не доказывает успех новой.

Применение 1: принять либо отклонить списание

if (cubrid_execute($request) !== true) {
    throw new RuntimeException('UPDATE не выполнен');
}
$changed = cubrid_affected_rows($request); // сразу, до SELECT
if ($changed === 1) { $accepted = true; }
elseif ($changed === 0) { $accepted = false; }
else { throw new RuntimeException('Некорректный счётчик'); }

Для UPDATE products SET qty = qty - ? WHERE id = ? AND qty >= ? счётчик 1 означает подходящую карточку. В полном проекте значения 2/1/2 дают 1 и остаток 3, затем 4/1/4 дают 0 и прежние 3. Отсутствие самого id также даст 0: для понятного пользователю объяснения иногда нужен дополнительный адресный запрос.

Применение 2: проверить сохранение конкретной карточки

if (cubrid_execute($editRequest) !== true) {
    throw new RuntimeException('Редактирование не выполнено');
}
$saved = cubrid_affected_rows($db);
if ($saved !== 1) {
    throw new RuntimeException('Ожидалось изменение одной карточки');
}

Здесь editRequest был подготовлен с WHERE id = ?, а $db — его соединение. Три редактирования заметок проверяют каждый счётчик до следующего SQL, затем отдельно читают сохранённые title/note/qty. Число 1 не доказывает, что выбраны правильные поля или правильный id: полезное обратное чтение остаётся способом проверить именно результат задачи.

Не используйте affected_rows для подсчёта SELECT, для числа видимых элементов интерфейса или как единственное доказательство commit. В запросе без WHERE можно изменить много строк корректным SQL, но ошибочной бизнес-логикой. Не переносите сюда без проверки особенности «найденных» и «изменённых» строк другого драйвера; пример строится на однозначных переходах 5 → 3 и отсутствии подходящей строки. Соседние функции: cubrid_execute, cubrid_fetch_assoc и cubrid_error_code.

cubrid_fetch_assocПолучить следующую строку с именованными ячейками и отличить EOF от ошибки.

PECL CUBRID >= 8.3.0; профили PHP и сервера проверяются отдельно. · cubrid

cubrid_fetch_assoc(resource $result, int $type = 0): array|false|null

SELECT уже определил, какие строки нужны экрану. fetch_assoc берёт одну из них и делает доступной как PHP-массив с именованными ячейками. Например, item_title содержит «Кабель», а available — нулевой остаток, который всё равно нужно показать.

Базовая запись

cubrid_fetch_assoc(resource $result, int $type = 0): array|false|null

Это справочная сигнатура, а не строка для выполнения. resource обозначает ресурс расширения; знак | перечисляет возможные виды результата. Метка PECL CUBRID 8.3.0 относится к версии расширения, не к PHP 8.3. Для запуска используется отдельный профиль native CUBRID, описанный в README проектов; основной PHP сайта не меняется.

ПараметрЧто передать
resultRequest успешного прямого execute либо подготовленный request после успешного execute. Bool true от подготовленного execute сюда не передают.
type, необязательныйДокументированная дополнительная опция CUBRID_LOB нужна для работы с LOB-объектами. Для обычных чисел/строк параметр пропускают.
РезультатКак понимать
arrayСледующая строка. Ключи определяются выбранными именами/псевдонимами колонок; затем курсор продвигается.
falseСтрок больше нет. Это нормальный конец, в том числе когда SELECT не нашёл ни одной строки.
nullОшибка получения строки. Не смешивайте с SQL NULL внутри уже возвращённого массива.

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

$item = cubrid_fetch_assoc($request);
if ($item === false) {
    echo 'Товар не найден';
} elseif ($item === null) {
    throw new RuntimeException('Не удалось получить карточку');
} else {
    echo htmlspecialchars($item['title'], ENT_QUOTES, 'UTF-8');
    echo ' / остаток ', (int)$item['qty'];
}

request относится к SELECT title, qty ... WHERE id = ?. После проверки вида результата читаются конкретные ячейки. Преобразование qty в int подходит ограниченному INT-остатку нашей схемы. Не применяйте такой cast ко всем значениям: точный DECIMAL, большой идентификатор и дата требуют своих правил. HTML-экранирование названия относится к выводу; bind решает другую задачу.

Применение 2: вывести ограниченную страницу

while (true) {
    $item = cubrid_fetch_assoc($request);
    if ($item === false) { break; }
    if ($item === null) { throw new RuntimeException('Страница прочитана не полностью'); }
    echo htmlspecialchars($item['item_title'], ENT_QUOTES, 'UTF-8');
    echo ' / ', (int)$item['available'], "\n";
}

Перед циклом проект страниц выполняет SELECT id AS item_id, title AS item_title, qty AS available ... ORDER BY id LIMIT 2. Цикл не выбирает и не сортирует весь каталог: он преобразует уже готовую небольшую страницу. false заканчивает чтение именно этого результата, а не означает, что во всей таблице больше нет товаров.

Одинаковые имена колонок в JOIN не дают двух разных ключей одного ассоциативного массива. Задавайте понятные уникальные AS, например product_title и category_title, и обращайтесь к ним точно. SQL NULL сохраняется как null в ячейке: array_key_exists проверяет наличие ключа, а isset дополнительно отсекает null. В сохранённых заметках показаны null, '' и 'NULL' отдельно; числовой 0 также не равен отсутствию строки.

Каждый fetch продвигает текущую позицию. Повторный вызов не возвращает прежнюю карточку; для сохранения используйте уже полученный массив. Получение очередной порции не означает, что драйвер/сервер вообще ничего не буферизуют, поэтому размер ответа ограничивают самим SQL. Ошибка в середине чтения означает неполный результат, её нельзя превратить в обычное окончание цикла. После работы нужен cubrid_close_request; формат LOB и его lifetime рассматриваются отдельным блоком CUBRID.

cubrid_close_requestОсвободить запрос после работы, не закрывая всё соединение.

PECL CUBRID >= 8.3.0; профили PHP и сервера проверяются отдельно. · cubrid

cubrid_close_request(resource $req_identifier): bool

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

Базовая запись

cubrid_close_request(resource $req_identifier): bool

Это справочная сигнатура, а не строка для выполнения. resource обозначает ресурс расширения; знак | перечисляет возможные виды результата. Метка PECL CUBRID 8.3.0 относится к версии расширения, не к PHP 8.3. Для запуска используется отдельный профиль native CUBRID, описанный в README проектов; основной PHP сайта не меняется.

ПараметрЧто передать
req_identifierЖивой request, которым владеет этот участок программы: от prepare либо прямого execute.
РезультатКак понимать
trueЗапрос закрыт. Его прежний ресурс больше не используется.
Отказ/неверный аргументДокументация гарантирует true при успехе; поведение некорректного ресурса не следует использовать как учебный API. Проверяйте успешное создание и закрывайте один раз.

Функция является псевдонимом cubrid_close_prepare. Она закрывает request, а не connection, не подтверждает транзакцию и не удаляет таблицу. Для снятия бизнес-результата сначала выполните affected_rows либо прочитайте нужные строки, затем освобождайте request. В закреплённом исходнике есть низкоуровневая работа с ресурсом без полной проверки произвольного значения; проекты не вызывают эту функцию для false/null либо повторно закрытого значения.

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

$closed = cubrid_close_request($request);
$request = null;
if ($closed !== true) { throw new RuntimeException('Запрос не освобождён'); }
$nextRequest = cubrid_execute($db,
    'SELECT title, qty FROM products WHERE id = 4');
if ($nextRequest === false) { throw new RuntimeException('Нет следующей карточки'); }
// Прочитать nextRequest и освободить его отдельно.

В полном проекте закрываются оба страничных запроса, затем отдельный SELECT по тому же $db возвращает Молоток / 2. Таким образом, человек видит различие между закрытием результата и отключением от базы. Присваивание null убирает локальную ссылку на уже освобождённый ресурс; оно не заменяет сам close.

Применение 2: убрать подготовленный фильтр даже при ошибке

$request = null;
try {
    $request = cubrid_prepare($db,
        'SELECT title FROM products WHERE category = ?');
    if ($request === false) { throw new RuntimeException('Нет запроса'); }
    // Bind, execute и обработка строк находятся здесь.
} finally {
    if (is_resource($request)) {
        cubrid_close_request($request);
        $request = null;
    }
}

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

Нельзя проверить закрытие повторным fetch или вторым close: это использование недействительного ресурса. Если ещё нужен SQL, подготовьте новый request. Когда закончена вся работа и транзакционная политика выполнена, вызовите cubrid_disconnect. Частичное освобождение буфера через другой API не следует считать взаимозаменяемым с закрытием подготовленного handle. Рядом: cubrid_prepare, cubrid_execute, cubrid_fetch_assoc.

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

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

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

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

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