PHP: сохранить и выгрузить файл в ORACLE
Пишу на скорую руку очередную заметку в формате Nota Bene. Столкнулся по работе с задачей сохранять загружаемые через html-форму файлы в базе данных Oracle через хранимую процедуру. Прежде я имел дело с полями BLOB в оракле, но при этом обработка производилась внутри Java-класса, и там использовался несколько иной подход. По своему обыкновению думал, что гугл мне выдаст миллион ссылок на вопрос «php oracle blob«. так оно и было, но…
все, что попадалось, имело отношение именно к php-реализации. Однако нигде не описывалось, как должна выглядеть сама хранимая процедура. В результате долго топтался на месте, но все же нашел способ. Сейчас изложу его тут, вдруг кому пригодится (хотя бы мне в будушем:)
И так, имеем следующие запчасти.
Таблица для хранения файлов:
CREATE TABLE GEN.GEN_TRADE_FILES ( FILE_ID NUMBER NOT NULL, FILE_NAME VARCHAR2(256 BYTE) NOT NULL, FILE_SIZE NUMBER NOT NULL, CRC32 NUMBER NOT NULL, FILE_DATA BLOB, CREATION_DATE DATE NOT NULL, CREATED_BY NUMBER NOT NULL, LAST_UPDATE_DATE DATE NOT NULL, LAST_UPDATED_BY NUMBER NOT NULL ); CREATE SEQUENCE GEN.GEN_TRADE_FILES_S START WITH 1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 20 NOCYCLE NOORDER;
Здесь
GEN — это моя схема в OracleDB, у кого-то скорее всего будет что-то свое
GEN_TRADE_FILES — название таблички в конвенции имен моего проекта
GEN.GEN_TRADE_FILES_S — генератор последовательности.
Думаю, расшифровывать назначение полей таблицы смысла не имеет. Если какие-то поля кажутся лишними, можно повыкидывать, смысл не изменится.
Следующая запчасть — хранимая процедура.
PROCEDURE store_file ( x_file_name VARCHAR2, x_file_size NUMBER, x_crc32 CHAR, x_user_id NUMBER, x_data OUT BLOB, x_file_id OUT NUMBER ) IS BEGIN SELECT GEN.GEN_TRADE_FILES_S.NEXTVAL INTO x_file_id FROM dual; INSERT INTO GEN.GEN_TRADE_FILES ( FILE_ID, FILE_NAME, FILE_SIZE, CRC32, FILE_DATA, CREATION_DATE, CREATED_BY, LAST_UPDATE_DATE, LAST_UPDATED_BY ) VALUES ( x_file_id, x_file_name, x_file_size, x_crc32, EMPTY_BLOB(), SYSDATE, x_user_id, SYSDATE, x_user_id ) returning FILE_DATA INTO x_data; END;
Важный момент в данном подходе, что при вставке строки в BLOB-поле вставляется пустой блоб, а в выходной параметр x_data возвращается дескриптор LOB-объекта. В последствии в php-коде легким движением руки наши данные занесутся в эту область (немного криво написал, но суть, думаю, донес).
Далее php-запчасти (мой упрощенный вариант).
upload.php:
<?php function get_connect() { return oci_connect($user, $pass);// тут не буду подробно расписывать процедуру коннекта } function save_file($file) { $conn = db_connect(); if (!$conn) return 'error connecting db'; //Gen_Trade_Pkg - так называется пакет в моем проекте $stid = oci_parse($conn, 'begin Gen_Trade_Pkg.store_file(:p1, :p2, :p3, :p4, :p5, :p6); end;'); if (!$stid) { $e = oci_error($stid); return 'statement parse error: '.$e['message']; } $file_id = -1; $user_id = 1024; //my stub user_id $filedata = file_get_contents($file['tmp_name']); $crc32 = crc32($filedata); $filesize = filesize($file['tmp_name']); $clob = oci_new_descriptor($conn, OCI_D_LOB); if (!$clob) {return 'could not get oci descriptior';} oci_bind_by_name($stid, ':p1', $file['name'], 256); oci_bind_by_name($stid, ':p2', $filesize, 40); oci_bind_by_name($stid, ':p3', $crc32); oci_bind_by_name($stid, ':p4', $user_id, 40); oci_bind_by_name($stid, ':p5', $clob, -1, OCI_B_BLOB); oci_bind_by_name($stid, ':p6', $file_id, 40); if (!oci_execute($stid)) {$e=oci_error($stid);return 'error: '.$e['message'];} $clob->save($filedata); oci_free_statement($stid); return 'file successfully stored. file_id='.$file_id; } ?> <html> <head> <title>upload file and store in Oracle database table via PHP</title> <body> <br/> <?php if ($_POST['savebtn']) echo (save_file($_FILES['upload_file'])); else { ?> <form id="uploadForm" name="uploadForm" method="post" enctype="multipart/form-data"> <input type="hidden" name="MAX_FILE_SIZE" value="100000" /> Файл:<br/> <input type="file" name="upload_file" style='height:21px;border: 1px solid #d0d0d0;'/></br> <input name='savebtn' type="submit" value="Save" /> </form> <?php } ?> </body>
Теперь скачивание файла.
PROCEDURE get_file ( x_file_id NUMBER, x_file_name OUT VARCHAR2, x_file_size OUT NUMBER, x_crc32 OUT NUMBER, x_data OUT BLOB ) IS BEGIN SELECT FILE_NAME, FILE_SIZE, CRC32, FILE_DATA INTO x_file_name, x_file_size, x_crc32, x_data FROM GEN.GEN_TRADE_FILES WHERE file_id = x_file_id; END;
download.php:
<?php function get_error($msg) { return '<html><body>'.$msg.'</body></html>'; } function get_connect() { return oci_connect($user, $pass);// тут не буду подробно расписывать процедуру коннекта } $file_id = $_GET['file_id']; if (!$file_id) die(get_error('file_id is not specified')); $file_id = intval($file_id); error_log("trying to get file $file_id"); $conn = get_connect(); $sql = "BEGIN GEN_TRADE_PKG.get_file(:p1, :p2, :p3, :p4, :p5); END;"; $stmt = oci_parse($conn , $sql); if (!$stmt) { $e = oci_error($stid); $last_sql_error = $e['message']; error_log('parse error_occured: ' . $last_sql_error); die(get_error($last_sql_error)); } $filename = ''; $filesize = ''; $crc32 = ''; $objClob = oci_new_descriptor($conn, OCI_D_LOB); oci_bind_by_name($stmt, ':p1', $file_id, 40); oci_bind_by_name($stmt, ':p2', $filename, 256); oci_bind_by_name($stmt, ':p3', $filesize, 40); oci_bind_by_name($stmt, ':p4', $crc32, 40); oci_bind_by_name($stmt, ':p5', $objClob, -1, OCI_B_BLOB); // тут можно сделать проверку контрольной суммы crc32 $res = oci_execute($stmt, OCI_DEFAULT); if (!$res) {$e=oci_error($stmt);$last_sql_error=$e['message'];error_log('oci_error:'.$last_sql_error); die(get_error($last_sql_error));} $filedata = $objClob->load(); $objClob->free(); oci_free_statement($stmt); header('Content-Type: application/octet-stream'); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Content-Type: application/download; charset=Windows-1251"); header("Content-Transfer-Encoding: binary"); header('Accept-Ranges: bytes'); header('Content-Length: ' . $filesize); header('Content-disposition: attachment; filename=' . $filename); echo $filedata;
Не могу понять » $stid = oci_parse($conn, ‘begin Gen_Trade_Pkg.store_file(:p1, :p2, :p3, :p4, :p5, :p6); end;’);»
Что означает «Gen_Trade_Pkg» и что мне сделать чтоб и у меня появился такой пакет???
Ну это не сложно))
Пакеты в оракле — это типа модули с программным кодом, грубо говоря, набор процедур, функций и каких-нибудь переменных. У пакета есть спецификация и тело. В В спецификации содержатся описания процедур, в теле, собственно, реализация. Создать его не сложно:
CREATE OR REPLACE PACKAGE Gen_Market_Pkg
IS
PROCEDURE get_file (
x_file_id NUMBER,
x_file_name OUT VARCHAR2,
x_file_size OUT NUMBER,
x_crc32 OUT NUMBER,
x_data OUT BLOB
);
PROCEDURE store_file (
x_file_name VARCHAR2,
x_file_size NUMBER,
x_crc32 CHAR,
x_user_id NUMBER,
x_data OUT BLOB,
x_file_id OUT NUMBER
);
END Gen_Market_Pkg;
/
CREATE OR REPLACE PACKAGE BODY Gen_Market_Pkg
-- здесь реализация процедур из поста
END Gen_Market_Pkg;
/
Как я уже и говорил, Gen_Market_Pkg — это название пакета в моем проекте, у вас оно может быть любым другим.
Добрый день!
Сделал по вашей инструкции, получаю следующее
Warning: OCI-Lob::save() function.OCI-Lob-save: ORA-22990: LOB locators cannot span transactions in *** on line 240
Blob в базе пустой.
заменить:
1. oci_execute($stid) на oci_execute($stid,OCI_DEFAULT)
2. $clob->save($filedata);
на
if ($clob->save($filedata)){
oci_commit($conn);
}else{
echo «Couldn’t upload Blob\n»;
}