この章のトピックは、次のとおりです。
PHP OCI8拡張モジュールを使用するとLOB
データを簡単に操作できます。AnyCoアプリケーションではBLOB
を使用して会社のロゴを格納し、これを各Webページに表示します。
SQL*Plusで、ロゴを格納する表PICTURES
を作成します。
CREATE TABLE pictures (id NUMBER, pic BLOB); CREATE SEQUENCE pictures_seq; CREATE TRIGGER pictures_trig BEFORE INSERT ON pictures FOR EACH ROW BEGIN :NEW.id := pictures_seq.NEXTVAL; END; /
新規のPHPファイルac_logo_upload.php
を作成します。初期のコンテンツは次のようになります。
<?php /** * ac_logo_upload.php: Upload a new company logo * @package Logo */ session_start(); require('ac_db.inc.php'); require('ac_equip.inc.php'); $sess = new \Equipment\Session; $sess->getSession(); if (!isset($sess->username) ││ empty($sess->username) ││ !$sess->isPrivilegedUser()) { header('Location: index.php'); exit; } $page = new \Equipment\Page; $page->printHeader("AnyCo Corp. Upload Logo"); $page->printMenu($sess->username, $sess->isPrivilegedUser()); printcontent($sess); $page->printFooter(); // Functions ?>
printcontent()
関数を追加します。
/** * Print the main body of the page * * @param Session $sess */ function printcontent($sess) { echo "<div id='content'>"; if (!isset($_FILES['lob_upload'])) { printform(); } else { $blobdata = file_get_contents($_FILES['lob_upload']['tmp_name']); if (!$blobdata) { // N.b. this test could be enhanced to confirm the image is a JPEG printform(); } else { $db = new \Oracle\Db("Equipment", $sess->username); $sql = 'INSERT INTO pictures (pic) VALUES(EMPTY_BLOB()) RETURNING pic INTO :blobbind'; $db->insertBlob($sql, 'Insert Logo BLOB', 'blobbind', $blobdata); echo '<p>New logo was uploaded</p>'; } } echo "</div>"; // content }
これで、HTMLフォームとフォームハンドラという2つの部分からなる一般的な構造になりました。INSERT
文は、バインド値を使用してBLOB
を表しています。新規Db
クラスのinsertBlob()
は、BLOB
データをバインド変数に関連付けて、レコードをコミットします。アップロードされたイメージがPICTURES
表に追加されます。
フォーム関数printform()
を追加して、ac_logo_upload.php
を完成させます。
/** * Print the HTML form to upload the image * * Adding CSRF protection is an exercise for the reader */ function printform() { echo <<<EOF Upload new company logo: <form action="ac_logo_upload.php" method="POST" enctype="multipart/form-data"> <div> Image file name: <input type="file" name="lob_upload"> <input type="submit" value="Upload" </div> <form EOF; }
注意: 'EOF;'トークンは行の先頭に配置し、その後に空白が含まれないようにします。 |
このフォームが発行されると、printcontent()
で見られるように、PHP Webサーバーが一時ファイル$_FILES['lob_upload']['tmp_name']
にアップロードされたBLOB
データにアクセスできるようになります。
PHPにはファイルの場所や上限サイズを制御する様々なオプションがあります。詳細は、PHPのドキュメントを参照してください。AnyCoアプリケーションではデフォルト値を使用します。
ac_db.inc.php
を編集し、insertBlob()
メソッドをDb
クラスに追加します。
/** * Insert a BLOB * * $sql = 'INSERT INTO BTAB (BLOBID, BLOBDATA) * VALUES(:MYBLOBID, EMPTY_BLOB()) RETURNING BLOBDATA INTO :BLOBDATA' * Db::insertblob($sql, 'do insert for X', myblobid', * $blobdata, array(array(":p", $p, -1))); * * $sql = 'UPDATE MYBTAB SET blobdata = EMPTY_BLOB() * RETURNING blobdata INTO :blobdata' * Db::insertblob($sql, 'do insert for X', 'blobdata', $blobdata); * * @param string $sql An INSERT or UPDATE statement that returns a LOB locator * @param string $action Action text for End-to-End Application Tracing * @param string $blobbindname Bind variable name of the BLOB in the statement * @param string $blob BLOB data to be inserted * @param array $otherbindvars Bind variables. An array of tuples */ public function insertBlob($sql, $action, $blobbindname, $blob, $otherbindvars = array()) { $this->stid = oci_parse($this->conn, $sql); $dlob = oci_new_descriptor($this->conn, OCI_D_LOB); oci_bind_by_name($this->stid, $blobbindname, $dlob, -1, OCI_B_BLOB); foreach ($otherbindvars as $bv) { // oci_bind_by_name(resource, bv_name, php_variable, length) oci_bind_by_name($this->stid, $bv[0], $bv[1], $bv[2]); } oci_set_action($this->conn, $action); oci_execute($this->stid, OCI_NO_AUTO_COMMIT); if ($dlob->save($blob)) { oci_commit($this->conn); } }
insertBlob()
メソッドは、最後のオプション・パラメータを通常のバインド変数として受け取ります。ac_logo_upload.php
のprintcontent()
でコールするときには、これは使用されません。
第6章「REF CURSORを使用した備品レコードの表示」で説明したREF CURSOR
のバインドと同様に、BLOB
は特別な型としてバインドされます。PHP OCI8にはOCI_B_CLOB
という定数もあり、これはCLOB
のバインドに使用できます。LOB
ディスクリプタはPHP OCI8のOCI-Lob
クラスのインスタンスで、データのアップロードと読取りに使用できる様々なメソッドがあります。oci_execute()
がSQL INSERT
文で処理されるとき、OCI_NO_AUTO_COMMIT
フラグが使用されます。これは、$dlob->save()
メソッドでデータが挿入されるまで、データベース・トランザクションをオープンにしておく必要があるためです。最終的に、明示的なoci_commit()
でBLOB
をコミットします。
ブラウザでAnyCoアプリケーションを実行し、Administratorにログインします。左側のメニューで「Upload Logo」リンクをクリックします。コンピュータ上でJPEGイメージを探して選択します。この章の次の項に示すとおり、タイトルの入ったページ・ヘッダー内にイメージを表示するため、15~20ピクセルの高さのイメージを選択します。
「アップロード」ボタンをクリックします。
ロゴの表示は、概念的には前の章で説明したグラフ・イメージの表示とほぼ同じです。ただし、BLOB
はすでにJPEG形式になっているため、GD拡張モジュールは必要ありません。
新規のPHPファイルac_logo_img.php
を作成します。ファイルには、次が含まれています。
<?php /** * ac_logo_img.php: Create a JPEG image of the company logo * * Don't have any text or white space before the "<?php" tag because it will * be incorporated into the image stream and corrupt the picture. * * @package Logo */ session_start(); require('ac_db.inc.php'); require('ac_equip.inc.php'); $sess = new \Equipment\Session; $sess->getSession(); if (isset($sess->username) && !empty($sess->username)) { $username = $sess->username; } else { // index.php during normal execution, or other external caller $username = "unknown-logo"; } $db = new \Oracle\Db("Equipment", $username); $sql = 'SELECT pic FROM pictures WHERE id = (SELECT MAX(id) FROM pictures)'; $img = $db->fetchOneLob($sql, "Get Logo", "pic"); header("Content-type: image/jpg"); echo $img; ?>
これは最新のロゴを問い合せて、JPEGストリームとして返します。イメージが破損しているようであれば、header()
関数とecho
関数のコールをコメント・アウトし、スクリプトによってテキストや空白が送出されていないかどうかを確認します。
ユーザー名チェックは、前の項で使用したものと異なります。Webユーザー名を認識する前のログイン・ページを含め、ロゴはすべてのページに表示されます。Db
はエンドツーエンドの追跡用にユーザー名を受け取るため、ac_logo_img.php
ではブートストラップ・ユーザー名unknown-logo
を使用します。
ac_db.inc.php
を編集し、fetchOneLob()
メソッドをDb
クラスに追加します。
/** * Runs a query that fetches a LOB column * @param string $sql A query that include a LOB column in the select list * @param string $action Action text for End-to-End Application Tracing * @param string $lobcolname The column name of the LOB in the query * @param array $bindvars Bind variables. An array of tuples * @return string The LOB data */ public function fetchOneLob($sql, $action, $lobcolname, $bindvars = array()) { $col = strtoupper($lobcolname); $this->stid = oci_parse($this->conn, $sql); foreach ($bindvars as $bv) { // oci_bind_by_name(resource, bv_name, php_variable, length) oci_bind_by_name($this->stid, $bv[0], $bv[1], $bv[2]); } oci_set_action($this->conn, $action); oci_execute($this->stid); $row = oci_fetch_array($this->stid, OCI_RETURN_NULLS); $lob = null; if (is_object($row[$col])) { $lob = $row[$col]->load(); $row[$col]->free(); } $this->stid = null; return($lob); }
oci_fetch_array()
オプションにOCI_RETURN_LOBS
フラグを含めて、データをPHP文字列として返すことを指定することもできます。このコードでは、そうではなくロケータとして列が返されます。これは、ロケータに対する操作方法を示しています。ここではload()
を使用してすべてのデータを読み取り、free()
メソッドを使用してリソースを解放しています。大きなデータを扱うアプリケーションでは、ロケータread()
メソッドを使用してLOB
をチャンクで処理すると、メモリーを効率よく使用して大きなデータ・ストリームを処理できます。
OCI_B_BLOB
型を使用してバインドすることからBLOB
にしか使用できないinsertBlob()
とは異なり、fetchOneLob()
はBLOB
データとCLOB
データの両方に使用できます。
アプリケーションで複数のイメージ(またはイメージのチャンク)を、次のようにループで順次処理するとします。
while (($img = $db->fetchOneLob($sql, "Get Logo", "pic")) != null ) { dosomething($img); }
この場合、ループの下で$img
を明示的に設定解除するとPHPのピークのメモリー使用を少なくすることができます。
dosomething($img); $unset($img);
これにより、現在の$img
に割り当てられているメモリーが、次のイメージ・データ・ストリームで再利用されます。そうしなかった場合、PHPによって2件目のイメージが構築され、$img
に割当てできる状態になるまで、元のイメージ・メモリーは解放されません。この最適化はAnyCoアプリケーションでは必要ありません。
アップロードされたロゴをAnyCoアプリケーションに表示するには、ac_equip.inc.php
を編集し、LOGO_URL
定義を非コメント化します。
define('LOGO_URL', 'http://localhost/ac_logo_img.php');
URLが使用環境に対して適切であることを確認します。
ロゴはPage::printHeader()
で表示されます。アプリケーションのすべての標準ページにロゴが表示されます。アプリケーションを再実行して、次を確認します。
データベースにイメージを保存しておくと、アプリケーション・データ全体をバックアップし、すべてのアプリケーションで共有できます。ただし、パフォーマンスについて考慮するのであれば、ロゴをディスクに書き込むキャッシュ技術を実装し、データベース・アクセスのオーバーヘッドを回避できる直接ストリームも検討します。新しいイメージがアップロードされるたびにアップロード・フォームでディスク・ファイルが再生成されます。