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

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

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

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

Глава 96 · После подключения CUBRID и основ BLOB/CLOB

CUBRID: прочитать прежнее LOB-вложение, выгрузить файл и выдать его пользователю

Паспорт вложения, замена файла, извлечение UTF-8 и проверенная HTTP-выдача с полным справочником прежнего LOB API.

Документ, дескриптор, файл и вывод — четыре разных понятия

В прежнем приложении к записи поставки уже привязаны файлы и инструкции. Задача сопровождения — получить выбранный документ целиком, сохранить понятный паспорт, передать файл человеку и не перепутать содержимое с результатом функции. Здесь это четыре последовательных применения, а не перечень вызовов без результата.

Старый код может обращаться к прежним LOB-функциям или удалённому GLO. Сначала нужно различить исторический контракт, наличие имени в установленном расширении и фактически работающий путь. Эти главы сохраняют объяснение прежнего API и показывают перенос полезной задачи на проверенный механизм.

Перед началом прочитайте подключение и первые запросы и BLOB, CLOB и привязку вложения к записи. В коротких фрагментах соединение $db уже открыто; полные проекты показывают собственную учебную таблицу, конкретный файл результата и очистку. Синтетический архив миграции не выдаётся за содержимое извлечённой исторической GLO-базы.

Профиль проверки: PHP 7.4.33, native cubrid 11.2.0.0002, CCI 11.2.0, сервер CUBRID 11.3.5.1274. Это отдельная среда совместимости. Статус прежних функций указан явно; успешный современный сценарий не служит доказательством запуска удалённой функции.

Действие оператораЧто связано в базеВидимый результат
Открыть паспорт измеренияid42 → body BLOB с CAL172 и числами250/500/0CSV и декодированные отсчёты
Обновить файл обменаid42 → BLOB BATCH172 с нулевыми и высокими байтами16398 байт вместо OLD COPY
Прочитать инструкциюid42 → body CLOB с тремя строками UTF-8extracted-instruction.txt
Скачать только разрешённый документid42 разрешён; id99 запрещён правилом урокаHTTP200/403 и точный файл ответа

BLOB хранит байты файла, CLOB — большое текстовое содержимое. Дескриптор — средство доступа, похожее на выданную приложению ссылку для чтения; это не сам файл и не его ID. Массив от get содержит такие дескрипторы. Аналогия не означает файловый путь: местоположение внешнего LOB-файла управляется сервером, а приложение выбирает SQL-запись и свой выходной файл.

От записи поставки к файлу пользователя
  1. Таблица: id42 → один body BLOB/CLOB
  2. get(SELECT body WHERE id42) → PHP array[0] с legacy-дескриптором
  3. size → строка байтов; export → файл ИЛИ send → output
  4. close(весь массив) → ресурс завершён; SQL-строка остаётся
  5. Для HTTP: права → готовый файл → заголовки и байты ответа

Самая маленькая полезная запись

$lobs = cubrid_lob_get($db, 'SELECT body FROM documents WHERE id=42');
if ($lobs === false || count($lobs) !== 1) { throw new RuntimeException('Нет одного документа'); }
$bytes = cubrid_lob_size($lobs[0]);
if ($bytes === false) { throw new RuntimeException('Нет размера'); }
echo $bytes, ' байт';
cubrid_lob_close($lobs);

Оператор = сохраняет результат, скобки передают аргументы, $lobs[0] читает первый элемент массива. Проверка === false отделяет ошибку от успешного значения; count проверяет, сколько дескрипторов получено. На фикстуре первого проекта вывод — «14 байт». Короткий фрагмент предполагает существующее соединение и таблицу; самостоятельный проект ниже создаёт их окружение, данные и точный результат. Исключения и освобождение ресурсов расширяются в полной версии.

Все пять функций документированы начиная с PECL CUBRID 8.3.1. Только size имеет отдельную смену типа результата в 8.4.0. Сигнатуры карточек явно включают false там, где краткая строка Manual показывает лишь успешный тип. Это справочная запись вариантов результата, а не объявление union-типа в исполняемом PHP7-коде.

В изолированной проверке PHP 7.4.33 / native cubrid 11.2.0.0002 / CUBRID 11.3.5 один BLOB прошёл get → size → export → захваченный CLI-send → close без предупреждений, с точным совпадением байтов. Наличие имени функции само по себе этого не доказывает. В исходнике PHP7 остаются подозрительные обращения к массиву как к ресурсу в get/close; успешный ограниченный случай не доказывает безопасность любых размеров, многорядных результатов и всех ошибок. Для новой разработки предпочтителен отдельно разобранный LOB2; семейства ресурсов не смешиваются.

Фикстуры создаются через LOB2, поскольку legacy-семейство этой главы предназначено для чтения/выгрузки. Это не подмена вызовов: получение, размер, экспорт, output и close в проектах ниже действительно выполняются через соответствующие legacy-функции. Данные синтетические и принадлежат только учебной таблице. Различие SQL NULL, пустого содержимого и отсутствующей строки подробнее показывает глава о сохранении LOB.

Получить паспорт выбранного вложения для оператора

Оператор выбирает документ поставки 42 и хочет получить его имя, размер и понятное содержание измерения. Паспорт CSV пригодится при передаче файла коллегам. Заголовок CAL172, нулевой байт и три числа — наш небольшой формат фикстуры, не универсальный формат измерительных приборов.

Путь данных и полученный результат
  1. id42 → SELECT только body
  2. array[0] → legacy LOB → size «14»
  3. export → CAL172 + NUL + числа 250/500/0
  4. CSV: id42, имя,14,SHA256; затем close

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

$lobs = cubrid_lob_get($db, 'SELECT body FROM documents WHERE id=42');
if ($lobs === false || count($lobs) !== 1) { throw new RuntimeException('Нет одного вложения'); }
$bytes = cubrid_lob_size($lobs[0]);
if ($bytes === false) { throw new RuntimeException('Размер недоступен'); }
echo $bytes, ' байт'; // для фикстуры проекта: 14 байт
cubrid_lob_close($lobs);

SELECT выбирает один столбец body одной строки. $lobs — PHP-массив дескрипторов; [0] выбирает первый. Размер возвращается строкой цифр, а не содержимым файла. Полный проект выгружает маленький файл, декодирует три 16-битных числа и сравнивает байты; имя calibration.bin назначено приложением, а не вычислено из дескриптора.

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

