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

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

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

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

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

CUBRID: поддерживать обычные запросы, получать свой ID и выгружать нужные строки

Свой AUTO_INCREMENT ID, сохранённая диагностика, постепенная CSV-выгрузка выбранных SQL-строк и точный поиск текста в существующем CUBRID-коде.

От задачи экрана к точному запросу

В приложении уже есть обычные cubrid_query, а люди хотят получить номер своей заявки, увидеть нужные товары, разобраться в отказе запроса и выгрузить выбранный каталог. Эта глава сохраняет контракт совместимых функций и развивает четыре задачи до конкретного CSV, который можно открыть и проверить.

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

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

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

Для начала нужны соединение и первые SQL-запросы, параметры поиска и границы commit/rollback. Здесь ещё раз разберём текущий вызов: имя функции стоит перед скобками, аргументы передают SQL и соединение, знак = сохраняет результат в переменную. Это resource для чтения, true для успешной команды либо false при отказе; эти значения нельзя смешивать с полями результата.

Задача человекаЧто просим у базыЧто получаем
Открыть только что созданную заявкуINSERT → немедленный insert_id → WHERE request_id=?701 | Кабель | 3
Понять пустой экранОтдельно отказ query и успешный пустой SELECTСохранённая причина или 0 подходящих строк
Передать складской CSVТолько sku,title,qty, нужный диапазон и остатокХомут4 и Розетка2 в файле
Найти название с апострофомСтроковый литерал либо отдельный bind-параметрКабель O'Reilly без изменения текста
SQL отбирает данные, PHP оформляет результат
  1. Требование: показать только доступные товары выбранного диапазона
  2. SELECT sku,title,qty WHERE ... ORDER BY id выполняет отбор в базе
  3. PHP получает уже нужные строки → HTML или очередная строка CSV
  4. Следующий шаг пользователя опирается на реальные данные и проверенный статус

Минимальная выборка и её смысл

$result = cubrid_query('SELECT id,title FROM catalog WHERE id=1', $db);
if ($result === false) {
    $savedCode = cubrid_errno($db);
    $savedMessage = cubrid_error($db);
    throw new RuntimeException('Карточка не открыта');
}
try { $row = cubrid_fetch_assoc($result); }
finally { cubrid_close_request($result); }
// Для нашего каталога row содержит id=1 и title=Кабель.

Это фрагмент для уже открытого соединения и подготовленной таблицы. Самостоятельные ZIP ниже создают только собственную таблицу, показывают исходные данные, проверяют файл результата и убирают таблицу после работы. Обычный запрос не обязан выбирать все поля: SELECT * передаёт ненужные колонки, а последующая сортировка огромного PHP-массива лишь добавляет работу. Условия и порядок выражайте в SQL; объём выдачи задавайте задачей экрана или экспорта.

Создать заявку и открыть именно её карточку

После отправки формы нужно открыть карточку только что созданной заявки. Номер назначает база. В проекте первая заявка получает 701, следующая — 702, а CSV первой по-прежнему содержит «701;Кабель;3». Мы сохраняем ID сразу, затем используем его как параметр точной выборки.

Путь данных и полученный результат
  1. INSERT Кабель, qty=3 → AUTO_INCREMENT=701
  2. Сразу cubrid_insert_id($db) → $ownId="701"
  3. Следующий INSERT → 702; $ownId остаётся "701"
  4. WHERE request_id=? с параметром 701 → только Кабель → created-request.csv

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

$ok = cubrid_query("INSERT INTO requests (title, qty) VALUES ('Кабель', 3)", $db);
if ($ok === false) { throw new RuntimeException('INSERT не выполнен'); }
$createdId = cubrid_insert_id($db); // До следующего SQL.
if ($createdId === false || $createdId === 0) {
    throw new RuntimeException('Сгенерированный ID не получен');
}

В requests заранее объявлен один AUTO_INCREMENT-столбец. query получает готовый SQL и соединение. Для INSERT успешный результат — true, а insert_id возвращает сам номер строкой. Переменная $createdId хранит снимок значения: последующая вставка не изменит уже сохранённую PHP-строку. commit — отдельное действие, необходимое при отключённом autocommit.

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

