Home > Coding > PHP: сохранить и выгрузить файл в ORACLE

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;
Categories: Coding Tags: , , ,
  1. Андрей
    September 5th, 2011 at 10:34 | #1

    Не могу понять ” $stid = oci_parse($conn, ‘begin Gen_Trade_Pkg.store_file(:p1, :p2, :p3, :p4, :p5, :p6); end;’);”
    Что означает “Gen_Trade_Pkg” и что мне сделать чтоб и у меня появился такой пакет???

  2. September 5th, 2011 at 10:53 | #2

    Ну это не сложно))
    Пакеты в оракле – это типа модули с программным кодом, грубо говоря, набор процедур, функций и каких-нибудь переменных. У пакета есть спецификация и тело. В В спецификации содержатся описания процедур, в теле, собственно, реализация. Создать его не сложно:
    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 – это название пакета в моем проекте, у вас оно может быть любым другим.

  3. March 21st, 2013 at 11:22 | #3

    Добрый день!
    Сделал по вашей инструкции, получаю следующее

    Warning: OCI-Lob::save() function.OCI-Lob-save: ORA-22990: LOB locators cannot span transactions in *** on line 240

    Blob в базе пустой.

  4. SKR
    May 16th, 2014 at 12:23 | #4

    заменить:

    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”;
    }