НаблюдениеРезультат
Документ42
Имяcalibration.bin
Размер, байт14
Отсчёты250; 500; 0
Самостоятельный проект, исходник и архив
Получить паспорт выбранного вложения для оператораСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

try {
    $config=c172Config();$db=c172Connect();$runtime=c172Runtime($db);

function checked172(callable $action) {
    $call=c172Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c172Failure('Действие с вложением не выполнено',$call);}
    return $call['value'];
}
function fixture172($db,array &$owned,string $label,string $type,string $payload): string {
    if (!in_array($type,['BLOB','CLOB'],true)) {throw new InvalidArgumentException('Неизвестный тип вложения');}
    $name=c172OwnName($db,$owned,'TABLE',$label);$table=c172Qualified($name);
    c172Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(80),body '.$type.')');c172Commit($db);
    // LOB2 creates our synthetic fixture. Reading below uses the actual legacy API.
    $lob=null;$request=null;
    try {
        $lob=checked172(static function() use ($db,$type) {return cubrid_lob2_new($db,$type);});
        checked172(static function() use ($lob,$payload) {return cubrid_lob2_write($lob,$payload);});
        $request=checked172(static function() use ($db,$table) {return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (42,?,?)');});
        checked172(static function() use ($request) {return cubrid_bind($request,1,'Документ поставки 42','string');});
        checked172(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,2,$lob,$type);});
        checked172(static function() use ($request) {return cubrid_execute($request);});
        if (cubrid_affected_rows($request)!==1) {throw new RuntimeException('Не создана строка документа');}
    } finally {
        c172Release($request);
        if (is_resource($lob)) {checked172(static function() use ($lob) {return cubrid_lob2_close($lob);});$lob=null;}
    }
    c172Commit($db);return $table;
}
function legacyGet172($db,string $table): array {
    // The table name comes only from our registry; id42 is a fixed lesson value.
    $lobs=checked172(static function() use ($db,$table) {return cubrid_lob_get($db,'SELECT body FROM '.$table.' WHERE id=42');});
    if (!is_array($lobs) || count($lobs)!==1 || !is_resource($lobs[0])) {throw new RuntimeException('Нужен ровно один legacy LOB');}
    return $lobs;
}
function legacySize172($lob): string {
    $size=checked172(static function() use ($lob) {return cubrid_lob_size($lob);});
    if (!is_string($size) || !preg_match('/^(0|[1-9][0-9]*)$/D',$size) || strlen($size)>7 || (int)$size>1048576) {
        throw new RuntimeException('Просмотр ограничен одним МиБ, размер должен быть десятичной строкой');
    }
    return $size;
}
function legacyClose172(&$lobs): void {
    if (is_array($lobs)) {
        $call=c172Capture(static function() use ($lobs) {return cubrid_lob_close($lobs);});
        $lobs=null;
        if ($call['value']!==true || $call['warning']) {c172Failure('Legacy LOB не освобождены',$call);}
    }
}
function writeArtifact172(string $name,string $data): void {
    if (basename($name)!==$name || file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}

    $payload="CAL172\0".pack('v*',250,500,0)."\n";
    $table=fixture172($db,$owned,'report','BLOB',$payload);
    $lobs=null;
    try {
        $lobs=legacyGet172($db,$table);$size=legacySize172($lobs[0]);
        $exported=checked172(static function() use ($db,$lobs) {return cubrid_lob_export($db,$lobs[0],__DIR__.'/report-source.bin');});
        $actual=file_get_contents(__DIR__.'/report-source.bin');
        if (!is_string($actual) || strlen($actual)!==(int)$size) {throw new RuntimeException('Не получены все байты документа');}
        $decoded=unpack('vfirst/vsecond/vthird',substr($actual,7,6));
        $samples=implode('; ',array_values($decoded));$digest=hash('sha256',$actual);
    } finally {legacyClose172($lobs);}
    c172Commit($db);
    $csv="id,filename,bytes,sha256\n42,calibration.bin,".$size.','.$digest."\n";
    writeArtifact172('attachment-report.csv',$csv);
    $rows=[['Документ',42],['Имя','calibration.bin'],['Размер, байт',(int)$size],['Отсчёты',$samples]];
    $checks=['one_selected_document'=>true,'size_is_decimal_string'=>is_string($size),
        'reported_size_matches'=>strlen($actual)===(int)$size,'header_preserved'=>substr($actual,0,7)==="CAL172\0",
        'payload_exact'=>$actual===$payload,'samples_decoded'=>$samples==='250; 500; 0',
        'csv_written'=>file_get_contents(__DIR__.'/attachment-report.csv')===$csv,'legacy_closed'=>$lobs===null];
    $observations=['api'=>'legacy cubrid_lob_*','size'=>$size,'size_type'=>gettype($size),
        'header_hex'=>bin2hex(substr($actual,0,7)),'samples'=>$decoded,'sha256'=>$digest,'exported'=>$exported];

} finally {

    if (is_resource($db)) {try {$removed=c172Cleanup($db,$owned);} finally {c172Close($db);}}
}