НаблюдениеРезультат
Созданная заявка701 | Кабель | 3
Следующая заявка702
Тип сохранённого IDstring
ID после SELECT0
CSVcreated-request.csv
Самостоятельный проект, исходник и архив
Создать заявку и открыть именно её карточкуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

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

    // Helpers preserve diagnostics; the SQL operation itself is visible below.
    function compatCall173($db,callable $action): array {
        $call=c173Capture($action);
        $call['connection_code']=cubrid_errno($db);
        $message=cubrid_error($db);
        $call['connection_message_present']=is_string($message)&&$message!=='';
        return $call;
    }
    function selected173($db,string $sql): array {
        $call=compatCall173($db,static function()use($db,$sql){return cubrid_query($sql,$db);});
        if(!is_resource($call['value']) || $call['warning']){c173Failure('Выборка не выполнена',$call);}
        $request=$call['value'];
        try {return c173Rows($request);} finally {c173Release($request);}
    }
    function csvRow173($file,array $fields): void {
        if(fputcsv($file,$fields,';','"','')===false){throw new RuntimeException('Строка CSV не записана');}
    }
    function csvSave173(string $name,array $records): void {
        $file=fopen(__DIR__.'/'.$name,'wb');
        if($file===false){throw new RuntimeException('Файл CSV не открыт');}
        try {foreach($records as $record){csvRow173($file,$record);}}
        finally {fclose($file);}
    }
    function csvRead173(string $name): array {
        $file=fopen(__DIR__.'/'.$name,'rb');
        if($file===false){throw new RuntimeException('Созданный CSV не найден');}
        $records=[];
        try {
            while(($record=fgetcsv($file,0,';','"',''))!==false){
                $records[]=$record;
                if(count($records)>12){throw new RuntimeException('Учебная CSV-выгрузка должна быть ограничена');}
            }
        } finally {fclose($file);}
        return $records;
    }

    $name=c173OwnName($db,$owned,'TABLE','own_id');$table=c173Qualified($name);
    c173Ddl($db,'CREATE TABLE '.$table.' (request_id INTEGER AUTO_INCREMENT(701,1) PRIMARY KEY,title VARCHAR(80) NOT NULL,qty INTEGER NOT NULL)');
    c173Commit($db);
    $insert=compatCall173($db,static function()use($db,$table){
        return cubrid_query("INSERT INTO ".$table." (title,qty) VALUES ('Кабель',3)",$db);
    });
    if($insert['value']!==true || $insert['warning']){c173Failure('Заявка не создана',$insert);}
    // Save the generated ID before another SQL operation changes the context.
    $ownId=cubrid_insert_id($db);
    if(!is_string($ownId) || !preg_match('/^[0-9]+$/D',$ownId)){throw new RuntimeException('Не получен ID своей вставки');}
    c173Commit($db);
    $next=compatCall173($db,static function()use($db,$table){
        return cubrid_query("INSERT INTO ".$table." (title,qty) VALUES ('Хомут',2)",$db);
    });
    if($next['value']!==true || $next['warning']){c173Failure('Вторая заявка не создана',$next);}
    $nextId=cubrid_insert_id($db);
    if(!is_string($nextId)){throw new RuntimeException('ID следующей вставки не получен');}
    c173Commit($db);
    $mine=c173Read($db,'SELECT request_id,title,qty FROM '.$table.' WHERE request_id=?',[$ownId]);
    $afterSelect=cubrid_insert_id($db);
    $all=c173Read($db,'SELECT request_id,title,qty FROM '.$table.' ORDER BY request_id');
    if(count($mine)!==1){throw new RuntimeException('Карточка своей заявки не найдена');}
    $record=$mine[0];
    csvSave173('created-request.csv',[['request_id','title','qty'],[$record['request_id'],$record['title'],$record['qty']]]);
    $parsed=csvRead173('created-request.csv');
    $rows=[['Созданная заявка',$record['request_id'].' | '.$record['title'].' | '.$record['qty']],
        ['Следующая заявка',$nextId],['Тип сохранённого ID',gettype($ownId)],
        ['ID после SELECT',$afterSelect],['CSV','created-request.csv']];
    $checks=['insert_true'=>$insert['value']===true,'own_id_string'=>$ownId==='701',
        'next_id_separate'=>$nextId==='702' && $ownId!==$nextId,
        'own_card_exact'=>$mine===[['request_id'=>'701','title'=>'Кабель','qty'=>'3']],
        'two_requests_saved'=>count($all)===2 && $all[1]['request_id']==='702',
        'select_clears_insert_context'=>$afterSelect===0,
        'csv_exact'=>$parsed===[['request_id','title','qty'],['701','Кабель','3']]];
    $observations=['created_id'=>$ownId,'later_id'=>$nextId,'own_card'=>$mine,'all_requests'=>$all,
        'after_select_insert_id'=>$afterSelect,'csv_rows'=>$parsed];

} finally {

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

c173Report('cub173-own-insert-id','Создать заявку и открыть именно её карточку',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Созданная заявка — 701 | Кабель | 3
Следующая заявка — 702
Тип сохранённого ID — string
ID после SELECT — 0
CSV — created-request.csv

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

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

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

MAX(request_id) отвечает на вопрос о максимальном ключе видимых строк, а не о своей вставке. Проект не имитирует конкурентных посетителей: вторая вставка лишь наглядно показывает необходимость сохранить ID. Номер не означает наличие прав на заявку и не гарантирует непрерывную нумерацию. После SELECT проверенный драйвер возвращает 0; вызывать insert_id после произвольной цепочки SQL поздно. Несколько AUTO_INCREMENT-колонок требуют другого способа однозначно получить ключ.

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

Добавьте третью заявку после второй. Какая карточка должна остаться в created-request.csv?

Разбор

В файле остаётся первая заявка 701: выборка использует сохранённый $ownId. Новый вызов insert_id нужен непосредственно после третьего INSERT для отдельной переменной. Не заменяйте WHERE по ключу поиском по названию или MAX.

Отличить ошибочный SQL от пустого каталога

Администратор видит пустой экран. Возможны разные причины: неверное имя столбца, успешный запрос без подходящих строк или реальный товар с нулевым остатком. Проект разделяет эти состояния: сохраняет отказ запроса с absent_column, затем получает Кабель с остатком 3 и отдельно выполняет корректный поиск отсутствующего id=999.

Путь данных и полученный результат
  1. SELECT absent_column → false + ошибка соединения
  2. Сохранить код и признак текста; явно rollback
  3. SELECT id,title,qty WHERE qty>0 → Кабель,3
  4. SELECT WHERE id=999 → пустой набор, а не отказ query

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

$request = cubrid_query('SELECT id, title FROM catalog WHERE id=999', $db);
if ($request === false) {
    $savedCode = cubrid_errno($db);
    $savedMessage = cubrid_error($db); // До другого SQL или очистки.
    throw new RuntimeException('Каталог временно недоступен');
}
$row = cubrid_fetch_assoc($request); // false в конце набора — не то же место отказа.
cubrid_close_request($request);

Результат самого query проверяется до чтения строки. resource означает выполненный запрос с набором, который может быть пустым. errno — число, error — текст для диагностики. Сохраните оба, прежде чем повторять запрос. В полном проекте предупреждение перехватывается, raw-текст не попадает в публичный HTML/CSV: он может содержать SQL и приватные имена.

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

НаблюдениеРезультат
Ошибочный столбецSQL отклонён; причина сохранена
Сохранённый код-494
Исправленный каталог1 | Кабель | 3
Новая диагностика0
Поиск id=9990
CSVquery-diagnostics.csv
Самостоятельный проект, исходник и архив
Отличить ошибочный SQL от пустого каталогаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

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

    // Helpers preserve diagnostics; the SQL operation itself is visible below.
    function compatCall173($db,callable $action): array {
        $call=c173Capture($action);
        $call['connection_code']=cubrid_errno($db);
        $message=cubrid_error($db);
        $call['connection_message_present']=is_string($message)&&$message!=='';
        return $call;
    }
    function selected173($db,string $sql): array {
        $call=compatCall173($db,static function()use($db,$sql){return cubrid_query($sql,$db);});
        if(!is_resource($call['value']) || $call['warning']){c173Failure('Выборка не выполнена',$call);}
        $request=$call['value'];
        try {return c173Rows($request);} finally {c173Release($request);}
    }
    function csvRow173($file,array $fields): void {
        if(fputcsv($file,$fields,';','"','')===false){throw new RuntimeException('Строка CSV не записана');}
    }
    function csvSave173(string $name,array $records): void {
        $file=fopen(__DIR__.'/'.$name,'wb');
        if($file===false){throw new RuntimeException('Файл CSV не открыт');}
        try {foreach($records as $record){csvRow173($file,$record);}}
        finally {fclose($file);}
    }
    function csvRead173(string $name): array {
        $file=fopen(__DIR__.'/'.$name,'rb');
        if($file===false){throw new RuntimeException('Созданный CSV не найден');}
        $records=[];
        try {
            while(($record=fgetcsv($file,0,';','"',''))!==false){
                $records[]=$record;
                if(count($records)>12){throw new RuntimeException('Учебная CSV-выгрузка должна быть ограничена');}
            }
        } finally {fclose($file);}
        return $records;
    }

    $name=c173CreateTable($db,$owned,'diagnostics',[
        ['id'=>1,'sku'=>'CB-01','title'=>'Кабель','category'=>'электрика','price_cents'=>12500,'qty'=>3],
        ['id'=>2,'sku'=>'CL-02','title'=>'Хомут','category'=>'крепёж','price_cents'=>250,'qty'=>0]
    ]);$table=c173Qualified($name);
    $bad=compatCall173($db,static function()use($db,$table){return cubrid_query('SELECT absent_column FROM '.$table,$db);});
    if($bad['value']!==false){throw new RuntimeException('Контрольный SQL неожиданно выполнился');}
    // Diagnostics are already saved. Explicitly end this unit before recovery.
    c173Rollback($db);
    $call=compatCall173($db,static function()use($db,$table){return cubrid_query('SELECT id,title,qty FROM '.$table.' WHERE qty>0 ORDER BY id',$db);});
    if(!is_resource($call['value']) || $call['warning']){c173Failure('Каталог не прочитан',$call);}
    $recoveredCode=$call['connection_code'];$recoveredMessage=$call['connection_message_present'];
    $request=$call['value'];
    try {$catalog=c173Rows($request);} finally {c173Release($request);}
    $empty=selected173($db,'SELECT id,title FROM '.$table.' WHERE id=999');
    $diagnosticRows=[['stage','outcome','code','message_present'],['invalid_column','sql_error',$bad['connection_code'],$bad['connection_message_present']?'yes':'no'],
        ['recovered_catalog','1 row',$recoveredCode,$recoveredMessage?'yes':'no'],['empty_selection','0 rows',0,'no']];
    csvSave173('query-diagnostics.csv',$diagnosticRows);
    $parsed=csvRead173('query-diagnostics.csv');
    $rows=[['Ошибочный столбец','SQL отклонён; причина сохранена'],['Сохранённый код',$bad['connection_code']],['Исправленный каталог',$catalog[0]['id'].' | '.$catalog[0]['title'].' | '.$catalog[0]['qty']],
        ['Новая диагностика',$recoveredCode],['Поиск id=999',count($empty)],['CSV','query-diagnostics.csv']];
    $checks=['invalid_sql_false'=>$bad['value']===false,'error_saved'=>$bad['warning'] && $bad['connection_code']===-494 && $bad['code']===-494 && $bad['facility']===1 && $bad['connection_message_present'],
        'recovery_rows'=>$catalog===[['id'=>'1','title'=>'Кабель','qty'=>'3']],
        'recovery_diagnostics_reset'=>$recoveredCode===0 && $recoveredMessage===false,
        'empty_is_not_failure'=>$empty===[],
        'csv_exact'=>$parsed===[['stage','outcome','code','message_present'],['invalid_column','sql_error','-494','yes'],['recovered_catalog','1 row','0','no'],['empty_selection','0 rows','0','no']]];
    $observations=['failed_query'=>['code'=>$bad['connection_code'],'message_present'=>$bad['connection_message_present'],
        'warning'=>$bad['warning'],'global_code'=>$bad['code'],'facility'=>$bad['facility']],
        'catalog'=>$catalog,'empty'=>$empty,'after_recovery'=>['code'=>$recoveredCode,'message_present'=>$recoveredMessage],
        'csv_rows'=>$parsed];

} finally {

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

c173Report('cub173-query-diagnostics','Отличить ошибочный SQL от пустого каталога',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Ошибочный столбец — SQL отклонён; причина сохранена
Сохранённый код — -494
Исправленный каталог — 1 | Кабель | 3
Новая диагностика — 0
Поиск id=999 — 0
CSV — query-diagnostics.csv

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

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

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

Код ошибки и наличие сообщения — диагностические данные, а не самостоятельное разрешение повторять запись. Нельзя после любого false автоматически снова отправлять INSERT: первый запрос мог завершиться на сервере до потери связи. Отказ доступа тоже способен дать false. Полная программа использует заранее известный несуществующий столбец; при реальном отказе исходную причину анализируют в закрытом журнале.

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

Замените id=999 на id=2. Должно ли приложение сообщить об ошибке, если qty равно 0?

Разбор

Корректный SELECT вернёт Хомут, а остаток 0 — значение поля. Проверяйте результат вызова строгим сравнением, затем отдельно применяйте бизнес-правило наличия. Не используйте if (!$row["qty"]) как проверку соединения.

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

Для передачи на склад нужны артикул, название и остаток выбранной части каталога. База сразу исключает чужой диапазон и нулевой остаток. PHP читает очередную строку и записывает её в CSV; полезный результат — файл с Хомутом и Розеткой, а не огромный массив всех товаров.

Путь данных и полученный результат
  1. 5 товаров в базе → WHERE id=2…4 AND qty>0
  2. SELECT sku,title,qty → Хомут и Розетка в ORDER BY id
  3. fetch одной строки → fputcsv → следующая строка
  4. Закрыть результат → готовый CSV и новый COUNT(*)=5

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

$request = cubrid_unbuffered_query(
    'SELECT sku,title,qty FROM catalog WHERE id BETWEEN 2 AND 4 AND qty>0 ORDER BY id', $db
);
if ($request === false) { throw new RuntimeException('Выгрузка не начата'); }
// Далее fetch по одной строке → fputcsv; затем close_request.
// Не вызываем num_rows или data_seek у такого результата.

В SELECT перечислены только три нужные колонки. BETWEEN ограничивает диапазон, qty>0 убирает отсутствующие товары, ORDER BY задаёт воспроизводимый порядок. unbuffered_query возвращает ресурс чтения; это не массив готовых строк. Полный цикл различает EOF и ошибку, проверяет запись в файл и освобождает запрос в finally.

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

НаблюдениеРезультат
Условие SQLid от 2 до 4; qty > 0
Строка 1CL-02 | Хомут | 4
Строка 2SO-03 | Розетка | 2
Выгружено строк2
Всего товаров в базе5
CSVselected-catalog.csv
Самостоятельный проект, исходник и архив
Выгрузить выбранный участок каталога по одной строкеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

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

    // Helpers preserve diagnostics; the SQL operation itself is visible below.
    function compatCall173($db,callable $action): array {
        $call=c173Capture($action);
        $call['connection_code']=cubrid_errno($db);
        $message=cubrid_error($db);
        $call['connection_message_present']=is_string($message)&&$message!=='';
        return $call;
    }
    function selected173($db,string $sql): array {
        $call=compatCall173($db,static function()use($db,$sql){return cubrid_query($sql,$db);});
        if(!is_resource($call['value']) || $call['warning']){c173Failure('Выборка не выполнена',$call);}
        $request=$call['value'];
        try {return c173Rows($request);} finally {c173Release($request);}
    }
    function csvRow173($file,array $fields): void {
        if(fputcsv($file,$fields,';','"','')===false){throw new RuntimeException('Строка CSV не записана');}
    }
    function csvSave173(string $name,array $records): void {
        $file=fopen(__DIR__.'/'.$name,'wb');
        if($file===false){throw new RuntimeException('Файл CSV не открыт');}
        try {foreach($records as $record){csvRow173($file,$record);}}
        finally {fclose($file);}
    }
    function csvRead173(string $name): array {
        $file=fopen(__DIR__.'/'.$name,'rb');
        if($file===false){throw new RuntimeException('Созданный CSV не найден');}
        $records=[];
        try {
            while(($record=fgetcsv($file,0,';','"',''))!==false){
                $records[]=$record;
                if(count($records)>12){throw new RuntimeException('Учебная CSV-выгрузка должна быть ограничена');}
            }
        } finally {fclose($file);}
        return $records;
    }

    $name=c173CreateTable($db,$owned,'csv_export',[
        ['id'=>1,'sku'=>'CB-01','title'=>'Кабель','category'=>'электрика','price_cents'=>12500,'qty'=>3],
        ['id'=>2,'sku'=>'CL-02','title'=>'Хомут','category'=>'крепёж','price_cents'=>250,'qty'=>4],
        ['id'=>3,'sku'=>'SO-03','title'=>'Розетка','category'=>'электрика','price_cents'=>5000,'qty'=>2],
        ['id'=>4,'sku'=>'BX-04','title'=>'Коробка','category'=>'электрика','price_cents'=>1200,'qty'=>0],
        ['id'=>5,'sku'=>'TP-05','title'=>'Лента','category'=>'электрика','price_cents'=>800,'qty'=>1]
    ]);$table=c173Qualified($name);
    $request=null;$file=null;$count=0;$preview=[];
    try {
        $sql='SELECT sku,title,qty FROM '.$table.' WHERE id BETWEEN 2 AND 4 AND qty>0 ORDER BY id';
        $call=compatCall173($db,static function()use($db,$sql){return cubrid_unbuffered_query($sql,$db);});
        if(!is_resource($call['value']) || $call['warning']){c173Failure('Выгрузка не начата',$call);}
        $request=$call['value'];
        $file=fopen(__DIR__.'/selected-catalog.csv','wb');
        if($file===false){throw new RuntimeException('CSV не открыт');}
        csvRow173($file,['sku','title','qty']);
        while(true){
            $read=c173Capture(static function()use($request){return cubrid_fetch_assoc($request);});
            if($read['warning'] || $read['code']!==0 || $read['value']===null){c173Failure('Строка выгрузки не прочитана',$read);}
            if($read['value']===false){break;}
            $row=$read['value'];
            csvRow173($file,[$row['sku'],$row['title'],$row['qty']]);
            $count++;
            // Bounded screen preview; the export is not accumulated in an array.
            if(count($preview)<3){$preview[]=$row;}
        }
    } finally {
        if(is_resource($file)){fclose($file);}
        c173Release($request);
    }
    // Read back this deliberately tiny fixture only after closing the SQL result.
    $parsed=csvRead173('selected-catalog.csv');
    $stillThere=c173Read($db,'SELECT COUNT(*) AS total FROM '.$table);
    $rows=[['Условие SQL','id от 2 до 4; qty > 0'],['Строка 1',$preview[0]['sku'].' | '.$preview[0]['title'].' | '.$preview[0]['qty']],
        ['Строка 2',$preview[1]['sku'].' | '.$preview[1]['title'].' | '.$preview[1]['qty']],['Выгружено строк',$count],['Всего товаров в базе',(int)$stillThere[0]['total']],['CSV','selected-catalog.csv']];
    $checks=['selected_count'=>$count===2,'bounded_preview'=>count($preview)===2,
        'selected_values'=>$preview===[['sku'=>'CL-02','title'=>'Хомут','qty'=>'4'],['sku'=>'SO-03','title'=>'Розетка','qty'=>'2']],
        'csv_exact'=>$parsed===[['sku','title','qty'],['CL-02','Хомут','4'],['SO-03','Розетка','2']],
        'connection_reused_after_release'=>$stillThere===[['total'=>'5']]];
    $observations=['selected_columns'=>['sku','title','qty'],'preview'=>$preview,'written_rows'=>$count,
        'database_rows'=>(int)$stillThere[0]['total'],'csv_rows'=>$parsed,'memory_benchmark_performed'=>false];

} finally {

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

c173Report('cub173-csv-export','Выгрузить выбранный участок каталога по одной строке',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Условие SQL — id от 2 до 4; qty > 0
Строка 1 — CL-02 | Хомут | 4
Строка 2 — SO-03 | Розетка | 2
Выгружено строк — 2
Всего товаров в базе — 5
CSV — selected-catalog.csv

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

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

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

Официальный контракт запрещает num_rows и data_seek для такого набора. Не накапливайте каждую прочитанную строку в $allRows: это отменит смысл постепенной обработки. Здесь сохранён только ограниченный предпросмотр до трёх строк; обратное чтение CSV относится к крошечной проверочной выгрузке. Мы не измеряем память или скорость драйвера и не обещаем, что клиентский/серверный буфер равен размеру одной строки. CSV с произвольным пользовательским текстом отдельно требует политики для формул табличных редакторов.

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

Нужно выгрузить только электрику, но не менять порядок и состав колонок. Где добавить условие?

Разбор

Добавьте category в WHERE на стороне SQL; переменное значение передавайте через подготовленный запрос, если переключаете API. Нельзя сначала получить все товары и отфильтровать массив после выгрузки. Счётчик $count остаётся количеством реально записанных строк, а не всех товаров базы.

В существующем приложении SQL собирается строкой, а название содержит апостроф: «Кабель O'Reilly». Сначала увидим, как подготовить именно строковый литерал для этого кода. Затем тот же поиск выполним параметром и сравним конкретные строки. Отдельный поиск «Скидка 10%» покажет, что знак процента при равенстве остаётся обычным символом.

Путь данных и полученный результат
  1. Ввод «Кабель O'Reilly» → escaped «Кабель O''Reilly»
  2. WHERE title='...' → только id=1, исходное название
  3. WHERE title=? + исходный ввод → та же строка
  4. Ключ сортировки title → заранее разрешённый SQL title, id

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

$escaped = cubrid_real_escape_string("Кабель O'Reilly", $db);
if ($escaped === false) { throw new RuntimeException('Экранирование не выполнено'); }
$request = cubrid_query("SELECT id,title FROM catalog WHERE title='" . $escaped . "'", $db);
if ($request === false) { throw new RuntimeException('Поиск не выполнен'); }

Внешние одинарные SQL-кавычки задаёт сам запрос. Функция возвращает внутренний текст литерала: O''Reilly. Она не добавляет WHERE, кавычки вокруг всей строки или ограничения доступа. У prepare/bind значение идёт отдельно от текста SQL; перед bind его повторно экранировать не нужно. Имя столбца для сортировки выбирается из фиксированной карты, а не этой функцией.

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

НаблюдениеРезультат
Искомое названиеКабель O'Reilly
Значение внутри SQL-кавычекКабель O''Reilly
Точный результат1 | Кабель O'Reilly
Параметр дал те же данныеда
Процент при сравнении =2 | Скидка 10%
CSVtext-search.csv
Самостоятельный проект, исходник и архив
Найти точное название с апострофом и сравнить с параметромСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

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

    // Helpers preserve diagnostics; the SQL operation itself is visible below.
    function compatCall173($db,callable $action): array {
        $call=c173Capture($action);
        $call['connection_code']=cubrid_errno($db);
        $message=cubrid_error($db);
        $call['connection_message_present']=is_string($message)&&$message!=='';
        return $call;
    }
    function selected173($db,string $sql): array {
        $call=compatCall173($db,static function()use($db,$sql){return cubrid_query($sql,$db);});
        if(!is_resource($call['value']) || $call['warning']){c173Failure('Выборка не выполнена',$call);}
        $request=$call['value'];
        try {return c173Rows($request);} finally {c173Release($request);}
    }
    function csvRow173($file,array $fields): void {
        if(fputcsv($file,$fields,';','"','')===false){throw new RuntimeException('Строка CSV не записана');}
    }
    function csvSave173(string $name,array $records): void {
        $file=fopen(__DIR__.'/'.$name,'wb');
        if($file===false){throw new RuntimeException('Файл CSV не открыт');}
        try {foreach($records as $record){csvRow173($file,$record);}}
        finally {fclose($file);}
    }
    function csvRead173(string $name): array {
        $file=fopen(__DIR__.'/'.$name,'rb');
        if($file===false){throw new RuntimeException('Созданный CSV не найден');}
        $records=[];
        try {
            while(($record=fgetcsv($file,0,';','"',''))!==false){
                $records[]=$record;
                if(count($records)>12){throw new RuntimeException('Учебная CSV-выгрузка должна быть ограничена');}
            }
        } finally {fclose($file);}
        return $records;
    }

    $name=c173CreateTable($db,$owned,'text_search',[
        ['id'=>1,'sku'=>'CB-01','title'=>"Кабель O'Reilly",'category'=>'электрика','price_cents'=>12500,'qty'=>3],
        ['id'=>2,'sku'=>'DS-02','title'=>'Скидка 10%','category'=>'акции','price_cents'=>250,'qty'=>1],
        ['id'=>3,'sku'=>'CB-03','title'=>'Кабель обычный','category'=>'электрика','price_cents'=>5000,'qty'=>2]
    ]);$table=c173Qualified($name);
    $search="Кабель O'Reilly";
    $escaped=cubrid_real_escape_string($search,$db);
    if($escaped===false){throw new RuntimeException('Текст SQL не подготовлен');}
    // Only this literal is escaped. The table was generated and validated by lab.php.
    $literal=selected173($db,"SELECT id,title FROM ".$table." WHERE title='".$escaped."' ORDER BY id");
    $bound=c173Read($db,'SELECT id,title FROM '.$table.' WHERE title=? ORDER BY id',[$search]);
    $percent=cubrid_real_escape_string('Скидка 10%',$db);
    if($percent===false){throw new RuntimeException('Второй текст SQL не подготовлен');}
    $exactPercent=selected173($db,"SELECT id,title FROM ".$table." WHERE title='".$percent."'");
    $sortKey='title';$allowedSort=['title'=>'title, id','id'=>'id'];
    if(!array_key_exists($sortKey,$allowedSort)){throw new InvalidArgumentException('Неизвестный порядок');}
    $sorted=selected173($db,'SELECT id,title FROM '.$table.' WHERE id IN (1,3) ORDER BY '.$allowedSort[$sortKey]);
    if(count($literal)!==1 || count($bound)!==1 || count($exactPercent)!==1){throw new RuntimeException('Точное совпадение не найдено');}
    $records=[['method','id','title'],['escaped',$literal[0]['id'],$literal[0]['title']],
        ['prepared',$bound[0]['id'],$bound[0]['title']],['percent_literal',$exactPercent[0]['id'],$exactPercent[0]['title']]];
    csvSave173('text-search.csv',$records);$parsed=csvRead173('text-search.csv');
    $rows=[['Искомое название',$search],['Значение внутри SQL-кавычек',$escaped],
        ['Точный результат',$literal[0]['id'].' | '.$literal[0]['title']],['Параметр дал те же данные',$literal===$bound],
        ['Процент при сравнении =',$exactPercent[0]['id'].' | '.$exactPercent[0]['title']],['CSV','text-search.csv']];
    $checks=['apostrophe_doubled'=>$escaped==="Кабель O''Reilly",'utf8_roundtrip'=>$literal===[['id'=>'1','title'=>"Кабель O'Reilly"]],
        'prepared_same'=>$bound===$literal,'percent_is_literal'=>$exactPercent===[['id'=>'2','title'=>'Скидка 10%']],
        'allowed_order_only'=>count($sorted)===2 && $allowedSort[$sortKey]==='title, id',
        'csv_exact'=>$parsed===[['method','id','title'],['escaped','1',"Кабель O'Reilly"],['prepared','1',"Кабель O'Reilly"],['percent_literal','2','Скидка 10%']]];
    $observations=['input'=>$search,'escaped_literal'=>$escaped,'literal_rows'=>$literal,'parameter_rows'=>$bound,
        'percent_rows'=>$exactPercent,'sort_key'=>$sortKey,'allowed_order_result'=>$sorted,'csv_rows'=>$parsed];

} finally {

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

c173Report('cub173-legacy-text-search','Найти точное название с апострофом и сравнить с параметром',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Искомое название — Кабель O'Reilly
Значение внутри SQL-кавычек — Кабель O''Reilly
Точный результат — 1 | Кабель O'Reilly
Параметр дал те же данные — да
Процент при сравнении = — 2 | Скидка 10%
CSV — text-search.csv

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

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

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

Не подставляйте escaped-текст вместо имени таблицы, колонки, ASC/DESC или фрагмента SQL. HTML и URL тоже имеют другие правила кодирования. В LIKE символы % и _ задают шаблон; экранирование SQL-литерала не превращает их автоматически в буквальный поиск. Подключение, кодировка и настройки сервера относятся к конкретному драйверу; addslashes и mysql_real_escape_string не являются заменой функции CUBRID. Новый переменный поиск удобнее строить параметрами.

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

Добавьте пользовательский выбор сортировки «сначала новые». Что нужно отправлять в SQL?

Разбор

Добавьте разрешённую пару newer → id DESC в $allowedSort и подставляйте только её значение. Название из запроса пользователя не превращается в SQL через real_escape_string. Текст поиска остаётся значением параметра; порядок — выбором из заранее написанных фрагментов.

Выбрать следующий шаг и не потерять смысл результата

Затем перейдите к жизненному циклу соединения и запроса: постоянное соединение не хранит готовый каталог вместо нового SELECT, а несколько результатов требуют другого интерфейса. Для сложной формы со значениями пользователя базовым инструментом остаются подготовленные запросы.

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

Функция под конкретную задачу

ФункцияЧто она даёт приложению
cubrid_query()Отправить один готовый SQL-запрос и различить набор строк, успех команды и отказ.
cubrid_unbuffered_query()Начать последовательное получение результата и постепенно записывать нужные строки.
cubrid_real_escape_string()Подготовить содержимое строкового SQL-литерала в существующем коде CUBRID.
cubrid_errno()Сохранить числовой код последнего отказа выбранного соединения.
cubrid_error()Получить диагностический текст соединения, отделив его от сообщения посетителю.
cubrid_insert_id()Сразу после INSERT получить сгенерированный ключ своей новой записи.

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

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

cubrid_queryОтправить один готовый SQL-запрос и различить набор строк, успех команды и отказ.

PECL CUBRID >= 8.3.1; профиль PHP 7.4.33/native 11.2.0.0002, CUBRID 11.3.5. · cubrid

cubrid_query(string $query, resource $conn_identifier = ?): resource

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

ПараметрЧто передать
query: stringОдин готовый SQL-запрос. Значения литералов должны быть подготовлены; для переменных значений удобнее prepare/bind.
conn_identifier: resource, необязательныйСоединение CUBRID; без аргумента используется последнее открытое cubrid_connect.

Результат: PHP Manual различает resource для запросов с набором (SELECT, SHOW, DESCRIBE, EXPLAIN и аналогичные), true для INSERT/UPDATE/DELETE/DROP и других команд, false при отказе. Сводная сигнатура resource не перечисляет все эти варианты. Пустой SELECT всё ещё даёт успешный ресурс.

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

Необязательное соединение в совместимом API обычно выбирает последний открытый cubrid_connect. В наших фрагментах $db передаётся явно: так ошибка второго соединения или другой открытый запрос не меняет предполагаемый адресат. «Необязательный» не означает, что NULL заменяет ресурс; ориентируйтесь на точную сигнатуру своей версии.

Одна функция, три разные ветки
  1. SELECT id,title → ресурс → fetch → строка либо EOF
  2. INSERT/UPDATE → true → проверка affected_rows/insert_id
  3. Некорректный SQL или отказ доступа → false → немедленная диагностика

В закреплённом native-драйвере 11.2.0.0002 возврат ресурса реализован для внутреннего типа CUBRID_STMT_SELECT. Документированный перечень SHOW/DESCRIBE/EXPLAIN зависит от фактической классификации сервером; наш исполняемый пример подтверждает SELECT и DML, не объявляет все разновидности команд проверенными. Несколько SQL-инструкций через этот интерфейс не поддерживаются: это не API пакетного выполнения.

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

$request = cubrid_query('SELECT id,title,qty FROM catalog WHERE id=1', $db);
if ($request === false) {
    $code = cubrid_errno($db);
    $message = cubrid_error($db);
    throw new RuntimeException('Не удалось открыть карточку');
}
try {
    $item = cubrid_fetch_assoc($request);
    // В учебном каталоге: id=1, title=Кабель, qty=3.
} finally { cubrid_close_request($request); }

Самостоятельный проект диагностики показывает конкретную карточку после исправления запроса и отдельно успешный пустой результат. Для SELECT число строк относится к набору результата, а не к количеству изменённых записей. Сначала проверяйте query, затем чтение и его диагностику; конец набора не надо выдавать за сбой соединения.

Применение 2: создать заявку, которую затем откроет человек

$ok = cubrid_query("INSERT INTO requests (title,qty) VALUES ('Кабель',3)", $db);
if ($ok !== true) { throw new RuntimeException('Заявка не создана'); }
$id = cubrid_insert_id($db); // Сохранить до следующего SQL.
if ($id === false || $id === 0) { throw new RuntimeException('Нет ожидаемого ключа'); }
if (cubrid_commit($db) !== true) { throw new RuntimeException('Запись не подтверждена'); }

Проект собственной заявки перечитывает именно ключ 701. True сообщает об успешном выполнении команды, но не заменяет её бизнес-проверку или commit: UPDATE с подходящим условием может затронуть ноль строк. Число изменений сохраняют через cubrid_affected_rows() до следующего запроса.

query не проверяет права пользователя вашего сайта и не экранирует подставленные данные автоматически. У серверного пользователя может не быть SELECT или UPDATE — это тоже false. Имена таблиц/колонок задавайте в коде или выбирайте из разрешённой карты; значения передавайте через параметры. Для нескольких результатов переходите к отдельному сценарию жизненного цикла запросов, а не склеивайте команды через точку с запятой.

cubrid_unbuffered_queryНачать последовательное получение результата и постепенно записывать нужные строки.

PECL CUBRID >= 8.3.0; профиль PHP 7.4.33/native 11.2.0.0002, CUBRID 11.3.5. · cubrid

cubrid_unbuffered_query(string $query, resource $conn_identifier = ?): resource

Начать последовательное получение результата и постепенно записывать нужные строки.

ПараметрЧто передать
query: stringГотовый SQL, в котором сразу выбраны нужные колонки, условия и порядок.
conn_identifier: resource, необязательныйЯвное соединение; по умолчанию последнее соединение cubrid_connect.

Результат: По PHP Manual: ресурс запроса для SELECT/SHOW/DESCRIBE/EXPLAIN, true для других успешных команд (например UPDATE/DELETE/DROP), false при отказе. Ресурс ещё требуется прочитать. Исполняемый проект этой главы использует SELECT.

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

Необязательное соединение в совместимом API обычно выбирает последний открытый cubrid_connect. В наших фрагментах $db передаётся явно: так ошибка второго соединения или другой открытый запрос не меняет предполагаемый адресат. «Необязательный» не означает, что NULL заменяет ресурс; ориентируйтесь на точную сигнатуру своей версии.

Официальное назначение — начать работу, пока результат формируется, без предварительного ожидания всего набора. В pinned-драйвере вызывается cci_execute с CCI_EXEC_ASYNC. Это техническая реализация, не обещание фоновой задачи в вашем PHP-коде или фиксированного расхода памяти. Стоимость сортировки, SQL-плана, сервера и сетевых буферов остаётся; сам fetch и запись файла тоже могут завершиться ошибкой.

Постепенная выдача складу
  1. WHERE id=2…4 AND qty>0 → отобранные строки
  2. fetch → CL-02,Хомут,4 → сразу CSV
  3. fetch → SO-03,Розетка,2 → сразу CSV
  4. EOF без ошибки → close_request → готовый файл

Применение 1: файл для импорта в другую программу

$result = cubrid_unbuffered_query('SELECT sku,title,qty FROM catalog WHERE qty>0 ORDER BY id', $db);
if ($result === false) { throw new RuntimeException('SELECT не начат'); }
$file = fopen('stock.csv', 'wb');
if ($file === false) { cubrid_close_request($result); throw new RuntimeException('Нет файла'); }
try {
    fputcsv($file, ['sku','title','qty'], ';', '"', '');
    while (true) {
        $row = cubrid_fetch_assoc($result);
        $error = cubrid_error_code();
        if ($error !== 0 || $row === null) { throw new RuntimeException('Чтение прервано'); }
        if ($row === false) { break; }
        if (fputcsv($file, [$row['sku'],$row['title'],$row['qty']], ';', '"', '') === false) {
            throw new RuntimeException('Запись прервана');
        }
    }
} finally { fclose($file); cubrid_close_request($result); }

В коротком фрагменте нужно также проверять запись заголовка; полный проект CSV делает это общим csvRow173. Он не добавляет все строки в массив и не сортирует их после чтения. Существующий файл открыт с wb и перезаписывается: это назначенное имя результата внутри отдельной папки примера, не произвольный путь посетителя.

Применение 2: последовательно посчитать объём отобранной выдачи

$result = cubrid_unbuffered_query("SELECT qty FROM catalog WHERE category='крепёж' ORDER BY id", $db);
if ($result === false) { throw new RuntimeException('Отбор не выполнен'); }
$written = 0; $units = 0;
try {
    while (true) {
        $row = cubrid_fetch_assoc($result);
        $error = cubrid_error_code();
        if ($error !== 0 || $row === null) { throw new RuntimeException('Чтение прервано'); }
        if ($row === false) { break; }
        $written++; $units += (int)$row['qty'];
    }
} finally { cubrid_close_request($result); }
// Это количество действительно прочитанных строк и единиц выбранной выдачи.

Если нужны только итоговые числа, лучше сразу попросить SQL COUNT/SUM, а не читать все строки ради суммы. Счётчик в примере полезен, когда строки уже требуется передавать наружу. Для unbuffered-набора PHP Manual прямо запрещает cubrid_num_rows() и cubrid_data_seek(). Не делайте повторный обход: сохраните нужный результат или выполните новый ограниченный запрос.

Обсуждение остановки выборки примерно на 100000 строках напоминает, что лимит памяти не лечится одним названием API. Там MySQL; его внутреннюю буферизацию не переносим на CUBRID. Здесь проверяются данные и последовательная запись. Заявление о точном выигрыше памяти или ускорении потребовало бы отдельного измерения.

cubrid_real_escape_stringПодготовить содержимое строкового SQL-литерала в существующем коде CUBRID.

PECL CUBRID >= 8.3.0; профиль PHP 7.4.33/native 11.2.0.0002, CUBRID 11.3.5. · cubrid

cubrid_real_escape_string(string $unescaped_string, resource $conn_identifier = ?): string

Подготовить содержимое строкового SQL-литерала в существующем коде CUBRID.

ПараметрЧто передать
unescaped_string: stringИсходный текст значения, ещё не подвергнутый SQL-экранированию.
conn_identifier: resource, необязательныйСоединение, в контексте которого будет выполняться SQL.

Результат: Подготовленная строка без окружающих SQL-кавычек; false при отказе. Пустая строка остаётся допустимым значением, поэтому проверяйте === false.

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

Необязательное соединение в совместимом API обычно выбирает последний открытый cubrid_connect. В наших фрагментах $db передаётся явно: так ошибка второго соединения или другой открытый запрос не меняет предполагаемый адресат. «Необязательный» не означает, что NULL заменяет ресурс; ориентируйтесь на точную сигнатуру своей версии.

PHP Manual объясняет удвоение одинарной кавычки: O'Reilly становится O''Reilly внутри строкового SQL-литерала. Строки обычно заключают в одинарные кавычки. При ansi_quotes=no сервер может трактовать двойные кавычки как границы текста; documented default yes означает другой контекст. Мы не меняем этот параметр ради поиска и используем одинарные SQL-кавычки.

Подготовить одно значение, а не весь запрос
  1. Исходный текст: Кабель O'Reilly
  2. escape($text,$db): Кабель O''Reilly
  3. SQL добавляет внешние кавычки: title='Кабель O''Reilly'
  4. Из базы снова читается Кабель O'Reilly

Применение 1: точное название в существующем query-коде

$text = "Кабель O'Reilly";
$inside = cubrid_real_escape_string($text, $db);
if ($inside === false) { throw new RuntimeException('Значение не подготовлено'); }
$sql = "SELECT id,title FROM catalog WHERE title='" . $inside . "'";
$result = cubrid_query($sql, $db);
if ($result === false) { throw new RuntimeException('Название не найдено из-за ошибки SQL'); }

Полный проект поиска сравнивает полученную строку с вариантом prepare/bind. Обе ветки возвращают id=1 и исходное название. Это не значит, что всякая подстановка безопасна: функция обрабатывает конкретный строковый литерал, а не SQL-структуру. Не превращайте вход пользователя в имя таблицы или произвольное условие WHERE.

Применение 2: оставить процент буквальным символом

$text = 'Скидка 10%';
$inside = cubrid_real_escape_string($text, $db);
if ($inside === false) { throw new RuntimeException('Значение не подготовлено'); }
$result = cubrid_query("SELECT id,title FROM catalog WHERE title='" . $inside . "'", $db);
// Здесь =: процент — часть названия. LIKE был бы другой задачей.

При равенстве % не является шаблоном. При LIKE нужны отдельно выбранные правила поиска и escaping %/_; не обещайте, что real_escape_string делает это за вас. Та же функция не заменяет htmlspecialchars при HTML-выводе, urlencode при построении URL или валидацию бизнес-данных. Проверенный профиль использует UTF-8; смысл спецсимволов сверяется с серверными настройками CUBRID, а не с поведением addslashes или MySQL.

В вопросе о пользовательском ORDER BY смешаны значение и имя поля. Наш полный проект выбирает сортировку через фиксированную карту title → title, id; такую карту не строят из произвольных строк запроса. Для новых переменных значений используйте cubrid_prepare() и cubrid_bind() и передавайте исходный текст без предварительного SQL-экранирования.

cubrid_errnoСохранить числовой код последнего отказа выбранного соединения.

PECL CUBRID >= 8.3.1; профиль PHP 7.4.33/native 11.2.0.0002, CUBRID 11.3.5. · cubrid

cubrid_errno(resource $conn_identifier = ?): int

Сохранить числовой код последнего отказа выбранного соединения.

ПараметрЧто передать
conn_identifier: resource, необязательныйВыбранное соединение; без аргумента последнее открытое cubrid_connect.

Результат: Целое число: код последней ошибки API данного соединения, либо 0, если такой ошибки нет. Код не является числом строк и не должен интерпретироваться как HTTP-статус.

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

Необязательное соединение в совместимом API обычно выбирает последний открытый cubrid_connect. В наших фрагментах $db передаётся явно: так ошибка второго соединения или другой открытый запрос не меняет предполагаемый адресат. «Необязательный» не означает, что NULL заменяет ресурс; ориентируйтесь на точную сигнатуру своей версии.

Обычно код читают, когда действие вернуло false. В закреплённом исходнике функция берёт connect->recent_error.code; cubrid_error_code() читает глобальный recent_error модуля. Это разные области хранения. Нельзя после нескольких вызовов брать «какой-нибудь последний код» и приписывать его прежнему SQL. Сохраните результат, код и текст рядом с отказавшим действием.

Диагностика принадлежит определённому действию
  1. query с absent_column → false
  2. errno($db) → сохранить числовой код
  3. error($db) → сохранить текст отдельно
  4. rollback/новый SQL могут заменить текущее состояние; сохранённые переменные остаются

Применение 1: неправильное имя поля в отчёте

$result = cubrid_query('SELECT absent_column FROM catalog', $db);
if ($result === false) {
    $saved = ['code' => cubrid_errno($db), 'message' => cubrid_error($db)];
    // Теперь можно завершить транзакцию и исправлять запрос.
    // Не показывайте raw message посетителю.
}

Проект диагностики сохраняет код, предупреждение и признак сообщения. Затем исправленный SELECT возвращает Кабель, а новая диагностика очищена. Сохранённый код первого действия остаётся доступным для отчёта. Отсутствующая строка id=999 при успешном SELECT не становится ошибкой API.

Применение 2: отделить отказ сохранения от нуля изменённых строк

$ok = cubrid_query('UPDATE catalog SET qty=0 WHERE id=999', $db);
if ($ok === false) {
    $savedCode = cubrid_errno($db);
    $savedMessage = cubrid_error($db);
    throw new RuntimeException('Не удалось выполнить изменение');
}
$changed = cubrid_affected_rows($db); // Сохранить сразу после UPDATE.
if ($changed === false) { throw new RuntimeException('Не получен счётчик'); }
// changed=0 означает: условие не нашло строку; это иной результат, чем false у query.

Развить этот сценарий помогает условное изменение остатка. При неожиданном отказе нужна политика отката/сообщения; автоматический повтор любой записи опасен. Число 0 у errno само по себе не подтверждает бизнес-успех: сначала проверяют тип и результат самого действия, предупреждение и ожидаемые строки.

Указанный ресурс должен быть действующим соединением. Сводная сигнатура int описывает нормальный вызов, а некорректные аргументы/закрытый ресурс могут приводить к предупреждению или ошибке проверки аргументов; не используйте диагностическую функцию как способ оживить уже закрытое соединение. У сетевого отказа первоначальный результат может быть неопределённым с точки зрения бизнес-операции — сопоставляйте его с ключом заявки и идемпотентностью.

cubrid_errorПолучить диагностический текст соединения, отделив его от сообщения посетителю.

PECL CUBRID >= 8.3.1; профиль PHP 7.4.33/native 11.2.0.0002, CUBRID 11.3.5. · cubrid

cubrid_error(resource $connection = ?): string

Получить диагностический текст соединения, отделив его от сообщения посетителю.

ПараметрЧто передать
connection: resource, необязательныйСоединение, чью последнюю диагностическую запись нужно прочитать. В pinned-драйвере отсутствие аргумента выбирает последнее соединение.

Результат: Текст сообщения об ошибке API строкой. На исправном соединении без текущей ошибки проверенный драйвер отдаёт пустую строку. Это сообщение, а не результат SELECT; непустой текст не заменяет проверку исходного return.

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

PHP Manual показывает один необязательный аргумент и чтение сообщения после отказа. В native 11.2.0.0002 читается connect->recent_error.msg. Для глобальной диагностики существует cubrid_error_msg(); не подменяйте область соединения случайным глобальным вызовом после очистки. Даже одинаковый текст сообщения — деталь версии, языка и конкретного SQL; не разбирайте его regexp как устойчивый числовой код.

Два разных адресата ошибки
  1. Сбой query → сохранённые code и message
  2. Закрытый журнал разработчика → контекст операции и безопасно сохранённая причина
  3. Посетитель → понятное сообщение без SQL/путей/секретов
  4. Публичный учебный CSV → только исход и признак диагностического текста

Применение 1: журнал неудачного открытия каталога

$request = cubrid_query('SELECT absent_column FROM catalog', $db);
if ($request === false) {
    $savedCode = cubrid_errno($db);
    $savedMessage = cubrid_error($db);
    // Решение о содержимом закрытого журнала принимает приложение.
    // В ответ пользователю: «Каталог временно недоступен» + номер обращения.
}

Полный диагностический проект намеренно не сохраняет raw message в общедоступные result.json/CSV. Строка может раскрыть структуру SQL и внутренние имена. Для реального закрытого журнала минимизируйте входные данные и реквизиты, задайте доступ и срок хранения; это отдельное место от HTML-страницы с результатом.

Применение 2: объяснить отказ создания заявки, не печатая SQL

$ok = cubrid_query("INSERT INTO requests (title,qty) VALUES ('Кабель',3)", $db);
if ($ok === false) {
    $failure = ['code'=>cubrid_errno($db), 'message'=>cubrid_error($db)];
    if (cubrid_rollback($db) !== true) { throw new RuntimeException('Не подтверждён откат'); }
    // failure сохранён ДО rollback; теперь можно выбрать дальнейшее действие.
    throw new RuntimeException('Заявка не сохранена; требуется проверка');
}
$id = cubrid_insert_id($db);

Успешную сторону показывает создание собственной заявки. Не выполняйте die(cubrid_error()) в публичном обработчике и не добавляйте сообщение к уже начатому бинарному/CSV-ответу. После исправленного запроса текущий текст может стать пустым; для исходной причины нужен заранее сохранённый снимок. Пустой текст не доказывает, что посетитель получил ответ или запись была подтверждена commit.

cubrid_insert_idСразу после INSERT получить сгенерированный ключ своей новой записи.

PECL CUBRID >= 8.3.0; профиль PHP 7.4.33/native 11.2.0.0002, CUBRID 11.3.5. · cubrid

cubrid_insert_id(resource $conn_identifier = ?): string

Сразу после INSERT получить сгенерированный ключ своей новой записи.

ПараметрЧто передать
conn_identifier: resource, необязательныйСоединение своей предыдущей INSERT-операции, полученное cubrid_connect. Передавайте явно и читайте ID до следующего SQL.

Результат: Сгенерированный AUTO_INCREMENT ID строкой; 0, когда предыдущий запрос не создал новые строки; false при отказе. Строка может содержать большое целое: не переводите её в int без проверки диапазона. Сводная string-сигнатура не перечисляет все случаи возврата.

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

Необязательное соединение в совместимом API обычно выбирает последний открытый cubrid_connect. В наших фрагментах $db передаётся явно: так ошибка второго соединения или другой открытый запрос не меняет предполагаемый адресат. «Необязательный» не означает, что NULL заменяет ресурс; ориентируйтесь на точную сигнатуру своей версии.

CUBRID допускает несколько AUTO_INCREMENT-колонок в одной таблице. PHP Manual прямо запрещает применять эту функцию для такого случая, даже если некоторое значение возвращается. Наши заявки содержат единственную такую колонку request_id. В PECL 8.4.0 возврат изменён с массива на строку, а первый аргумент class_name удалён; старая форма вызова не соответствует современной сигнатуре. Наличие символа отмечено с PECL CUBRID 8.3.0.

Ключ принадлежит завершённой INSERT-операции
  1. requests.request_id AUTO_INCREMENT(701,1)
  2. INSERT Кабель,3 → запрос успешен
  3. Сразу insert_id($db) → строка "701" в своей переменной
  4. commit → SELECT WHERE request_id=? → карточка701

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

$ok = cubrid_query("INSERT INTO requests (title,qty) VALUES ('Кабель',3)", $db);
if ($ok !== true) { throw new RuntimeException('INSERT не завершён'); }
$createdId = cubrid_insert_id($db);
if (!is_string($createdId) || $createdId === '') { throw new RuntimeException('Нет ключа'); }
if (cubrid_commit($db) !== true) { throw new RuntimeException('Нет подтверждения записи'); }
// Далее параметризованный SELECT по createdId и проверка доступа к карточке.

Полный проект создаёт 701, затем 702, но CSV первой заявки остаётся привязанным к 701. Ключ хранится сразу, до следующего SELECT. В pinned-реализации SELECT/UPDATE/DELETE дают int 0 при последующем insert_id; DDL и другие классы запросов имеют отдельные ветки false/диагностики. Не воспринимайте ID как вечное свойство соединения.

Применение 2: составить ведомость нескольких созданных заявок

$created = [];
foreach (['Кабель','Хомут'] as $title) {
    $request = cubrid_prepare($db, 'INSERT INTO requests (title,qty) VALUES (?,1)');
    if ($request === false) { throw new RuntimeException('Не подготовлена заявка'); }
    try {
        if (cubrid_bind($request,1,$title,'string') !== true || cubrid_execute($request) !== true) {
            throw new RuntimeException('Не сохранена заявка');
        }
        $id = cubrid_insert_id($db); // Один ID сразу после каждой успешной вставки.
        if (!is_string($id)) { throw new RuntimeException('Нет ID заявки'); }
        $created[] = ['id'=>$id,'title'=>$title];
    } finally { cubrid_close_request($request); }
}
if (cubrid_commit($db) !== true) { throw new RuntimeException('Ведомость не подтверждена'); }

Это контекстный фрагмент внутри общей транзакционной границы; при исключении внешний обработчик обязан сделать rollback, как объясняет глава транзакций. Полученные до отката номера не превращают отменённые записи в существующие и не гарантируют отсутствие пропусков. Не выводите весь диапазон новых ключей из одного ID после многострочной вставки.

В вопросе о получении ID после INSERT важна связь идентификатора со своей записью. Чужой MySQL-код не служит спецификацией CUBRID. Здесь используется его native API; MAX(id) выбирает максимум видимых строк, а поиск по названию не уникален. Сам ключ также не подтверждает право посетителя открыть запись: проверка владельца остаётся обязанностью SQL и приложения.

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

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

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

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

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