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

Дерево руководства
Глава 94 · После подключения, параметров и транзакций CUBRID
CUBRID: сохранить вложение, текстовую инструкцию и файл поставщика
Двоичный файл измерений, инструкция UTF-8, исходный CSV поставщика и три состояния вложения с настоящими скачиваемыми результатами.
От карточки документа к сохранённому содержимому
У товара есть название и количество, но к нему могут понадобиться исходное измерение, инструкция или документ поставщика. Здесь будут сохранены такие вложения, а затем получены настоящие файлы для скачивания. Отдельный пример покажет оператору разницу между отсутствующим документом и приложенным пустым файлом.
Эти уроки продолжают подключение и первые запросы и подготовленные запросы с параметрами. BLOB хранит двоичные данные, например содержимое файла измерений; CLOB предназначен для большого текста. Строка таблицы связывает такое содержимое с товаром, инструкцией или поставкой. Здесь из базы получаются настоящие файлы, которые можно скачать и сравнить.
Дескриптор LOB — средство доступа к данным, а не их строковое содержимое. Создать LOB, записать в него данные и привязать его к строке SQL — отдельные действия. В коротких фрагментах соединение $db и запрос $request уже подготовлены; полный проект показывает весь путь от создания таблицы до результата и очистки.
Проверенный профиль расширения: PHP 7.4.33, native cubrid 11.2.0.0002, CCI 11.2.0, сервер CUBRID 11.3.5.1274. Это отдельная среда совместимости, а не подтверждение сборки cubrid для основного PHP 8.5.11. Сайт показывает сохранённые результаты; новые запуски выполняются в собственной учебной базе.
| Что хранится | Модель в базе | Что получит читатель |
|---|---|---|
| Измерение датчика: заголовок и три числа | id=10, title, body BLOB | attachment.bin и отсчёты 1250; 0; 4095 |
| Инструкция к Кабелю O'Reilly | id=7, title, body CLOB | instruction.txt с кириллицей и знаком ✓ |
| Оригинальный документ поставки | id=21, title, body BLOB | supplier.csv с двумя товарами |
| Список состояний документов | id=10/20/30, NULL/пустой/заполненный BLOB | attachment-states.csv и три подсказки |
Представьте строку как карточку документа, а LOB — как отдельное содержимое, с которым карточка связана. Дескриптор PHP — временный ключ доступа во время выполнения программы. Он не является самим файлом, номером строки, URL скачивания или объектом класса PHP. Аналогия не обещает одного физического файла на строку: размещением содержимого управляет CUBRID и настроенное внешнее хранилище.
Самый короткий путь: пустой BLOB и три байта
// Соединение $db уже открыто. Создадим временное содержимое.
$lob = cubrid_lob2_new($db, 'BLOB');
if ($lob === false) { throw new RuntimeException('LOB не создан'); }
if (cubrid_lob2_write($lob, "OK\n") !== true) {
throw new RuntimeException('Три байта не записаны');
}
$bytes = cubrid_lob2_size($lob);
if ($bytes === false) { throw new RuntimeException('Нет размера'); }
echo $bytes; // 3: O, K, перевод строки
// Пока ни одной строки документа в таблице не появилось.
cubrid_lob2_close($lob);
$lob = null;Оператор = сохраняет результат вызова в переменную; аргументы в скобках передаются функции. Строка BLOB выбирает двоичный тип, "OK\n" задаёт содержимое. Проверка === false отделяет отказ от нулевого размера. Вызов close завершает владение созданным ресурсом. Чтобы сохранить его для следующего запуска, нужно подготовить INSERT с ?, привязать LOB к этому параметру и выполнить SQL; границу сохранения задаёт commit.
- PHP: new($db,"BLOB") → живой пустой дескриптор
- write("OK\n") либо import(назначенный файл) → содержимое
- prepare INSERT id,title,body → bind обычных полей и lob2_bind параметра body
- execute → проверка одной строки → освобождение ресурсов → commit
- Следующий SELECT по id → содержимое LOB → файл результата, размер и SHA
LOB2 и прежние cubrid_lob_* или GLO — разные семейства API; их ресурсы и правила нельзя смешивать. Все семь функций этой главы документированы начиная с PECL CUBRID 8.4.1. Сигнатуры карточек показывают также false, когда краткая строка PHP Manual перечисляет только int/string: раздел Return Values явно описывает этот отказ. Для малого VARCHAR обычно не нужен CLOB; для файлового сервиса внешнее объектное хранилище тоже может быть подходящей архитектурой, если согласованы права, резервное копирование и ссылка в базе.
Сохранить файл измерения и вернуть его без потери нулевых байтов
У карточки датчика нужно хранить исходное измерение. Учебный формат состоит из заголовка METER171, нулевого разделителя, трёх 16-битных отсчётов и окончания END. Скачиваемый файл будет получен из базы, а не скопирован из исходной переменной.
- id=10, «Измерение датчика» → пустой BLOB
- Заголовок 9 байт + отсчёты 6 байт + окончание 5 байт → write
- LOB → параметр 3 INSERT → execute → commit
- SELECT только id=10 → bytes/SHA → attachment.bin и отсчёты 1250; 0; 4095
Минимальный вызов
$lob = cubrid_lob2_new($db, 'BLOB');
if ($lob === false) { throw new RuntimeException('Не создано вложение'); }
if (!cubrid_lob2_write($lob, "METER171\0")) {
throw new RuntimeException('Не записан заголовок');
}
// Дальше нужно связать LOB с ? подготовленного INSERT и выполнить запрос.new возвращает дескриптор, write принимает сами байты. В двойных кавычках \0 обозначает нулевой байт; это часть файла, а не SQL NULL. Полная программа pack('v*',...) получает три маленьких целых в формате little-endian; unpack читает те же шесть байтов обратно. Индекс строки id=10 выбирается условием WHERE, поэтому не загружается весь каталог.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Карточка | Измерение датчика |
| Файл | attachment.bin |
| Размер, байт | 20 |
| Заголовок | METER171 + NUL |
| Три отсчёта | 1250; 0; 4095 |
| Содержимое совпало | да |
| Размер size64 | 20 |
Самостоятельный проект, исходник и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c171Config();$db=c171Connect();$runtime=c171Runtime($db);
function value171(callable $action) {
$call=c171Capture($action);
if ($call['value']===false || $call['value']===null || $call['warning']) {
c171Failure('Действие с вложением не выполнено',$call);
}
return $call['value'];
}
function closeCreated171(&$lob): void {
if (is_resource($lob)) {
$call=c171Capture(static function() use ($lob) {return cubrid_lob2_close($lob);});
$lob=null;
if ($call['value']!==true || $call['warning']) {c171Failure('Созданный LOB не закрыт',$call);}
} else {$lob=null;}
}
function saveLob171($db,string $table,int $id,string $title,$lob,string $type): int {
$request=null;
try {
$request=value171(static function() use ($db,$table) {
return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (?,?,?)');
});
value171(static function() use ($request,$id) {return cubrid_bind($request,1,$id,'int');});
value171(static function() use ($request,$title) {return cubrid_bind($request,2,$title,'string');});
value171(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,3,$lob,$type);});
value171(static function() use ($request) {return cubrid_execute($request);});
$affected=value171(static function() use ($request) {return cubrid_affected_rows($request);});
if ($affected!==1) {throw new RuntimeException('Не сохранена одна строка вложения');}
return $affected;
} finally {c171Release($request);}
}
function savedLob171($db,string $table,int $id): array {
$request=null;$row=null;$lob=null;$fetched=null;
try {
$request=c171Request($db,'SELECT id,title,body FROM '.$table.' WHERE id=?',[$id]);
$fetched=c171Capture(static function() use ($request) {
return cubrid_fetch_array($request,CUBRID_ASSOC|CUBRID_LOB);
});
if (!is_array($fetched['value']) || $fetched['warning']) {c171Failure('Вложение не найдено',$fetched);}
$row=$fetched['value'];
if ($row['body']===null) {return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>true,'bytes'=>null,'bytes64'=>null,'data'=>null];}
$lob=$row['body'];
if (!is_resource($lob)) {throw new RuntimeException('Нужен LOB-ресурс результата');}
$size=value171(static function() use ($lob) {return cubrid_lob2_size($lob);});
$size64=value171(static function() use ($lob) {return cubrid_lob2_size64($lob);});
if (!is_int($size) || $size<0 || $size>1048576 || $size64!==(string)$size) {
throw new RuntimeException('Этот учебный просмотр ограничен одним МиБ');
}
$data='';$remaining=$size;
while ($remaining>0) {
$amount=min(8192,$remaining);
$part=value171(static function() use ($lob,$amount) {return cubrid_lob2_read($lob,$amount);});
if (!is_string($part) || strlen($part)===0 || strlen($part)>$remaining) {throw new RuntimeException('Неполное чтение LOB');}
$data.=$part;$remaining-=strlen($part);
}
return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>false,'bytes'=>$size,'bytes64'=>$size64,'data'=>$data];
} finally {
// Driver 11.2.0.0002 retains raw pointers in the request.
// Consume first, release request, then drop every PHP reference.
c171Release($request);
$lob=null;$row=null;$fetched=null;
}
}
function artifact171(string $name,string $data): void {
if (basename($name)!==$name) {throw new InvalidArgumentException('Нужно простое имя файла');}
if (file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}
$name=c171OwnName($db,$owned,'TABLE','binary_file');$t=c171Qualified($name);
c171Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),body BLOB)');c171Commit($db);
$payload="METER171\0".pack('v*',1250,0,4095)."\nEND\n";
$lob=null;
try {
$lob=value171(static function() use ($db) {return cubrid_lob2_new($db,'BLOB');});
// One binary PHP string; embedded NUL must not be sent as a string bind.
$written=value171(static function() use ($lob,$payload) {return cubrid_lob2_write($lob,$payload);});
$size=value171(static function() use ($lob) {return cubrid_lob2_size($lob);});
$size64=value171(static function() use ($lob) {return cubrid_lob2_size64($lob);});
$affected=saveLob171($db,$t,10,'Измерение датчика',$lob,'BLOB');
} finally {closeCreated171($lob);}
c171Commit($db);
$saved=savedLob171($db,$t,10);c171Commit($db);
artifact171('attachment.bin',$saved['data']);
$measurements=array_values(unpack('v*',substr($saved['data'],9,6)));
$rows=[['Карточка',$saved['title']],['Файл','attachment.bin'],['Размер, байт',$saved['bytes']],
['Заголовок','METER171 + NUL'],['Три отсчёта',implode('; ',$measurements)],
['Содержимое совпало',$saved['data']===$payload],['Размер size64',$saved['bytes64']]];
$checks=['created_resource_closed'=>$lob===null,'write_true'=>$written===true,'one_row_saved'=>$affected===1,
'native_size'=>$size===strlen($payload),'size64_string'=>is_string($size64)&&$size64===(string)$size,
'saved_binary_exact'=>$saved['data']===$payload,'header_exact'=>substr($saved['data'],0,9)==="METER171\0",
'measurements_exact'=>$measurements===[1250,0,4095],'artifact_exact'=>hash_file('sha256',__DIR__.'/attachment.bin')===hash('sha256',$payload)];
$observations=['selected_id'=>$saved['id'],'payload_hex'=>bin2hex($saved['data']),'measurements'=>$measurements,
'sha256'=>hash('sha256',$saved['data']),'source_bytes'=>strlen($payload),'size_type'=>gettype($size),'size64_type'=>gettype($size64)];
} finally {
if (is_resource($db)) {try {$removed=c171Cleanup($db,$owned);} finally {c171Close($db);}}
}
c171Report('cub171-binary-attachment','Сохранить файл измерения и вернуть его без потери нулевых байтов',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Карточка — Измерение датчика
Файл — attachment.bin
Размер, байт — 20
Заголовок — METER171 + NUL
Три отсчёта — 1250; 0; 4095
Содержимое совпало — да
Размер size64 — 20
Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.
Открыть результат выполнения · Посмотреть данные JSON · Скачать файл измерения
Границы решения
BLOB не является фотографией только из-за расширения файла. Здесь формат известен приложению; файл не исполняется. Для маленького 20-байтового примера полученное содержимое держится в PHP, но универсальная загрузка огромного LOB целиком потребует слишком много памяти. Следующая глава показывает порционное чтение. SHA проверяет равенство, но сам по себе не подтверждает авторство файла.
Попробуйте изменить условие
Измените второй отсчёт с 0 на 25. Как проверить, что изменились именно два байта числа, а заголовок остался прежним?
Разбор
Замените значение pack и ожидаемую тройку unpack. Сравните первые девять байтов и длину файла; изменится SHA-256. Не заменяйте бинарный ноль строкой «0»: это другой байт и другая структура.
Собрать инструкцию UTF-8 по шагам и сохранить как CLOB
Оператор готовит инструкцию к кабелю: заголовок и два шага. Нужно сохранить кириллицу, апостроф в названии и знак проверки, а затем выдать ровно тот же текст. CLOB хранит содержимое; отдельное название товара остаётся обычным столбцом VARCHAR.
- id=7, Кабель O'Reilly → CLOB
- Три строки UTF-8 → три write по порядку
- CLOB → параметр INSERT → commit
- SELECT id=7 → тот же текст → instruction.txt; размер байтов отдельно от числа символов
Минимальный вызов
$text = cubrid_lob2_new($db, 'CLOB');
if ($text === false) { throw new RuntimeException('Не создан текст'); }
if (!cubrid_lob2_write($text, "1. Отключить питание.\n")) {
throw new RuntimeException('Шаг не сохранён');
}CLOB выбран явно вторым аргументом. Последующие write дописывают следующие части в текущей последовательности. \n задаёт перевод строки; апостроф внутри двойных кавычек не закрывает PHP-строку. Сравнение с strlen проверяет байты, preg_split с модификатором u считает кодовые точки для этой короткой инструкции; это не обещание числа графем любого языка.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Товар | Кабель O'Reilly |
| Записано частей | 3 |
| Размер UTF-8, байт | 108 |
| Кодовых точек Unicode | 65 |
| Текст совпал | да |
| Файл | instruction.txt |
Самостоятельный проект, исходник и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c171Config();$db=c171Connect();$runtime=c171Runtime($db);
function value171(callable $action) {
$call=c171Capture($action);
if ($call['value']===false || $call['value']===null || $call['warning']) {
c171Failure('Действие с вложением не выполнено',$call);
}
return $call['value'];
}
function closeCreated171(&$lob): void {
if (is_resource($lob)) {
$call=c171Capture(static function() use ($lob) {return cubrid_lob2_close($lob);});
$lob=null;
if ($call['value']!==true || $call['warning']) {c171Failure('Созданный LOB не закрыт',$call);}
} else {$lob=null;}
}
function saveLob171($db,string $table,int $id,string $title,$lob,string $type): int {
$request=null;
try {
$request=value171(static function() use ($db,$table) {
return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (?,?,?)');
});
value171(static function() use ($request,$id) {return cubrid_bind($request,1,$id,'int');});
value171(static function() use ($request,$title) {return cubrid_bind($request,2,$title,'string');});
value171(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,3,$lob,$type);});
value171(static function() use ($request) {return cubrid_execute($request);});
$affected=value171(static function() use ($request) {return cubrid_affected_rows($request);});
if ($affected!==1) {throw new RuntimeException('Не сохранена одна строка вложения');}
return $affected;
} finally {c171Release($request);}
}
function savedLob171($db,string $table,int $id): array {
$request=null;$row=null;$lob=null;$fetched=null;
try {
$request=c171Request($db,'SELECT id,title,body FROM '.$table.' WHERE id=?',[$id]);
$fetched=c171Capture(static function() use ($request) {
return cubrid_fetch_array($request,CUBRID_ASSOC|CUBRID_LOB);
});
if (!is_array($fetched['value']) || $fetched['warning']) {c171Failure('Вложение не найдено',$fetched);}
$row=$fetched['value'];
if ($row['body']===null) {return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>true,'bytes'=>null,'bytes64'=>null,'data'=>null];}
$lob=$row['body'];
if (!is_resource($lob)) {throw new RuntimeException('Нужен LOB-ресурс результата');}
$size=value171(static function() use ($lob) {return cubrid_lob2_size($lob);});
$size64=value171(static function() use ($lob) {return cubrid_lob2_size64($lob);});
if (!is_int($size) || $size<0 || $size>1048576 || $size64!==(string)$size) {
throw new RuntimeException('Этот учебный просмотр ограничен одним МиБ');
}
$data='';$remaining=$size;
while ($remaining>0) {
$amount=min(8192,$remaining);
$part=value171(static function() use ($lob,$amount) {return cubrid_lob2_read($lob,$amount);});
if (!is_string($part) || strlen($part)===0 || strlen($part)>$remaining) {throw new RuntimeException('Неполное чтение LOB');}
$data.=$part;$remaining-=strlen($part);
}
return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>false,'bytes'=>$size,'bytes64'=>$size64,'data'=>$data];
} finally {
// Driver 11.2.0.0002 retains raw pointers in the request.
// Consume first, release request, then drop every PHP reference.
c171Release($request);
$lob=null;$row=null;$fetched=null;
}
}
function artifact171(string $name,string $data): void {
if (basename($name)!==$name) {throw new InvalidArgumentException('Нужно простое имя файла');}
if (file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}
$name=c171OwnName($db,$owned,'TABLE','instructions');$t=c171Qualified($name);
c171Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),body CLOB)');c171Commit($db);
$lines=["Кабель O'Reilly\n","1. Отключить питание.\n","2. Проверить маркировку ✓.\n"];
$source=implode('',$lines);$lob=null;$calls=0;
try {
$lob=value171(static function() use ($db) {return cubrid_lob2_new($db,'CLOB');});
foreach($lines as $line) {
value171(static function() use ($lob,$line) {return cubrid_lob2_write($lob,$line);});$calls++;
}
$size=value171(static function() use ($lob) {return cubrid_lob2_size($lob);});
$size64=value171(static function() use ($lob) {return cubrid_lob2_size64($lob);});
$affected=saveLob171($db,$t,7,"Кабель O'Reilly",$lob,'CLOB');
} finally {closeCreated171($lob);}
c171Commit($db);$saved=savedLob171($db,$t,7);c171Commit($db);
artifact171('instruction.txt',$saved['data']);
$decoded=preg_split('//u',$saved['data'],-1,PREG_SPLIT_NO_EMPTY);
if ($decoded===false) {throw new RuntimeException('Инструкция не является UTF-8');}
$rows=[['Товар',$saved['title']],['Записано частей',$calls],['Размер UTF-8, байт',$saved['bytes']],
['Кодовых точек Unicode',count($decoded)],['Текст совпал',$saved['data']===$source],['Файл','instruction.txt']];
$checks=['three_writes'=>$calls===3,'one_row_saved'=>$affected===1,'created_resource_closed'=>$lob===null,
'utf8_exact'=>$saved['data']===$source,'byte_length'=>$size===strlen($source)&&$saved['bytes']===$size,
'decimal_size'=>$size64===(string)$size,'bytes_not_characters'=>$size>count($decoded),
'apostrophe_preserved'=>strpos($saved['data'],"O'Reilly")!==false,
'check_mark_preserved'=>strpos($saved['data'],'✓')!==false,
'artifact_exact'=>hash_file('sha256',__DIR__.'/instruction.txt')===hash('sha256',$source)];
$observations=['instruction'=>$saved['data'],'line_bytes'=>array_map('strlen',$lines),'characters'=>count($decoded),
'size64'=>$size64,'sha256'=>hash('sha256',$saved['data']),'charset'=>$runtime['charset']];
} finally {
if (is_resource($db)) {try {$removed=c171Cleanup($db,$owned);} finally {c171Close($db);}}
}
c171Report('cub171-text-instructions','Собрать инструкцию UTF-8 по шагам и сохранить как CLOB',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Товар — Кабель O'Reilly
Записано частей — 3
Размер UTF-8, байт — 108
Кодовых точек Unicode — 65
Текст совпал — да
Файл — instruction.txt
Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.
Открыть результат выполнения · Посмотреть данные JSON · Скачать инструкцию UTF-8
Границы решения
Не делайте HTML из инструкции без htmlspecialchars. Логический тип CLOB не заменяет согласованность UTF-8 исходника, соединения и выходного файла. В реальном редакторе большие тексты могут храниться во внешнем файловом хранилище с метаданными в базе: выбирайте подход с учётом резервного копирования и прав. Этот пример не сохраняет HTTP-форму и не обрабатывает пользовательскую загрузку.
Попробуйте изменить условие
Добавьте третью строку инструкции с символом «€». Какие результаты изменятся?
Разбор
Изменятся число write, число кодовых точек, размер в байтах и SHA-256. Для UTF-8 знак € занимает три байта, но одну кодовую точку. Сравнивайте исходник и выгруженный файл; не выводите ожидаемый текст вместо фактически полученного.
Архивировать исходный CSV поставщика и вернуть проверенную копию
Нужно сохранить оригинальный документ поставки, чтобы сверить спорную строку позднее. В архиве проекта есть supplier-input.csv с двумя товарами. Программа помещает целый файл в BLOB, выбирает запись поставки по id и сохраняет возвращённый файл supplier.csv.
- supplier-input.csv → две товарные строки в UTF-8
- Новый пустой BLOB → import всего файла; курсор остаётся 0
- id=21 и название поставки + BLOB → SQL INSERT → commit
- SELECT id=21 → supplier.csv → заголовок/два товара/SHA совпадают
Минимальный вызов
$lob = cubrid_lob2_new($db, 'BLOB');
if ($lob === false) { throw new RuntimeException('Не создано вложение'); }
if (!cubrid_lob2_import($lob, __DIR__.'/supplier-input.csv')) {
throw new RuntimeException('CSV не перенесён');
}__DIR__ — папка текущего PHP-файла; имя входа фиксировано приложением. Import получает путь, читает файл и заполняет новый LOB. Он не делает INSERT товарных строк. В конце отдельный fgetcsv разбирает две строки уже возвращённого документа для видимого результата; расширение .csv не заменяет проверку его структуры.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| Поставка | Поставка CAB-7 / CLP-2 |
| Товарных строк | 2 |
| Первый товар | Кабель O'Reilly |
| Байт в исходном файле | 63 |
| Позиция после import | 0 |
| Копия совпала | да |
| Файл | supplier.csv |
Самостоятельный проект, исходник и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c171Config();$db=c171Connect();$runtime=c171Runtime($db);
function value171(callable $action) {
$call=c171Capture($action);
if ($call['value']===false || $call['value']===null || $call['warning']) {
c171Failure('Действие с вложением не выполнено',$call);
}
return $call['value'];
}
function closeCreated171(&$lob): void {
if (is_resource($lob)) {
$call=c171Capture(static function() use ($lob) {return cubrid_lob2_close($lob);});
$lob=null;
if ($call['value']!==true || $call['warning']) {c171Failure('Созданный LOB не закрыт',$call);}
} else {$lob=null;}
}
function saveLob171($db,string $table,int $id,string $title,$lob,string $type): int {
$request=null;
try {
$request=value171(static function() use ($db,$table) {
return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (?,?,?)');
});
value171(static function() use ($request,$id) {return cubrid_bind($request,1,$id,'int');});
value171(static function() use ($request,$title) {return cubrid_bind($request,2,$title,'string');});
value171(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,3,$lob,$type);});
value171(static function() use ($request) {return cubrid_execute($request);});
$affected=value171(static function() use ($request) {return cubrid_affected_rows($request);});
if ($affected!==1) {throw new RuntimeException('Не сохранена одна строка вложения');}
return $affected;
} finally {c171Release($request);}
}
function savedLob171($db,string $table,int $id): array {
$request=null;$row=null;$lob=null;$fetched=null;
try {
$request=c171Request($db,'SELECT id,title,body FROM '.$table.' WHERE id=?',[$id]);
$fetched=c171Capture(static function() use ($request) {
return cubrid_fetch_array($request,CUBRID_ASSOC|CUBRID_LOB);
});
if (!is_array($fetched['value']) || $fetched['warning']) {c171Failure('Вложение не найдено',$fetched);}
$row=$fetched['value'];
if ($row['body']===null) {return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>true,'bytes'=>null,'bytes64'=>null,'data'=>null];}
$lob=$row['body'];
if (!is_resource($lob)) {throw new RuntimeException('Нужен LOB-ресурс результата');}
$size=value171(static function() use ($lob) {return cubrid_lob2_size($lob);});
$size64=value171(static function() use ($lob) {return cubrid_lob2_size64($lob);});
if (!is_int($size) || $size<0 || $size>1048576 || $size64!==(string)$size) {
throw new RuntimeException('Этот учебный просмотр ограничен одним МиБ');
}
$data='';$remaining=$size;
while ($remaining>0) {
$amount=min(8192,$remaining);
$part=value171(static function() use ($lob,$amount) {return cubrid_lob2_read($lob,$amount);});
if (!is_string($part) || strlen($part)===0 || strlen($part)>$remaining) {throw new RuntimeException('Неполное чтение LOB');}
$data.=$part;$remaining-=strlen($part);
}
return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>false,'bytes'=>$size,'bytes64'=>$size64,'data'=>$data];
} finally {
// Driver 11.2.0.0002 retains raw pointers in the request.
// Consume first, release request, then drop every PHP reference.
c171Release($request);
$lob=null;$row=null;$fetched=null;
}
}
function artifact171(string $name,string $data): void {
if (basename($name)!==$name) {throw new InvalidArgumentException('Нужно простое имя файла');}
if (file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}
$name=c171OwnName($db,$owned,'TABLE','supplier_file');$t=c171Qualified($name);
c171Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),body BLOB)');c171Commit($db);
$input=__DIR__.'/supplier-input.csv';
if (!is_file($input)||!is_readable($input)||filesize($input)>1048576) {throw new RuntimeException('Нет допустимого входного CSV');}
$source=file_get_contents($input);if (!is_string($source)) {throw new RuntimeException('Входной CSV не прочитан');}
$lob=null;
try {
// Always a NEW empty LOB: import is not a truncate/replace operation.
$lob=value171(static function() use ($db) {return cubrid_lob2_new($db,'BLOB');});
$imported=value171(static function() use ($lob,$input) {return cubrid_lob2_import($lob,$input);});
$size=value171(static function() use ($lob) {return cubrid_lob2_size($lob);});
$position=value171(static function() use ($lob) {return cubrid_lob2_tell($lob);});
$affected=saveLob171($db,$t,21,'Поставка CAB-7 / CLP-2',$lob,'BLOB');
} finally {closeCreated171($lob);}
c171Commit($db);$saved=savedLob171($db,$t,21);c171Commit($db);
artifact171('supplier.csv',$saved['data']);
$file=fopen(__DIR__.'/supplier.csv','rb');if ($file===false) {throw new RuntimeException('Копия CSV не открыта');}
try {$header=fgetcsv($file);$products=[];while(($row=fgetcsv($file))!==false) {$products[]=$row;}} finally {fclose($file);}
$rows=[['Поставка',$saved['title']],['Товарных строк',count($products)],['Первый товар',$products[0][1]],
['Байт в исходном файле',$size],['Позиция после import',$position],['Копия совпала',$saved['data']===$source],['Файл','supplier.csv']];
$checks=['import_true'=>$imported===true,'one_row_saved'=>$affected===1,'created_resource_closed'=>$lob===null,
'import_did_not_move_cursor'=>$position===0,'file_length'=>$size===strlen($source),
'header_exact'=>$header===['sku','title','qty'],'two_products'=>count($products)===2,
'values_exact'=>$products===[["CAB-7","Кабель O'Reilly","3"],["CLP-2","Хомут","0"]],
'stored_exact'=>$saved['data']===$source,'artifact_exact'=>hash_file('sha256',__DIR__.'/supplier.csv')===hash('sha256',$source)];
$observations=['products'=>$products,'header'=>$header,'sha256'=>hash('sha256',$saved['data']),
'bytes'=>$size,'position_after_import'=>$position,'source_filename'=>'supplier-input.csv'];
} finally {
if (is_resource($db)) {try {$removed=c171Cleanup($db,$owned);} finally {c171Close($db);}}
}
c171Report('cub171-file-import','Архивировать исходный CSV поставщика и вернуть проверенную копию',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Поставка — Поставка CAB-7 / CLP-2
Товарных строк — 2
Первый товар — Кабель O'Reilly
Байт в исходном файле — 63
Позиция после import — 0
Копия совпала — да
Файл — supplier.csv
Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.
Открыть результат выполнения · Посмотреть данные JSON · Скачать CSV поставщика
Границы решения
Для реального приёма HTTP-файла сначала нужны ограничения загрузки и назначенный сервером путь; этот проект начинает с известного локального файла. Не импортируйте в уже заполненный LOB как способ обрезания: проверенный драйвер может отказать и повредить внутреннее состояние дескриптора. История подготовки содержит такой отказ; рабочий проект использует только новый LOB и корректную фиксированную фикстуру.
Попробуйте изменить условие
Добавьте товар с запятой в названии. Достаточно ли просто приписать ещё одну запятую к строке CSV?
Разбор
Нет: поле с запятой нужно заключить в двойные кавычки по правилам CSV, а внутренние кавычки удвоить. Лучше создавать CSV через fputcsv. Сохранение BLOB должно остаться побайтно точным независимо от того, как fgetcsv затем разобрал поля.
Различить отсутствующий файл, пустой BLOB и готовое вложение
В списке документов отсутствие файла и приложенный пустой шаблон требуют разных подсказок. Третья запись содержит готовую квитанцию OK с переводом строки. Программа читает каждую нужную запись по ключу и формирует CSV для оператора.
- id=10 → SQL NULL → «запросить файл»
- id=20 → новый BLOB без write → size=0 → «пустой шаблон»
- id=30 → BLOB с OK и переводом строки → size=3 → «готов»
- Три строгие ветви → attachment-states.csv
Минимальный вызов
if ($row['body'] === null) {
$status = 'Файл не приложен';
} else {
$size = cubrid_lob2_size($row['body']);
if ($size === false) { throw new RuntimeException('Размер не получен'); }
$status = $size === 0 ? 'Пустой файл' : 'Есть содержимое';
}=== null проверяет отсутствие значения SQL, === false — отказ функции, === 0 — успешный нулевой размер. Пустой BLOB создаётся new без write и привязывается к INSERT. SQL NULL сохраняется отдельной SQL-командой. Строковое число «0», нулевой байт и PHP false не заменяют эти состояния.
Что получится после запуска
| Наблюдение | Результат |
|---|---|
| id=10 | Файл не приложен (SQL NULL) |
| id=20 | Пустой файл, 0 байт |
| id=30 | Готовая квитанция, 3 байта |
| Разных состояний | 3 |
| Файл | attachment-states.csv |
Самостоятельный проект, исходник и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
try {
$config=c171Config();$db=c171Connect();$runtime=c171Runtime($db);
function value171(callable $action) {
$call=c171Capture($action);
if ($call['value']===false || $call['value']===null || $call['warning']) {
c171Failure('Действие с вложением не выполнено',$call);
}
return $call['value'];
}
function closeCreated171(&$lob): void {
if (is_resource($lob)) {
$call=c171Capture(static function() use ($lob) {return cubrid_lob2_close($lob);});
$lob=null;
if ($call['value']!==true || $call['warning']) {c171Failure('Созданный LOB не закрыт',$call);}
} else {$lob=null;}
}
function saveLob171($db,string $table,int $id,string $title,$lob,string $type): int {
$request=null;
try {
$request=value171(static function() use ($db,$table) {
return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (?,?,?)');
});
value171(static function() use ($request,$id) {return cubrid_bind($request,1,$id,'int');});
value171(static function() use ($request,$title) {return cubrid_bind($request,2,$title,'string');});
value171(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,3,$lob,$type);});
value171(static function() use ($request) {return cubrid_execute($request);});
$affected=value171(static function() use ($request) {return cubrid_affected_rows($request);});
if ($affected!==1) {throw new RuntimeException('Не сохранена одна строка вложения');}
return $affected;
} finally {c171Release($request);}
}
function savedLob171($db,string $table,int $id): array {
$request=null;$row=null;$lob=null;$fetched=null;
try {
$request=c171Request($db,'SELECT id,title,body FROM '.$table.' WHERE id=?',[$id]);
$fetched=c171Capture(static function() use ($request) {
return cubrid_fetch_array($request,CUBRID_ASSOC|CUBRID_LOB);
});
if (!is_array($fetched['value']) || $fetched['warning']) {c171Failure('Вложение не найдено',$fetched);}
$row=$fetched['value'];
if ($row['body']===null) {return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>true,'bytes'=>null,'bytes64'=>null,'data'=>null];}
$lob=$row['body'];
if (!is_resource($lob)) {throw new RuntimeException('Нужен LOB-ресурс результата');}
$size=value171(static function() use ($lob) {return cubrid_lob2_size($lob);});
$size64=value171(static function() use ($lob) {return cubrid_lob2_size64($lob);});
if (!is_int($size) || $size<0 || $size>1048576 || $size64!==(string)$size) {
throw new RuntimeException('Этот учебный просмотр ограничен одним МиБ');
}
$data='';$remaining=$size;
while ($remaining>0) {
$amount=min(8192,$remaining);
$part=value171(static function() use ($lob,$amount) {return cubrid_lob2_read($lob,$amount);});
if (!is_string($part) || strlen($part)===0 || strlen($part)>$remaining) {throw new RuntimeException('Неполное чтение LOB');}
$data.=$part;$remaining-=strlen($part);
}
return ['id'=>(int)$row['id'],'title'=>$row['title'],'sql_null'=>false,'bytes'=>$size,'bytes64'=>$size64,'data'=>$data];
} finally {
// Driver 11.2.0.0002 retains raw pointers in the request.
// Consume first, release request, then drop every PHP reference.
c171Release($request);
$lob=null;$row=null;$fetched=null;
}
}
function artifact171(string $name,string $data): void {
if (basename($name)!==$name) {throw new InvalidArgumentException('Нужно простое имя файла');}
if (file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}
$name=c171OwnName($db,$owned,'TABLE','file_states');$t=c171Qualified($name);
c171Ddl($db,'CREATE TABLE '.$t.' (id INT PRIMARY KEY,title VARCHAR(80),body BLOB NULL)');c171Commit($db);
c171Command($db,'INSERT INTO '.$t.' (id,title,body) VALUES (?,?,NULL)',[10,'Нет документа']);
foreach([[20,'Пустой шаблон',''],[30,'Готовая квитанция',"OK\n"]] as $input) {
$lob=null;
try {
$lob=value171(static function() use ($db) {return cubrid_lob2_new($db,'BLOB');});
if ($input[2]!=='') {
$data=$input[2];value171(static function() use ($lob,$data) {return cubrid_lob2_write($lob,$data);});
}
saveLob171($db,$t,$input[0],$input[1],$lob,'BLOB');
} finally {closeCreated171($lob);}
}
c171Commit($db);$states=[];$csv="id,status,bytes\n";
foreach([10,20,30] as $id) {
$saved=savedLob171($db,$t,$id);
$status=$saved['sql_null']?'missing':($saved['bytes']===0?'empty':'ready');
$states[]=['id'=>$id,'status'=>$status,'bytes'=>$saved['bytes'],'bytes64'=>$saved['bytes64'],
'data_hex'=>$saved['data']===null?null:bin2hex($saved['data'])];
$csv.=$id.','.$status.','.($saved['bytes']===null?'':(string)$saved['bytes'])."\n";
}
c171Commit($db);artifact171('attachment-states.csv',$csv);
$rows=[['id=10','Файл не приложен (SQL NULL)'],['id=20','Пустой файл, 0 байт'],['id=30','Готовая квитанция, 3 байта'],
['Разных состояний',count(array_unique(array_column($states,'status')))],['Файл','attachment-states.csv']];
$checks=['three_rows'=>count($states)===3,'null_distinct'=>$states[0]['bytes']===null&&$states[0]['bytes64']===null,
'empty_distinct'=>$states[1]['bytes']===0&&$states[1]['bytes64']==='0'&&$states[1]['data_hex']==='',
'data_exact'=>$states[2]['bytes']===3&&$states[2]['data_hex']==='4f4b0a',
'three_states'=>array_column($states,'status')===['missing','empty','ready'],
'resource_closed'=>$lob===null,'artifact_rows'=>substr_count($csv,"\n")===4,
'artifact_written'=>file_get_contents(__DIR__.'/attachment-states.csv')===$csv];
$observations=['states'=>$states,'policy'=>['missing'=>'Запросить файл','empty'=>'Уточнить пустой шаблон','ready'=>'Разрешить просмотр после проверки прав']];
} finally {
if (is_resource($db)) {try {$removed=c171Cleanup($db,$owned);} finally {c171Close($db);}}
}
c171Report('cub171-attachment-states','Различить отсутствующий файл, пустой BLOB и готовое вложение',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
id=10 — Файл не приложен (SQL NULL)
id=20 — Пустой файл, 0 байт
id=30 — Готовая квитанция, 3 байта
Разных состояний — 3
Файл — attachment-states.csv
Распакуйте весь ZIP и прочитайте README.txt. example.php содержит действия урока, lab.php — подключение к отдельной учебной базе, создание только собственной таблицы и очистку. Запускается CLI-проект с native-расширением cubrid, не публичный обработчик сайта. После запуска result.html показывает результат, result.json — наблюдения, а отдельный файл ниже содержит полученные данные.
Открыть результат выполнения · Посмотреть данные JSON · Скачать состояния вложений
Границы решения
Наличие байтов не означает, что документ достоверен, доступен данному пользователю или имеет ожидаемый формат. Соответствующие проверки выполняются отдельно. При отсутствии самой строки SELECT нельзя показывать «пустой файл»: полный проект останавливается с ошибкой «вложение не найдено». Для массового списка сначала выбирайте только метаданные нужной страницы; не скачивайте каждый BLOB ради списка названий.
Попробуйте изменить условие
Добавьте состояние «строка документа не найдена». Нужно ли сохранять его в том же столбце как ещё один пустой BLOB?
Разбор
Нет. Это исход запроса по id, а не значение body существующей строки. Обработайте отсутствие строки отдельно: 404/сообщение по политике приложения. Для SQL NULL, пустого и заполненного LOB оставьте три существующие ветви.
Почему нужны точные байты, явные состояния и владение ресурсами
Вопрос о NUL внутри данных напоминает о различии текста и произвольных байтов; особенности SQLite не переносятся на CUBRID. В наших программах бинарные данные проходят через ресурс BLOB, а не строковую ветку lob2_bind, где закреплённый native-драйвер использует strlen C.
Обсуждение неверной кодировки CLOB при выгрузке описывает реальную проблему пользователя Oracle: верные данные становятся нечитаемыми на выходе. Здесь решение своё — UTF-8-фикстура, согласованное соединение, чтение из CUBRID и сравнение точных байтов. Переменные среды OCI8 и названия кодировок Oracle к native CUBRID не применяются.
В вопросе о допустимом пустом BLOB смешались отсутствие значения и намеренно пустое содержимое. Пример состояний показывает три явные ветви и не полагается на слабое сравнение PHP. Ошибка чтения остаётся четвёртым исходом, а отсутствие строки — отдельным результатом SELECT.
Практическая граница старого драйвера существенна: новый LOB и LOB из результата освобождаются разными путями. Полный savedLob171 сначала потребляет содержимое, закрывает запрос, затем снимает все PHP-ссылки на полученный ресурс. Чтение здесь ограничено одним МиБ и одной строкой; следующий раздел показывает порционную обработку без накопления целого файла. В этих программах нет произвольного SQL из пользовательского ввода и нет обращения ко всей таблице ради одного вложения.
В CUBRID 11.3 DELETE записи и DROP TABLE имеют разные последствия для внешнего хранилища: удаление записи удаляет связанное содержимое, а удаление таблицы убирает locator, оставляя внешний файл. Cleanup учебного проекта подтверждает удаление собственной SQL-таблицы; он не выдаёт всю папку LOB за пустую. Не удаляйте вручную файлы работающей базы. Учитывайте внешнее хранилище при резервном копировании и удалении отдельной лаборатории. Ключи, названия, права и признаки документа храните обычными полями: на LOB-столбце нельзя построить обычный индекс или определить PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL; LOB также не является элементом коллекции.
Семь функций по задаче
| Функция | Для чего |
|---|---|
| cubrid_lob2_new | Создать отдельный BLOB/CLOB перед записью и привязкой к строке. |
| cubrid_lob2_bind | Передать содержимое LOB в конкретный параметр подготовленного SQL. |
| cubrid_lob2_write | Записать очередную порцию двоичных данных или текста. |
| cubrid_lob2_import | Перенести целый локальный файл в подготовленный LOB. |
| cubrid_lob2_close | Освободить самостоятельно созданный дескриптор LOB в правильный момент. |
| cubrid_lob2_size | Узнать размер и отличить существующее пустое вложение от отказа. |
| cubrid_lob2_size64 | Получить размер десятичной строкой без сужения до PHP int. |
Следующий шаг: читать ровно нужную часть файла
Теперь вложение можно сохранить, повторно получить по ключу и выдать настоящим файлом. Следующая глава — чтение, экспорт, позиции и диапазоны LOB: прочитать порциями, получить файл целиком, выбрать нужный фрагмент и не потерять границы ресурса. Метаданные названия/размера/владельца выбирают ограниченным SQL; большое содержимое читают только по запросу разрешённого документа.
Для приложения с несколькими пользователями добавьте проверку прав на конкретный документ, назначенные сервером имена, ограничения размера и согласованное резервное копирование базы с LOB-хранилищем. Основа этих решений связана с загрузкой файлов, файловыми операциями и транзакциями CUBRID.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
cubrid_lob2_newСоздать отдельный BLOB/CLOB перед записью и привязкой к строке.
PECL CUBRID >= 8.4.1; проверена версия 11.2.0.0002. · cubrid
cubrid_lob2_new(resource $conn_identifier = ?, string $type = "BLOB"): resource|falseК карточке оборудования надо приложить файл измерений или инструкцию. Сначала создаётся пустой LOB нужного типа; строка таблицы появится только после INSERT/UPDATE и подтверждения транзакции.
| Параметр | Что передать |
|---|---|
| conn_identifier | Соединение cubrid_connect или cubrid_connect_with_url. Если опущено, используется последнее открытое соединение этих функций. В прикладном коде передавайте $db явно, чтобы не записать файл в другую базу. |
| type | Строка BLOB или CLOB, регистр несущественен. По умолчанию BLOB. BLOB — произвольные байты; CLOB — большое текстовое содержимое. Это не MIME-тип и не расширение имени файла. |
Результат: Дескриптор resource при успехе, false при отказе. Его нельзя выводить вместо содержимого или считать первичным ключом строки.
При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c171Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте строгий тип результата: нулевой размер и пустое содержимое не обязательно означают ошибку.
Применение 1: файл измерений с двоичными байтами
$lob = cubrid_lob2_new($db, 'BLOB');
if ($lob === false) { throw new RuntimeException('Вложение не создано'); }
// $lob пока пуст. В него отдельно записываются байты файла.
if (!cubrid_lob2_write($lob, "BIN\0\x01")) {
throw new RuntimeException('Запись не выполнена');
}Файл измерений сохраняет нулевой байт и восстанавливает скачиваемый attachment.bin. Сравниваются размер и SHA-256 после чтения из базы: true от создания ещё не доказывает сохранность файла.
Применение 2: инструкция с текстом UTF-8
$instructions = cubrid_lob2_new($db, 'clob');
if ($instructions === false) { throw new RuntimeException('Текст не создан'); }
// Регистр clob допустим; кодировка самих строк остаётся UTF-8.
if (!cubrid_lob2_write($instructions, "Проверить питание.\n")) {
throw new RuntimeException('Инструкция не записана');
}Инструкция из нескольких шагов добавляет следующие строки по порядку. CLOB сам по себе не исправляет неверную кодировку исходника и не экранирует HTML. Для небольшого описания достаточно VARCHAR; большой тип нужен по требованиям данных, а не для каждого названия товара.
Создание зависит от настройки LOB-хранилища сервера. Указанный в README отдельный каталог хранения относится к учебной базе. Копирование одного файла базы или PHP не переносит внешнее LOB-хранилище: резервное копирование должно учитывать его вместе с данными базы. Держите созданный ресурс живым до выполнения привязанного запроса, освобождайте его через cubrid_lob2_close. Ошибка подключения, неподдерживаемый тип или отказ хранилища не равны пустому BLOB.
cubrid_lob2_bindПередать содержимое LOB в конкретный параметр подготовленного SQL.
PECL CUBRID >= 8.4.1; проверена версия 11.2.0.0002. · cubrid
cubrid_lob2_bind(resource $req_identifier, int $bind_index, mixed $bind_value, string $bind_value_type = ?): boolINSERT содержит вопросительный знак вместо файла. Bind связывает этот параметр с LOB; сам запрос ещё не выполняется. Значение не надо превращать в SQL-литерал, base64 или строку с кавычками.
| Параметр | Что передать |
|---|---|
| req_identifier | Дескриптор запроса, возвращённый cubrid_prepare. |
| bind_index | Порядковый номер ? начиная с 1. Это номер параметра SQL, не индекс PHP-массива и не номер строки. |
| bind_value | Дескриптор LOB2 либо строка. Несмотря на mixed в сигнатуре, не передавайте произвольные массивы/объекты. Для бинарных данных используйте ресурс после new/write или import. |
| bind_value_type | Необязательные BLOB/CLOB без учёта регистра. У строки по умолчанию BLOB; у ресурса тип наследуется от new. Явный тип ресурса должен совпасть с его действительным типом. |
Результат: true при успешной привязке, false при отказе. Далее отдельно проверяются execute, affected_rows и commit.
При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c171Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте строгий тип результата: нулевой размер и пустое содержимое не обязательно означают ошибку.
Применение 1: прикрепить подготовленное двоичное содержимое
// SQL: INSERT INTO attachments (id, title, body) VALUES (?, ?, ?)
cubrid_bind($request, 1, 10, 'int');
cubrid_bind($request, 2, 'Измерение', 'string');
if (cubrid_lob2_bind($request, 3, $lob, 'BLOB') !== true) {
throw new RuntimeException('Файл не передан в параметр 3');
}
if (cubrid_execute($request) !== true) {
throw new RuntimeException('Строка не сохранена');
}Полная обработка каждого отказа есть в сохранении двоичного вложения. Native-драйвер 11.2.0.0002 для строкового bind_value использует C strlen: встроенный NUL обрывает эту ветку. Поэтому бинарный файл передаётся ресурсом через binary-safe write/import. Это конкретное ограничение данной реализации, а не свойство всех строк PHP.
Применение 2: небольшой текст прямо в CLOB-параметр
// В строке нет встроенного NUL; SQL содержит один ? для notes CLOB.
if (!cubrid_lob2_bind($request, 1, "Проверено: кабель O'Reilly", 'CLOB')) {
throw new RuntimeException('Примечание не передано');
}
// Затем execute и явный commit; bind сам их не заменяет.Такой короткий текст не требует ручного деления на порции. Когда текст приходит частями, удобнее создать CLOB и записывать шаги. Когда нужен SQL NULL, используйте обычный cubrid_bind с типом null либо SQL NULL; lob2_bind(null) не является обещанным способом сохранить отсутствующее значение. Три состояния вложения показывает этот выбор явно.
При повторном execute проверьте, какие параметры перебиндиваются; не закрывайте ресурс до завершения записи. После сомнительного сетевого исхода нельзя бесконечно повторять INSERT без бизнес-ключа: появятся дубли вложений. Для одного содержимого и его метаданных используйте общую транзакцию.
cubrid_lob2_writeЗаписать очередную порцию двоичных данных или текста.
PECL CUBRID >= 8.4.1; проверена версия 11.2.0.0002. · cubrid
cubrid_lob2_write(resource $lob_identifier, string $buf): boolФайл может поступать порциями, а инструкция — последовательными строками. Write помещает очередную PHP-строку байтов в LOB. Это содержимое, а не путь к файлу.
| Параметр | Что передать |
|---|---|
| lob_identifier | Живой дескриптор, созданный new или полученный с CUBRID_LOB из результата. Для изменения в этих уроках используется новый дескриптор. |
| buf | Строка записываемых данных. PHP-строка может содержать NUL и произвольные байты; текст предварительно должен иметь выбранную кодировку. |
Результат: true при успехе, false при отказе. Результат не является числом записанных байтов; strlen(buf) измеряет длину переданной порции, size — состояние LOB.
При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c171Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте строгий тип результата: нулевой размер и пустое содержимое не обязательно означают ошибку.
Применение 1: заголовок и данные измерения
$parts = ["METER\0", "\x01\x00\xff", "END\n"];
foreach ($parts as $part) {
if (!cubrid_lob2_write($lob, $part)) {
throw new RuntimeException('Порция вложения не записана');
}
}В бинарном вложении нулевые байты проверяются после сохранения строки. При настоящем большом файле читайте ограниченную порцию через fread, а не формируйте весь файл конкатенацией в памяти. Для готового локального файла есть cubrid_lob2_import.
Применение 2: три строки инструкции
foreach (["Отключить питание.\n", "Проверить кабель.\n", "Закрыть крышку.\n"] as $line) {
if (!cubrid_lob2_write($instructions, $line)) {
throw new RuntimeException('Шаг инструкции не записан');
}
}Текстовая инструкция показывает байтовый размер UTF-8 и точное совпадение экспортированного текста. Символ «К» занимает несколько байтов; количество байтов нельзя выдавать за число видимых букв. Порции здесь заканчиваются на границах строк, поэтому в промежуточном интерфейсе не появляется половина UTF-8-символа.
PHP Manual сохраняет историческое ограничение «только дописывание». Исходник проверенной версии передаёт текущую позицию в CCI и увеличивает её на длину порции. Уроки используют надёжную последовательность new → write → write без перемещения назад; произвольное редактирование середины и обрезание файла здесь не обещаны. Положение отдельно объясняют cubrid_lob2_tell и cubrid_lob2_seek. Создавая замену файлу, создавайте новый LOB и меняйте ссылку в строке после успешного заполнения.
cubrid_lob2_importПеренести целый локальный файл в подготовленный LOB.
PECL CUBRID >= 8.4.1; проверена версия 11.2.0.0002. · cubrid
cubrid_lob2_import(resource $lob_identifier, string $file_name): boolПоставщик прислал CSV, который нужно сохранить вместе с записью приёмки и позднее вернуть побайтно. Import переносит весь локальный файл в уже созданный LOB; он не разбирает CSV и не импортирует его строки в SQL-таблицы.
| Параметр | Что передать |
|---|---|
| lob_identifier | Ресурс new либо ресурс из результата. Для новой версии файла создайте пустой LOB. |
| file_name | Имя/путь локального файла, читаемого процессом PHP. Не URL, не путь на компьютере посетителя и не произвольный адрес из HTTP-запроса. |
Результат: true при переносе, false при ошибке. Отсутствующий/нечитаемый файл вызывает отказ. Операция не меняет логическую позицию курсора LOB и обрабатывает файл целиком.
При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c171Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте строгий тип результата: нулевой размер и пустое содержимое не обязательно означают ошибку.
Применение 1: сохранить исходный CSV поставщика
$lob = cubrid_lob2_new($db, 'BLOB');
if ($lob === false || !cubrid_lob2_import($lob, __DIR__.'/supplier-input.csv')) {
throw new RuntimeException('Исходный CSV не сохранён');
}
// Затем lob2_bind, execute, освобождение ресурсов и commit.В архиве поставки поставляется реальная UTF-8-фикстура supplier-input.csv с кириллицей и апострофом. Скачиваемый supplier.csv получен из базы; равенство SHA-256 доказывает сохранение файла, а fgetcsv отдельно позволяет прочитать две товарные строки. BLOB выбран потому, что нужен оригинальный файл с точными байтами.
Применение 2: загрузить подготовленную инструкцию
$text = cubrid_lob2_new($db, 'CLOB');
if ($text === false || !cubrid_lob2_import($text, __DIR__.'/manual-utf8.txt')) {
throw new RuntimeException('Инструкция не прочитана из файла');
}
// Перед этим редактор сохранил manual-utf8.txt именно в UTF-8.Это вариант текстовой инструкции, когда файл уже подготовлен редактором, а не собирается по шагам. CLOB не распознаёт и не конвертирует автоматически любую исходную кодировку. Для получения файла обратно используется cubrid_lob2_export.
Реализация 11.2.0.0002 начинает запись с локального смещения 0; логический курсор дескриптора не двигается. При попытке import в заполненный LOB подготовка получила отказ -1016, а последующее обращение к испорченному дескриптору завершило нативный процесс. Исходник этой ветки освобождает внутренний locator, не обнуляя его. Поэтому здесь нет обещания безопасного усечения или восстановления после такого отказа: создайте новый пустой ресурс, затем замените значение SQL. До переноса проверяйте назначенный приложением путь, доступность, размер и формат. У общего API официальный отказ — false, но политику обработки нативных сбоев конкретной старой сборки нельзя свести к повторному вызову на том же ресурсе.
cubrid_lob2_closeОсвободить самостоятельно созданный дескриптор LOB в правильный момент.
PECL CUBRID >= 8.4.1; проверена версия 11.2.0.0002. · cubrid
cubrid_lob2_close(resource $lob_identifier): boolПосле сохранения вложения приложению больше не нужен созданный дескриптор. Его закрытие освобождает локальный ресурс доступа; не удаляет строку, не отменяет транзакцию и не удаляет пользовательский исходный файл.
| Параметр | Что передать |
|---|---|
| lob_identifier | Дескриптор new либо результата согласно официальному API. В проверенной реализации для дескрипторов из результата действует дополнительный порядок освобождения, описанный ниже. |
Результат: true при успешном закрытии, false при отказе. После закрытия ресурс не используется повторно; PHP-переменную обнуляют. Повторный вызов не является безопасной проверкой «уже закрыт».
При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c171Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте строгий тип результата: нулевой размер и пустое содержимое не обязательно означают ошибку.
Применение 1: гарантированно закрыть самостоятельно созданный LOB
$lob = cubrid_lob2_new($db, 'BLOB');
if ($lob === false) { throw new RuntimeException('Нет LOB'); }
try {
// write/import → bind → execute; запрос освобождается после execute.
} finally {
$ok = cubrid_lob2_close($lob);
$lob = null;
if ($ok !== true) { throw new RuntimeException('LOB не закрыт'); }
}Полный проект вложения закрывает запрос до созданного LOB, затем подтверждает запись и читает её заново. Импорт поставки демонстрирует успешный перенос в новый LOB. Общий finally не гарантирует восстановления после описанного нативного сбоя import: повреждённый дескриптор этой версии нельзя считать пригодным к повторной работе. Пример с try/finally показывает обычную обязанность освобождения, а политика записи/отката остаётся отдельной.
Применение 2: закончить чтение LOB из результата
// $row получена cubrid_fetch_array($request, CUBRID_ASSOC | CUBRID_LOB).
// Сначала прочитайте/экспортируйте $row['body'] полностью.
// Для native 11.2.0.0002 не закрывайте этот ресурс вручную,
// пока запрос хранит его внутреннюю ссылку.
cubrid_close_request($request);
$request = null;
unset($row); // удаляются последние PHP-ссылки на полученный LOB
// После этого ресурс больше не читают и lob2_close не вызывают.В исходнике 11.2.0.0002 запрос хранит внутренние указатели на выданные LOB. Ранний lob2_close освобождает структуру, которую следующий fetch или close_request ещё может затронуть. Обратный вызов lob2_close после close_request тоже не является решением: ресурс уже отсоединён от соединения. Поэтому чтение LOB и проекты этой главы сохраняют ссылки до освобождения запроса, затем снимают все ссылки через unset/null. Это оговорка конкретного драйвера поверх общего контракта документации.
Нельзя вернуть fetched-LOB из функции, которая в finally уже закрыла запрос, и читать его позже. Результат функции должен быть готовым файлом/метаданными либо иметь явно переданное владение открытым запросом. Отдельно освобождаются соединение, запрос, LOB и локальный файл: одно close не заменяет остальные операции.
cubrid_lob2_sizeУзнать размер и отличить существующее пустое вложение от отказа.
PECL CUBRID >= 8.4.1; проверена версия 11.2.0.0002. · cubrid
cubrid_lob2_size(resource $lob_identifier): int|falseПеред выдачей вложения полезно показать его размер и проверить разумный предел. Счётчик относится к содержимому LOB, а не к длине дескриптора, названию файла или общему размеру базы.
| Параметр | Что передать |
|---|---|
| lob_identifier | Живой LOB2, созданный new или полученный из результата с CUBRID_LOB. |
Результат: Размер как int; false при ошибке. 0 — корректное пустое содержимое. SQL NULL вообще не является LOB-ресурсом, size для него не вызывают.
При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c171Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте строгий тип результата: нулевой размер и пустое содержимое не обязательно означают ошибку.
Применение 1: размер файла после записи
$size = cubrid_lob2_size($lob);
if ($size === false) { throw new RuntimeException('Размер недоступен'); }
if ($size > 1024 * 1024) { throw new RuntimeException('Превышен предел вложения'); }
echo $size, ' байт';Файл измерения сравнивает размер с длиной исходных данных и с файлом, выгруженным уже из SQL-строки. Для UTF-8 в инструкции размер — байты сохранённого текста; strlen также считает байты, а не буквы.
Применение 2: правильная подпись пустого вложения
if ($payload === null) {
$label = 'Файл не приложен';
} else {
$n = cubrid_lob2_size($payload);
if ($n === false) { throw new RuntimeException('Нет размера'); }
$label = $n === 0 ? 'Приложен пустой файл' : 'Файл: '.$n.' байт';
}Экран состояний не смешивает SQL NULL, нулевой размер и ошибку. Условие if (!$n) ошибочно объединит 0 и false. Ограничение размера не заменяет проверку типа содержимого, полномочий пользователя и допустимого имени скачивания.
PHP int ограничен платформой; большое значение нельзя без проверки считать переносимым между 32- и 64-разрядными сборками. Для протокола, сохраняющего полный размер десятичной строкой, используйте cubrid_lob2_size64. Маленькие учебные файлы не доказывают работу с многогигабайтными вложениями.
cubrid_lob2_size64Получить размер десятичной строкой без сужения до PHP int.
PECL CUBRID >= 8.4.1; проверена версия 11.2.0.0002. · cubrid
cubrid_lob2_size64(resource $lob_identifier): string|falseВ JSON-квитанции размер большого файла лучше хранить без неявного сужения до платформенного целого. Эта версия возвращает десятичное представление размера строкой.
| Параметр | Что передать |
|---|---|
| lob_identifier | Ресурс new либо живой ресурс из результата. Как и size, функция не принимает SQL NULL или имя файла. |
Результат: Строка десятичных цифр, например "0" или "16384"; false при ошибке. Строка "0" — успешный нулевой размер. Документация рекомендует функцию для размеров, превышающих диапазон int.
При отказе сразу сохраните cubrid_error_code(), cubrid_error_code_facility() и cubrid_error_msg(), пока следующий вызов не изменил диагностику. Полный проект использует c171Capture для перехвата предупреждения и сохранения признаков отказа. Проверяйте строгий тип результата: нулевой размер и пустое содержимое не обязательно означают ошибку.
Применение 1: квитанция скачиваемого файла
$decimalSize = cubrid_lob2_size64($lob);
if ($decimalSize === false) { throw new RuntimeException('Размер не получен'); }
if (!preg_match('/^(0|[1-9][0-9]*)$/D', $decimalSize)) {
throw new RuntimeException('Неожиданный формат размера');
}
$receipt = ['bytes_decimal' => $decimalSize];
echo json_encode($receipt); // число сохраняется именно строкойВ результате файла измерений сохраняются оба результата и их типы. Не преобразуйте decimalSize во float ради удобства: большие целые теряют точность. На маленьком файле одинаковые цифры size и size64 ещё не означают одинаковые PHP-типы.
Применение 2: сопоставить текст с сохранённой длиной
$sizeText = cubrid_lob2_size64($instructions);
if ($sizeText === false) { throw new RuntimeException('Нет длины инструкции'); }
// Для этого заранее ограниченного малого текста strlen безопасен.
$sameLength = $sizeText === (string) strlen($utf8Text);Инструкция UTF-8 сопоставляет длину и SHA-256, а состояния файла сохраняют "0" отдельно от SQL NULL. Для большого смещения читайте cubrid_lob2_seek64 и cubrid_lob2_tell64: размер и текущая позиция — разные значения.
Обычное сравнение строк по алфавиту не является общим сравнением целых: "100" окажется перед "20". Для неотрицательных нормализованных десятичных чисел сначала сравнивают количество цифр, затем одинаковые по длине строки; либо используют точную арифметику. В этой главе лимиты малы и проверяются через size, а size64 сохраняется как строковая квитанция.
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