c172Report('cub172-attachment-report','Получить паспорт выбранного вложения для оператора',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Документ — 42
Имя — calibration.bin
Размер, байт — 14
Отсчёты — 250; 500; 0

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

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

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

Паспорт не загружает все вложения таблицы. Для большого документа сначала нужен SQL-список метаданных, а содержимое — только по выбранному разрешённому ID. Здесь файл заранее ограничен одним МиБ и целиком читается для наглядного декодирования; это не обещание постоянной памяти при любом размере. Значение SHA проверяет байты, но само по себе не подтверждает автора документа.

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

Добавьте вторую запись документа. Как сохранить правило «показывать только выбранный документ», не делать SELECT * и не сортировать все файлы в PHP?

Разбор

Оставьте WHERE id=<проверенный ID>, выбирайте отдельно метаданные для экрана и один body для открытия. В новом коде параметризуйте SELECT через prepare/bind и LOB2; не подставляйте необработанную строку из запроса в legacy SQL.

Обновить локальную копию файла поставки и проверить её целиком

В папке обмена лежит устаревшая копия документа. Нужно заменить только назначенный приложением файл и проверить, что получено всё содержимое, включая нулевые и высокие байты. Для наглядности проект сам создаёт старую копию; чужие файлы он не использует.

Путь данных и полученный результат
  1. Старый собственный файл OLD COPY
  2. SELECT body WHERE id42 → один legacy LOB
  3. export в тот же путь → 16398 байт
  4. Размер + начало/конец + полный SHA → готовая копия

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

$ok = cubrid_lob_export($db, $lobs[0], __DIR__.'/delivery.bin');
if ($ok !== true) { throw new RuntimeException('Файл не выгружен'); }
echo filesize(__DIR__.'/delivery.bin'), ' байт';

Третий аргумент — путь на машине PHP. Export сам записывает файл, а возвращает boolean; передавать его результат в file_put_contents как содержимое нельзя. Полный проект сначала создаёт собственный OLD COPY, затем проверяет замену по длине, началу, концу и SHA-256.

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

НаблюдениеРезультат
Файлexported-attachment.bin
Размер, байт16398
Прежняя копия замененада
Побайтная проверкада
Самостоятельный проект, исходник и архив
Обновить локальную копию файла поставки и проверить её целикомСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

try {
    $config=c172Config();$db=c172Connect();$runtime=c172Runtime($db);

function checked172(callable $action) {
    $call=c172Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c172Failure('Действие с вложением не выполнено',$call);}
    return $call['value'];
}
function fixture172($db,array &$owned,string $label,string $type,string $payload): string {
    if (!in_array($type,['BLOB','CLOB'],true)) {throw new InvalidArgumentException('Неизвестный тип вложения');}
    $name=c172OwnName($db,$owned,'TABLE',$label);$table=c172Qualified($name);
    c172Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(80),body '.$type.')');c172Commit($db);
    // LOB2 creates our synthetic fixture. Reading below uses the actual legacy API.
    $lob=null;$request=null;
    try {
        $lob=checked172(static function() use ($db,$type) {return cubrid_lob2_new($db,$type);});
        checked172(static function() use ($lob,$payload) {return cubrid_lob2_write($lob,$payload);});
        $request=checked172(static function() use ($db,$table) {return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (42,?,?)');});
        checked172(static function() use ($request) {return cubrid_bind($request,1,'Документ поставки 42','string');});
        checked172(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,2,$lob,$type);});
        checked172(static function() use ($request) {return cubrid_execute($request);});
        if (cubrid_affected_rows($request)!==1) {throw new RuntimeException('Не создана строка документа');}
    } finally {
        c172Release($request);
        if (is_resource($lob)) {checked172(static function() use ($lob) {return cubrid_lob2_close($lob);});$lob=null;}
    }
    c172Commit($db);return $table;
}
function legacyGet172($db,string $table): array {
    // The table name comes only from our registry; id42 is a fixed lesson value.
    $lobs=checked172(static function() use ($db,$table) {return cubrid_lob_get($db,'SELECT body FROM '.$table.' WHERE id=42');});
    if (!is_array($lobs) || count($lobs)!==1 || !is_resource($lobs[0])) {throw new RuntimeException('Нужен ровно один legacy LOB');}
    return $lobs;
}
function legacySize172($lob): string {
    $size=checked172(static function() use ($lob) {return cubrid_lob_size($lob);});
    if (!is_string($size) || !preg_match('/^(0|[1-9][0-9]*)$/D',$size) || strlen($size)>7 || (int)$size>1048576) {
        throw new RuntimeException('Просмотр ограничен одним МиБ, размер должен быть десятичной строкой');
    }
    return $size;
}
function legacyClose172(&$lobs): void {
    if (is_array($lobs)) {
        $call=c172Capture(static function() use ($lobs) {return cubrid_lob_close($lobs);});
        $lobs=null;
        if ($call['value']!==true || $call['warning']) {c172Failure('Legacy LOB не освобождены',$call);}
    }
}
function writeArtifact172(string $name,string $data): void {
    if (basename($name)!==$name || file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}

    $payload="BATCH172\n".str_repeat("\x00\x7f\x80\xff",4096)."\nEND\n";
    $table=fixture172($db,$owned,'export_file','BLOB',$payload);
    $path=__DIR__.'/exported-attachment.bin';writeArtifact172('exported-attachment.bin',"OLD COPY\n");
    $before=hash_file('sha256',$path);$lobs=null;
    try {
        $lobs=legacyGet172($db,$table);$size=legacySize172($lobs[0]);
        $ok=checked172(static function() use ($db,$lobs,$path) {return cubrid_lob_export($db,$lobs[0],$path);});
    } finally {legacyClose172($lobs);}
    c172Commit($db);clearstatcache(true,$path);
    $bytes=filesize($path);$digest=hash_file('sha256',$path);
    $file=fopen($path,'rb');if (!is_resource($file)) {throw new RuntimeException('Нет выгруженного файла');}
    try {$prefix=fread($file,13);fseek($file,-5,SEEK_END);$ending=fread($file,5);} finally {fclose($file);}
    $rows=[['Файл','exported-attachment.bin'],['Размер, байт',$bytes],['Прежняя копия заменена',$before!==$digest],['Побайтная проверка',$digest===hash('sha256',$payload)]];
    $checks=['export_succeeded'=>$ok===true,'file_size_matches'=>$bytes===strlen($payload)&&$size===(string)$bytes,
        'old_copy_replaced'=>$before!==$digest,'full_hash_matches'=>$digest===hash('sha256',$payload),
        'binary_prefix_preserved'=>$prefix==="BATCH172\n\x00\x7f\x80\xff",'ending_preserved'=>$ending==="\nEND\n",'legacy_closed'=>$lobs===null];
    $observations=['legacy_export'=>true,'existing_destination'=>true,'before_sha256'=>$before,'after_sha256'=>$digest,
        'prefix_hex'=>bin2hex($prefix),'ending_hex'=>bin2hex($ending),'bytes'=>$bytes];

} finally {

    if (is_resource($db)) {try {$removed=c172Cleanup($db,$owned);} finally {c172Close($db);}}
}

