この章のトピックは、次のとおりです。
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
*
* Do not put 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()で表示されます。アプリケーションのすべての標準ページにロゴが表示されます。アプリケーションを再実行して、次を確認します。

データベースにイメージを保存しておくと、アプリケーション・データ全体をバックアップし、すべてのアプリケーションで共有できます。ただし、パフォーマンスについて考慮するのであれば、ロゴをディスクに書き込むキャッシュ技術を実装し、データベース・アクセスのオーバーヘッドを回避できる直接ストリームも検討します。新しいイメージがアップロードされるたびにアップロード・フォームでディスク・ファイルが再生成されます。