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

Дерево руководства
Глава 83 · После первого подключения и основ SQL
CUBRID: параметры каталога, точный остаток и страницы результата
Родной CUBRID: параметры каталога, NULL и текст, условное списание, ограниченные страницы и срок жизни запросов.
Сначала нужные данные, затем PHP-вывод
На странице каталога нужно показать подходящие товары, сохранить правку карточки и отпустить только доступное количество. База умеет отбирать, сортировать и ограничивать строки до передачи в PHP. В этой главе результатом станут Розетка за 8000 копеек, сохранённый Кабель O'Reilly с нулевым остатком, отказ списать лишние четыре единицы и две готовые страницы по два товара.
Для начала достаточно понять три разных значения: $db — соединение с базой, $request — подготовленная или выполненная SQL-команда, $item — один PHP-массив уже прочитанной строки. Они не взаимозаменяемы. Особенно легко ошибиться с execute: прямой вызов возвращает request, а выполнение подготовленного — только true/false.
- Категория электрика и предел 10000 копеек — данные PHP
- prepare: SELECT нужных полей WHERE category = ? AND price_cents <= ?
- bind: №1 → электрика, №2 → 10000; execute запускает SQL
- CUBRID отбирает Розетку 8000; fetch_assoc возвращает её поля
- Экран показывает название и цену; 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 остаётся одной, меняются два значения.
- SQL: category = ? AND price_cents <= ?
- bind №1: электрика; bind №2: 10000 → SELECT → Розетка 8000
- Тот же request: инструменты + 30000 → новое выполнение → Молоток 25000
- 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 |
| Запрос освобождён | да |
Полная программа, файлы и архив для запуска
<?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().
- PHP: title = Кабель O'Reilly, note = null, qty = 0
- bind передаёт значения отдельно от UPDATE
- Вторая карточка получает note = пустая строка; третья — четыре буквы NULL
- 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 |
| Заметка 1 | NULL: значение отсутствует |
| Заметка 2 | Пустая строка: 0 символов |
| Заметка 3 | NULL: четыре буквы |
| IS NULL: найденный id | 1 |
| Обновлено карточек | 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 при этом может изменить ноль строк — это важный бизнес-результат, а не повод печатать «заказ принят».
- Кабель: qty = 5; wanted = 2
- UPDATE qty = qty - 2 WHERE id = 1 AND qty >= 2 → affected = 1 → qty = 3
- wanted = 4 → условие qty >= 4 не выполняется → affected = 0
- 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 выполнен успешно | да |
| Второе списание принято | нет |
Полная программа, файлы и архив для запуска
<?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-страница вернёт Кабель и Розетку, следующая — Клемму и Молоток. Псевдонимы колонок дадут понятные ключи массива, а отдельное закрытие каждого запроса покажет его срок жизни.
- afterId = 0 → WHERE id > 0 ORDER BY id LIMIT 2 → id 1, 2
- fetch_assoc: item_title = Кабель, available = 0 → строка экрана
- Последний id = 2 → следующий SQL выбирает id 3, 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_value | PHP-значение, которое нужно передать. PHP null передаёт SQL NULL; текст NULL остаётся текстом при строковом типе. |
| bind_value_type | Необязательное имя типа; по умолчанию STRING. Это строка вроде string, number или null, а не PHP-константа. Регистр имени несущественен в закреплённом драйвере. |
| Результат | Как понимать |
|---|---|
| true | Значение связано с указанной позицией. Это не подтверждение INSERT/UPDATE. |
| false | Связать не удалось. Проверяйте каждый вызов: вне диапазона позиций отказ может не сопровождаться серверным текстом ошибки. |
Документированные типы и особые случаи
| Имя | Назначение и ограничение |
|---|---|
| STRING | Символьные CHAR/VARCHAR; тип по умолчанию. Апостроф передаётся без ручного экранирования SQL. |
| NCHAR | Национальные символьные типы NCHAR/NVARCHAR: укажите тип явно. |
| BIT | BIT/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 / false | Request для чтения и освобождения либо отказ. Это относится и к командам без 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|nullSELECT уже определил, какие строки нужны экрану. 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 сайта не меняется.
| Параметр | Что передать |
|---|---|
| result | Request успешного прямого 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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