c172Report('cub172-file-export','Обновить локальную копию файла поставки и проверить её целиком',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Файл — exported-attachment.bin
Размер, байт — 16398
Прежняя копия заменена — да
Побайтная проверка — да

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

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

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

Legacy export открывает назначение на запись: существующий файл заменяется. Здесь это намеренный контракт. Для рабочего сервиса безопаснее новая временная копия, проверка, затем согласованная замена; атомарность такого протокола отдельно зависит от файловой системы. Размер/хеш проверяются после завершения экспорта. Этот опыт не переносится автоматически на lob2_export, у которого другая политика существующего назначения.

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

Нужно сохранять предыдущую копию до проверки новой. Что поменять в последовательности?

Разбор

Выделите новое собственное имя, экспортируйте туда, сравните длину и SHA, и только после успеха заменяйте рабочую копию. При неуспехе оставьте прежнюю. Имя из HTTP-запроса не должно превращаться в произвольный локальный путь.

Получить UTF-8-инструкцию из прежнего CLOB без лишнего вывода

Старый обработчик использует cubrid_lob_send и печатает инструкцию прямо в ответ. Нужно понять, что функция выводит, сохранить русский текст без добавленного «1» и отделить данные от результата вызова. Маленький текст здесь перехватывается буфером PHP; следующим проектом будет настоящий HTTP.

Путь данных и полученный результат
  1. CLOB id42 → русская инструкция с O’Reilly
  2. lob_send → output; return отдельно true
  3. Ограниченный буфер → три строки и байтовый размер
  4. Точный extracted-instruction.txt без boolean в конце

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

ob_start();
$ok = cubrid_lob_send($db, $lobs[0]);
$text = ob_get_clean();
if ($ok !== true) { throw new RuntimeException('Нет текста'); }
// $text — выведенные байты; $ok — успешность, не содержимое.

ob_start временно собирает вывод, ob_get_clean возвращает его и закрывает буфер. Переменная $ok получает true/false. Конструкция echo cubrid_lob_send(...) сначала выводит документ, а затем может допечатать «1» — это уже другие байты. В проекте буфер очищается даже при исключении.

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

НаблюдениеРезультат
ЗаголовокИнструкция №42
Первый шаг1. Проверить O'Reilly.
Второй шаг2. Сохранить акт ✓.
Размер UTF-8, байт93
Самостоятельный проект, исходник и архив
Получить UTF-8-инструкцию из прежнего CLOB без лишнего выводаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];

try {
    $config=c172Config();$db=c172Connect();$runtime=c172Runtime($db);

function checked172(callable $action) {
    $call=c172Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c172Failure('Действие с вложением не выполнено',$call);}
    return $call['value'];
}
function fixture172($db,array &$owned,string $label,string $type,string $payload): string {
    if (!in_array($type,['BLOB','CLOB'],true)) {throw new InvalidArgumentException('Неизвестный тип вложения');}
    $name=c172OwnName($db,$owned,'TABLE',$label);$table=c172Qualified($name);
    c172Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(80),body '.$type.')');c172Commit($db);
    // LOB2 creates our synthetic fixture. Reading below uses the actual legacy API.
    $lob=null;$request=null;
    try {
        $lob=checked172(static function() use ($db,$type) {return cubrid_lob2_new($db,$type);});
        checked172(static function() use ($lob,$payload) {return cubrid_lob2_write($lob,$payload);});
        $request=checked172(static function() use ($db,$table) {return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (42,?,?)');});
        checked172(static function() use ($request) {return cubrid_bind($request,1,'Документ поставки 42','string');});
        checked172(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,2,$lob,$type);});
        checked172(static function() use ($request) {return cubrid_execute($request);});
        if (cubrid_affected_rows($request)!==1) {throw new RuntimeException('Не создана строка документа');}
    } finally {
        c172Release($request);
        if (is_resource($lob)) {checked172(static function() use ($lob) {return cubrid_lob2_close($lob);});$lob=null;}
    }
    c172Commit($db);return $table;
}
function legacyGet172($db,string $table): array {
    // The table name comes only from our registry; id42 is a fixed lesson value.
    $lobs=checked172(static function() use ($db,$table) {return cubrid_lob_get($db,'SELECT body FROM '.$table.' WHERE id=42');});
    if (!is_array($lobs) || count($lobs)!==1 || !is_resource($lobs[0])) {throw new RuntimeException('Нужен ровно один legacy LOB');}
    return $lobs;
}
function legacySize172($lob): string {
    $size=checked172(static function() use ($lob) {return cubrid_lob_size($lob);});
    if (!is_string($size) || !preg_match('/^(0|[1-9][0-9]*)$/D',$size) || strlen($size)>7 || (int)$size>1048576) {
        throw new RuntimeException('Просмотр ограничен одним МиБ, размер должен быть десятичной строкой');
    }
    return $size;
}
function legacyClose172(&$lobs): void {
    if (is_array($lobs)) {
        $call=c172Capture(static function() use ($lobs) {return cubrid_lob_close($lobs);});
        $lobs=null;
        if ($call['value']!==true || $call['warning']) {c172Failure('Legacy LOB не освобождены',$call);}
    }
}
function writeArtifact172(string $name,string $data): void {
    if (basename($name)!==$name || file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}

    $payload="Инструкция №42\n1. Проверить O'Reilly.\n2. Сохранить акт ✓.\n";
    $table=fixture172($db,$owned,'text_extract','CLOB',$payload);$lobs=null;
    try {
        $lobs=legacyGet172($db,$table);$size=legacySize172($lobs[0]);
        ob_start();
        try {$call=c172Capture(static function() use ($db,$lobs) {return cubrid_lob_send($db,$lobs[0]);});$received=ob_get_contents();}
        finally {ob_end_clean();}
        if ($call['value']!==true || $call['warning'] || !is_string($received)) {c172Failure('Текст не передан в output',$call);}
    } finally {legacyClose172($lobs);}
    c172Commit($db);writeArtifact172('extracted-instruction.txt',$received);
    $lines=explode("\n",rtrim($received,"\n"));
    $rows=[['Заголовок',$lines[0]],['Первый шаг',$lines[1]],['Второй шаг',$lines[2]],['Размер UTF-8, байт',strlen($received)]];
    $checks=['send_returned_true'=>$call['value']===true,'text_exact'=>$received===$payload,'byte_size_matches'=>$size===(string)strlen($received),
        'three_lines'=>count($lines)===3,'utf8_valid'=>preg_match('//u',$received)===1,'apostrophe_preserved'=>strpos($received,"O'Reilly")!==false,
        'file_exact'=>file_get_contents(__DIR__.'/extracted-instruction.txt')===$payload,'legacy_closed'=>$lobs===null];
    $observations=['transport'=>'captured PHP output in CLI, not HTTP','return_type'=>gettype($call['value']),
        'size'=>$size,'utf8_lines'=>$lines,'sha256'=>hash('sha256',$received),'captured_bytes'=>strlen($received)];

} finally {

    if (is_resource($db)) {try {$removed=c172Cleanup($db,$owned);} finally {c172Close($db);}}
}

c172Report('cub172-text-extraction','Получить UTF-8-инструкцию из прежнего CLOB без лишнего вывода',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Заголовок — Инструкция №42
Первый шаг — 1. Проверить O'Reilly.
Второй шаг — 2. Сохранить акт ✓.
Размер UTF-8, байт — 93

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

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

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

Это проверка CLI-вывода функции, не доказательство HTTP-заголовков. Буфер содержит весь маленький документ; для больших файлов так делать не нужно. CLOB, соединение и текст должны быть согласованы по кодировке. Смена MIME не исправляет испорченные байты и не превращает произвольное содержимое в изображение.

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

Почему strlen текста инструкции больше количества видимых букв? Как показать размер скачивания?

Разбор

UTF-8 кодирует часть символов несколькими байтами. Content-Length и размер файла используют байты. Для количества символов нужна отдельная строковая операция с явной кодировкой; нельзя подставить её результат вместо длины HTTP-тела.

Выдать выбранное вложение реальным HTTP-ответом и отказать чужому ID

После переноса обработчика нужно убедиться, что посетитель получает именно файл, а не HTML-страницу, число успешности или документ другого ID. Родитель выгружает LOB в свой проверенный файл, затем локальный PHP-worker выдаёт его настоящим HTTP. Скачиваемый download-response.bin записан из полученного ответа, а не скопирован из исходной фикстуры.

Путь данных и полученный результат
  1. До подключения CUBRID запускается один локальный worker
  2. Родитель: legacy export id42 → файл + размер/SHA
  3. GET id42 → 200 +14 точных байтов; GET id99 →403
  4. Worker остановлен → удаление собственной SQL-таблицы

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

// После проверки доступа и существования подготовленного файла:
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="calibration-42.bin"');
header('Content-Length: '.filesize($path));
readfile($path); // Не echo readfile($path): return — число, а не файл.

header задаёт метаданные HTTP-ответа до его тела. readfile выводит содержимое. Полный проект разделяет получение файла из базы и его выдачу: worker не знает пароль БД и вообще не вызывает CUBRID. Только id42 разрешён учебным правилом; id99 получает403 до чтения файла. Это правило не выдаётся за вход пользователя или полноценную систему прав.

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

НаблюдениеРезультат
Разрешённый документ: HTTP200
Получено байт14
Другой ID: HTTP403
HTTP-worker остановленда
Самостоятельный проект, исходник и архив
Выдать выбранное вложение реальным HTTP-ответом и отказать чужому IDСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=null;$owned=$removed=$rows=$checks=$observations=[];
require __DIR__.'/http-lab.php';
startHttp172();
try {
    $config=c172Config();$db=c172Connect();$runtime=c172Runtime($db);

function checked172(callable $action) {
    $call=c172Capture($action);
    if ($call['value']===false || $call['value']===null || $call['warning']) {c172Failure('Действие с вложением не выполнено',$call);}
    return $call['value'];
}
function fixture172($db,array &$owned,string $label,string $type,string $payload): string {
    if (!in_array($type,['BLOB','CLOB'],true)) {throw new InvalidArgumentException('Неизвестный тип вложения');}
    $name=c172OwnName($db,$owned,'TABLE',$label);$table=c172Qualified($name);
    c172Ddl($db,'CREATE TABLE '.$table.' (id INT PRIMARY KEY,title VARCHAR(80),body '.$type.')');c172Commit($db);
    // LOB2 creates our synthetic fixture. Reading below uses the actual legacy API.
    $lob=null;$request=null;
    try {
        $lob=checked172(static function() use ($db,$type) {return cubrid_lob2_new($db,$type);});
        checked172(static function() use ($lob,$payload) {return cubrid_lob2_write($lob,$payload);});
        $request=checked172(static function() use ($db,$table) {return cubrid_prepare($db,'INSERT INTO '.$table.' (id,title,body) VALUES (42,?,?)');});
        checked172(static function() use ($request) {return cubrid_bind($request,1,'Документ поставки 42','string');});
        checked172(static function() use ($request,$lob,$type) {return cubrid_lob2_bind($request,2,$lob,$type);});
        checked172(static function() use ($request) {return cubrid_execute($request);});
        if (cubrid_affected_rows($request)!==1) {throw new RuntimeException('Не создана строка документа');}
    } finally {
        c172Release($request);
        if (is_resource($lob)) {checked172(static function() use ($lob) {return cubrid_lob2_close($lob);});$lob=null;}
    }
    c172Commit($db);return $table;
}
function legacyGet172($db,string $table): array {
    // The table name comes only from our registry; id42 is a fixed lesson value.
    $lobs=checked172(static function() use ($db,$table) {return cubrid_lob_get($db,'SELECT body FROM '.$table.' WHERE id=42');});
    if (!is_array($lobs) || count($lobs)!==1 || !is_resource($lobs[0])) {throw new RuntimeException('Нужен ровно один legacy LOB');}
    return $lobs;
}
function legacySize172($lob): string {
    $size=checked172(static function() use ($lob) {return cubrid_lob_size($lob);});
    if (!is_string($size) || !preg_match('/^(0|[1-9][0-9]*)$/D',$size) || strlen($size)>7 || (int)$size>1048576) {
        throw new RuntimeException('Просмотр ограничен одним МиБ, размер должен быть десятичной строкой');
    }
    return $size;
}
function legacyClose172(&$lobs): void {
    if (is_array($lobs)) {
        $call=c172Capture(static function() use ($lobs) {return cubrid_lob_close($lobs);});
        $lobs=null;
        if ($call['value']!==true || $call['warning']) {c172Failure('Legacy LOB не освобождены',$call);}
    }
}
function writeArtifact172(string $name,string $data): void {
    if (basename($name)!==$name || file_put_contents(__DIR__.'/'.$name,$data)!==strlen($data)) {throw new RuntimeException('Файл результата не записан');}
}

    $payload="CHECK172\0\x01\x02\x03\xff\n";
    $table=fixture172($db,$owned,'download','BLOB',$payload);$lobs=null;
    try {
        $lobs=legacyGet172($db,$table);$size=legacySize172($lobs[0]);
        checked172(static function() use ($db,$lobs) {return cubrid_lob_export($db,$lobs[0],__DIR__.'/download-source.bin');});
    } finally {legacyClose172($lobs);}
    c172Commit($db);
    $actual=hash_file('sha256',__DIR__.'/download-source.bin');
    if ($actual!==hash('sha256',$payload)) {throw new RuntimeException('Файл для выдачи не совпал с документом');}
    saveHttpProof172('download-handoff.json',['record_id'=>42,'file'=>'download-source.bin','bytes'=>(int)$size,'sha256'=>$actual]);
    $allowed=getHttp172(42,1);$denied=getHttp172(99,2);$worker=stopHttp172();
    writeArtifact172('download-response.bin',$allowed['body']);
    $headers=$allowed['proof']['headers'];
    $rows=[['Разрешённый документ: HTTP',$allowed['proof']['status']],['Получено байт',strlen($allowed['body'])],
        ['Другой ID: HTTP',$denied['proof']['status']],['HTTP-worker остановлен',$worker['stopped']]];
    $checks=['allowed_200'=>$allowed['proof']['status']===200,'body_exact'=>$allowed['body']===$payload,
        'length_header_exact'=>($headers['content-length']??null)===(string)strlen($payload),
        'binary_content_type'=>($headers['content-type']??null)==='application/octet-stream',
        'attachment_name'=>($headers['content-disposition']??null)==='attachment; filename="calibration-42.bin"',
        'nosniff'=>($headers['x-content-type-options']??null)==='nosniff','other_id_denied'=>$denied['proof']['status']===403&&$denied['body']==="denied\n",
        'two_http_requests'=>$worker['http_requests']===2,'worker_stopped'=>$worker['stopped']&&!$worker['final_running']&&!$worker['forced_stop'],
        'saved_response_exact'=>file_get_contents(__DIR__.'/download-response.bin')===$payload,'legacy_closed'=>$lobs===null];
    $observations=['delivery'=>'legacy export to file, then core PHP HTTP file delivery',
        'http'=>['responses'=>[$allowed['proof'],$denied['proof']],'worker_stopped'=>$worker['stopped'],'http_requests'=>$worker['http_requests']],
        'body_sha256'=>hash('sha256',$allowed['body']),'body_hex'=>bin2hex($allowed['body'])];

} finally {
if (function_exists('stopHttp172')) {stopHttp172();}
    if (is_resource($db)) {try {$removed=c172Cleanup($db,$owned);} finally {c172Close($db);}}
}

c172Report('cub172-download-response','Выдать выбранное вложение реальным HTTP-ответом и отказать чужому ID',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Разрешённый документ: HTTP — 200
Получено байт — 14
Другой ID: HTTP — 403
HTTP-worker остановлен — да

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

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

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

Два реальных GET выполняются только внутри изолированной loopback namespace. Проверка готовности использует TCP без дополнительного HTTP. Это проверка маршрута export→file→HTTP, а не HTTP-запуск cubrid_lob_send. На сайте проверяйте права текущего пользователя на документ, назначайте имя загрузки сервером и не добавляйте журнал/HTML к телу файла. Предварительная выгрузка требует места на диске; потоковая выдача без файла — другой маршрут с собственным временем жизни ресурса.

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

Добавьте разрешение только пользователю-владельцу документа. Где должно стоять это решение?

Разбор

После аутентификации получите ID текущего пользователя из доверенной сессии и проверьте связь с документом до чтения содержимого. Нельзя считать знание ID или имени файла правом доступа. В этой лаборатории проверяется только порядок двух ветвей, а не настоящая авторизация.

Пять функций по действию с документом

ФункцияЗадача
cubrid_lob_get()Получить legacy-дескрипторы выбранных BLOB/CLOB
cubrid_lob_size()Узнать размер содержимого строкой цифр
cubrid_lob_export()Сохранить выбранный LOB в локальный файл
cubrid_lob_send()Передать содержимое LOB в вывод PHP
cubrid_lob_close()Освободить весь массив legacy LOB

Следующий шаг: перенести старые документы без потери смысла

В обсуждении потока, который всё равно исчерпал память весь поток собирался в строку. Наш маленький CLI-перехват показан именно как ограниченное наблюдение; большой файл не следует незаметно пропускать через такой буфер.

Вопрос о неправильном MIME и испорченном изображении и вопрос о записи boolean вместо содержимого PDF относятся к Firebird, а не CUBRID. Из них взяты реальные задачи, не драйверные решения: отделить формат байтов от заголовка, output от return, локальный файл от ответа браузера. Нормы CUBRID сверены с его документацией и исходником.

Дальше — перенос удалённого GLO через явный манифест и бизнес-ключи. Для нового приложения выбирайте создание и привязку LOB2 и порционное чтение и выгрузку. HTTP-права и безопасные имена файлов связаны с безопасностью приложения и устройством HTTP-ответа.

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

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

cubrid_lob_getПолучить legacy-дескрипторы выбранных BLOB/CLOB

PECL CUBRID >= 8.3.1; size возвращает string начиная с 8.4.0; профиль проверки 11.2.0.0002. · cubrid

cubrid_lob_get(resource $conn_identifier, string $sql): array|false

При сопровождении старого приложения нужно получить документ по его записи, а затем узнать размер, выгрузить файл или отправить содержимое. Функция выполняет SELECT и возвращает массив дескрипторов LOB; сам массив ещё не является байтами документов.

ПараметрЧто передать
conn_identifierОткрытое соединение CUBRID. Оно используется для выполнения SQL.
sqlСтрока SELECT, выбирающая ровно один столбец типа BLOB либо CLOB. Ограничьте строки WHERE/LIMIT; отдельного массива bind-параметров у функции нет.

Результат: Массив LOB-ресурсов при успехе; false при отказе. Дескрипторы предназначены для legacy size/export/send и затем передаются всем массивом в close. Пустой массив нельзя индексировать как $lobs[0].

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

Применение 1: выбранный файл измерений

$lobs = cubrid_lob_get($db, 'SELECT body FROM documents WHERE id=42');
if ($lobs === false || count($lobs) !== 1) {
    throw new RuntimeException('Ожидался один документ');
}
try {
    $bytes = cubrid_lob_size($lobs[0]);
    if ($bytes === false) { throw new RuntimeException('Нет размера'); }
    echo $bytes;
} finally { cubrid_lob_close($lobs); }

Паспорт вложения развивает этот вызов: выбран один документ, проверяется его формат и получается CSV. Индекс [0] — положение в PHP-массиве, а не первичный ключ документа. $db, имя таблицы и id в фрагменте уже заданы приложением; полный проект создаёт только собственную таблицу.

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

$lobs = cubrid_lob_get($db, 'SELECT instruction FROM products WHERE id=7');
if ($lobs === false || count($lobs) !== 1) { throw new RuntimeException('Нет инструкции'); }
try {
    $ok = cubrid_lob_export($db, $lobs[0], __DIR__.'/instruction.txt');
    if (!$ok) { throw new RuntimeException('Инструкция не выгружена'); }
} finally { cubrid_lob_close($lobs); }

Извлечение CLOB показывает ту же модель для русского текста. Нельзя расширить SELECT до id,title,body ради удобства: этот API допускает только один LOB-столбец. Метаданные экрана выбирайте отдельным небольшим запросом либо используйте LOB2-чтение из обычной строки результата.

В проверенном исходнике SQL NULL попадает в ветку отказа get; это не обещание массива с null для каждого отсутствующего документа. Сначала определите отсутствие содержимого обычным SQL, а пустой BLOB отличайте по размеру. Ошибку SQL/соединения не выдавайте за отсутствие документа. Произвольный пользовательский текст нельзя конкатенировать в sql: для нового обработчика выберите подготовленный SELECT и LOB2. Официальная обязанность завершения — cubrid_lob_close().

В изолированной проверке PHP 7.4.33 / native cubrid 11.2.0.0002 / CUBRID 11.3.5 один BLOB прошёл get → size → export → захваченный CLI-send → close без предупреждений, с точным совпадением байтов. Наличие имени функции само по себе этого не доказывает. В исходнике PHP7 остаются подозрительные обращения к массиву как к ресурсу в get/close; успешный ограниченный случай не доказывает безопасность любых размеров, многорядных результатов и всех ошибок. Для новой разработки предпочтителен отдельно разобранный LOB2; семейства ресурсов не смешиваются.

cubrid_lob_sizeУзнать размер содержимого строкой цифр

PECL CUBRID >= 8.3.1; size возвращает string начиная с 8.4.0; профиль проверки 11.2.0.0002. · cubrid

cubrid_lob_size(resource $lob_identifier): string|false

Подпись «14 байт» помогает оператору понять, какой файл он собирается получить. Размер измеряет содержимое BLOB/CLOB, а не число ресурсов, букв в UTF-8 или длину имени файла.

ПараметрЧто передать
lob_identifierОдин legacy LOB из массива cubrid_lob_get. Не весь массив, не SQL NULL, не путь и не ресурс LOB2.

Результат: Десятичная строка размера при успехе, false при отказе. Начиная с версии 8.4.0 тип изменён с int на string. Строка "0" — корректный нулевой размер; if (!$size) ошибочно смешает его с отказом.

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

Применение 1: квитанция без потери числовой точности

$decimal = cubrid_lob_size($lobs[0]);
if ($decimal === false) { throw new RuntimeException('Нет размера'); }
$receipt = ['bytes_decimal' => $decimal];
echo json_encode($receipt); // например {"bytes_decimal":"14"}

Паспорт документа сохраняет размер строкой в наблюдениях, а для заранее ограниченной маленькой фикстуры преобразует его в int для декодирования. Не переводите большие значения во float: можно потерять точные цифры. Ресурс сначала получают через cubrid_lob_get().

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

$decimal = cubrid_lob_size($lobs[0]);
if ($decimal === false || !preg_match('/^(0|[1-9][0-9]*)$/D', $decimal)) {
    throw new RuntimeException('Нет корректного размера');
}
// Наш интерфейс допускает не более 1 МиБ, поэтому сначала отсеиваем длинные числа.
if (strlen($decimal) > 7 || (int)$decimal > 1048576) {
    throw new RuntimeException('Скачайте документ вместо предпросмотра');
}

UTF-8-инструкция использует именно байты перед перехватом малого текста. Размер совпадает со strlen сохранённого текста, но не обязан совпадать с числом символов. Для большого размера не сравнивайте строки только лексикографически: "100" и "20" имеют иной алфавитный порядок. У LOB2 есть cubrid_lob2_size64() с собственным ресурсным контрактом.

Один успешный size не доказывает, что файл полностью доставлен: после выгрузки сопоставляйте длину и, когда нужна точность, полный SHA-256. Маленькие лабораторные файлы не являются проверкой многогигабайтных LOB. SQL NULL не передают в size вообще.

cubrid_lob_exportСохранить выбранный LOB в локальный файл

PECL CUBRID >= 8.3.1; size возвращает string начиная с 8.4.0; профиль проверки 11.2.0.0002. · cubrid

cubrid_lob_export(resource $conn_identifier, resource $lob_identifier, string $path_name): bool

Если потребителю нужен локальный файл, лучше явно отделить запись файла от вывода в HTTP. Export получает содержимое legacy LOB и сохраняет его по заданному приложением пути.

ПараметрЧто передать
conn_identifierСоединение, через которое доступно вложение.
lob_identifierОдин LOB, предварительно полученный cubrid_lob_get.
path_nameПуть файла на машине, где работает PHP. У процесса должны быть права записи; это не адрес скачивания посетителя.

Результат: true при успехе, false при ошибке. Возвращаемый boolean не является данными файла. Исходник проверенной версии открывает назначение в режиме w, поэтому существующее содержимое может быть заменено.

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

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

$ok = cubrid_lob_export($db, $lobs[0], __DIR__.'/latest-delivery.bin');
if ($ok !== true) { throw new RuntimeException('Не обновлена копия'); }
clearstatcache(true, __DIR__.'/latest-delivery.bin');
$bytes = filesize(__DIR__.'/latest-delivery.bin');

Полный проект обновления предварительно записывает OLD COPY в свой файл и проверяет точную замену всей длины, включая начало и конец. Это осознанное изменение назначенного файла; передача пути посетителем не допускается. Результат экспорта проверяется фактическими байтами, а не только boolean.

Применение 2: подготовить файл перед HTTP-выдачей

$path = __DIR__.'/document-ready.bin';
if (file_exists($path)) { throw new RuntimeException('Нужно новое назначение'); }
if (!cubrid_lob_export($db, $lobs[0], $path)) {
    throw new RuntimeException('Документ не подготовлен');
}
// Проверить размер/хеш. Затем отдельный обработчик выдаёт готовый файл.

Реальная локальная выдача проверяет именно маршрут legacy export → файл → HTTP. Для сервиса с параллельными запросами одно фиксированное имя непригодно: нужен собственный временный каталог/имя, проверка и публикация готового файла. Проверка file_exists в коротком фрагменте не является атомарной защитой от гонок.

Функция требует сначала cubrid_lob_get() и после завершения работы — cubrid_lob_close(). Запись на диск не означает отправку посетителю. Напротив, cubrid_lob_send() пишет в output, а не возвращает строку для file_put_contents. Политику существующего файла нельзя переносить между legacy export и cubrid_lob2_export() без чтения их отдельных контрактов. Частичный файловый результат при отказе не следует выдавать как готовый документ.

cubrid_lob_sendПередать содержимое LOB в вывод PHP

PECL CUBRID >= 8.3.1; size возвращает string начиная с 8.4.0; профиль проверки 11.2.0.0002. · cubrid

cubrid_lob_send(resource $conn_identifier, resource $lob_identifier): bool

В старом обработчике документ может передаваться прямо в вывод PHP. Send выполняет эту передачу; код вызова отдельно решает, куда этот вывод попадёт и какие заголовки уже отправлены.

ПараметрЧто передать
conn_identifierСоединение CUBRID.
lob_identifierОдин legacy-дескриптор из cubrid_lob_get.

Результат: true при успешной передаче, false при отказе. Содержимое пишется в output; возвращаемое значение не является строкой документа. В CLI output — стандартный вывод, а в web-SAPI — тело ответа.

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

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

ob_start();
try {
    $ok = cubrid_lob_send($db, $lobs[0]);
    $text = ob_get_contents();
} finally { ob_end_clean(); }
if ($ok !== true) { throw new RuntimeException('Текст не передан'); }
file_put_contents(__DIR__.'/instruction.txt', $text);

Извлечение инструкции реально вызывает send в CLI и сравнивает русский текст побайтно. Здесь небольшой контролируемый буфер выбран для наблюдения. Он собирает весь вывод и не решает проблему памяти при больших файлах. Нельзя писать echo cubrid_lob_send(...): допечатанное true превращается в «1» после документа.

Применение 2: прямое тело HTTP в прежнем обработчике

// Контекстный web-фрагмент: доступ к документу уже проверен.
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="document.bin"');
header('X-Content-Type-Options: nosniff');
if (cubrid_lob_send($db, $lobs[0]) !== true) {
    // Ошибка фиксируется в журнале; HTML нельзя дописывать к файлу.
}

Этот фрагмент описывает контракт прямого legacy-send, но не выдаётся за выполненный HTTP-пример. Полный HTTP-проект использует другую явно показанную цепочку: legacy export → готовый файл → readfile. Заголовки и два реальных ответа проверяются там. Send не назначает сам имя скачивания, не проверяет права и не определяет MIME по произвольным байтам.

Нужен предварительный cubrid_lob_get(), затем cubrid_lob_close() после передачи. Если часть тела уже отправлена, обычная HTML-страница ошибки испортит ответ; заранее проверяйте метаданные и готовность источника. Для нового управляемого потока смотрите cubrid_lob2_read(). Не копируйте output целиком в PHP-строку, когда задача требует ограниченного потребления памяти.

cubrid_lob_closeОсвободить весь массив legacy LOB

PECL CUBRID >= 8.3.1; size возвращает string начиная с 8.4.0; профиль проверки 11.2.0.0002. · cubrid

cubrid_lob_close(array $lob_identifier_array): bool

После работы со старым вложением нужно завершить владение всеми выданными дескрипторами. Close принимает именно массив результата get; освобождение этого массива не является SQL-удалением документа.

ПараметрЧто передать
lob_identifier_arrayВесь массив LOB-идентификаторов, возвращённый cubrid_lob_get. Не отдельный элемент, не результат LOB2 и не произвольный массив чисел.

Результат: true при успешном закрытии, false при отказе. Закрытые дескрипторы повторно не читаются; снимите оставшиеся PHP-ссылки. Повторный close не используется как проверка состояния.

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

Применение 1: завершить выгрузку независимо от ошибки файла

$lobs = cubrid_lob_get($db, 'SELECT body FROM documents WHERE id=42');
if ($lobs === false || count($lobs) !== 1) { throw new RuntimeException('Не получен один документ'); }
try {
    if (!cubrid_lob_export($db, $lobs[0], __DIR__.'/delivery.bin')) {
        throw new RuntimeException('Не создан файл');
    }
} finally {
    $closed = cubrid_lob_close($lobs);
    $lobs = null;
    if ($closed !== true) { throw new RuntimeException('Ресурсы не закрыты'); }
}

Обновление копии выполняет этот порядок над одним действительным LOB. finally описывает обычную обязанность завершения; он не гарантирует восстановления после нативного повреждения памяти. Диагностику первичной ошибки важно сохранить до нового вызова, чтобы отказ close не подменил объяснение исходного сбоя.

Применение 2: отказаться от слишком большого предпросмотра

// $lobs уже получен успешным get, а размер проверен отдельным шагом.
try {
    if ($previewAllowed) {
        cubrid_lob_send($db, $lobs[0]);
    } else {
        // Выбран путь скачивания: предпросмотр сейчас не открывается.
    }
} finally {
    cubrid_lob_close($lobs);
    $lobs = null;
}

Пользователь может отказаться от просмотра после показа паспорта файла; ресурс всё равно требуется завершить. Для небольшой инструкции сначала заканчивается захват output, затем close освобождает LOB. Общее правило не зависит от того, показаны данные или выбран другой маршрут.

Close не заменяет commit/rollback, закрытие соединения и закрытие локального файла. Он также не удаляет внешнее хранилище CUBRID. У cubrid_lob2_close() другой параметр и особые правила fetched-ресурсов проверенного драйвера; нельзя механически заменить одно имя функции другим. Исторический PHP Manual требует close всего массива, даже если программа использовала только один элемент.

В изолированной проверке PHP 7.4.33 / native cubrid 11.2.0.0002 / CUBRID 11.3.5 один BLOB прошёл get → size → export → захваченный CLI-send → close без предупреждений, с точным совпадением байтов. Наличие имени функции само по себе этого не доказывает. В исходнике PHP7 остаются подозрительные обращения к массиву как к ресурсу в get/close; успешный ограниченный случай не доказывает безопасность любых размеров, многорядных результатов и всех ошибок. Для новой разработки предпочтителен отдельно разобранный LOB2; семейства ресурсов не смешиваются.

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

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

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

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

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