FileMakerにはオブジェクトフィールドとよばれる、いわゆるバイナリファイルを格納するためのフィールドが用意されている。FileMaker Pro上ではドラッグ&ドロップをはじめとしたマウス操作で簡単に画像ファイルやPDFファイルなどを格納することが可能だが、Webからこれらのフィールドを取り扱う場合はちょっと注意する必要がある。
今回からは、FileMaker Webアプリケーションを構築するにあたり必要なオブジェクトフィールドの取り扱い方・注意点を2回にわたって紹介しよう。
Webアプリからオブジェクトフィールドを操作する大前提
まず最初に注意しておかなければならないことだが、XML公開をはじめとしたカスタムWeb機能上ではオブジェクトフィールドにたいしてデータの追加や変更・削除をおこなうことはできない。
Webユーザがオブジェクトフィールドの内容を変更または追加することはできません。
(fms10_cwp_php_ja.pdf/FileMaker Server カスタム Web 公開 with PHP P.20より引用)
つまりオブジェクトフィールドを採用する以上「Webアプリケーション上からオブジェクトフィールド内の画像を変更」などといったことはまず不可能になる。PHP側でファイルアップロードの機構を実装し、PHP・バッチファイル・FileMaker ServerスケジューラからFileMakerスクリプトを起動させれば突破口が見いだせる場合もあるが、あまり得策ではないだろう。FileMaker Pro用/Webアプリケーション用で2つのUIを持つ環境では大丈夫だが、Webアプリケーションのみの場合はあらかじめ注意しておきたい。
画像やPDFファイルといったバイナリファイルをFileMakerに持たせたい場合は、オブジェクトフィールドを使用するのではなく、次のような方法が望ましいだろう。
- テキストフィールドを用意し、参照させたいファイルへのパスを格納
- 計算フィールドを用意し、1で作成したテキストフィールドをオブジェクトとして表示
そうは言ってもイチから作ったデータベースではないといった理由で、オブジェクトフィールドを使わなければならない場面がある。続いてはオブジェクトフィールドの表示方法について紹介しよう。
オブジェクトフィールドの表示方法
カスタムWeb公開においてオブジェクトフィールドの内容を取得するには、/fmi/xml/cnt/data.jpgを参照する。
URI例
/fmi/xml/cnt/data.jpg?-db=(ファイル名)&-lay=(レイアウト名)&-recid=(レコードID)&-field=(フィールド名)(1)
XML公開では、オブジェクトフィールドの内容に上記のようなURIが格納される。これを<img src=>や<a href>に格納すれば良いことになるが、このままではURIからFileMakerファイル名やレイアウト情報が容易に判別可能になっているため危険だ。そのまま使用するのではなく、image_proxy.phpおよびvignereEncryptURL()を使用する。
コード例(1) - image_proxy.php未使用。当然ながらURIはそのままsrcに格納される
$obj = '/fmi/xml/cnt/data.jpg?-db=fxphp_obj&-lay=fxphp_obj&-recid=2&-field=fo_obj_1(1)';
?>
<img src="<?php echo $obj; ?>">
コード例(2) - image_proxy.phpとvignereEncryptURLを使用。URIは難読化される
$obj = '/fmi/xml/cnt/data.jpg?-db=fxphp_obj&-lay=fxphp_obj&-recid=2&-field=fo_obj_1(1)';
?>
<img src='./image_proxy.php?FXimage=<?php echo vignereEncryptURL($obj); ?>'>
実際にFX.phpとimage_proxy.phpを使用し、オブジェクトフィールドの内容を表示してみよう。なお、ここではオブジェクトフィールドの内容はすべてJPG画像としている。
データベース情報
- ファイル名: fxphp_obj.fp7
- テーブル名: fxphp_obj
- レイアウト名: fxphp_obj
- フィールド: 画像を参照
PHPコード: fmviewobj.php
<?php
include_once('./fx/FX.php');
include_once('./fx/server_data.php');
include_once('./fx/image_proxy.php');
$data = new FX($serverIP, $webCompanionPort, $dataSourceType, $scheme);
$data->SetDBData($databaseFileName,'fxphp_obj', 1);
$data->SetDBUserPass($webUN,$webPW);
$data->SetCharacterEncoding('utf8');
$data->SetDataParamsEncoding('utf8');
$dataSet = $data->FMFind();
// URI文字列を格納
$objURI = $dataSet['data'][key($dataSet['data'])]['fo_obj_1'][0];
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>FX.php オブジェクトフィールド参照</title>
</head>
<body>
<img src="./fx/image_proxy.php?FXimage=<?php echo vignereEncryptURL($objURI); ?>">
</body>
</html>
あらかじめimage_proxy.php内の$encryptKeyをもとにURIを難読化し、image_proxy.phpにたいして難読化した文字列を渡して同ファイル内で復号化、fsockopen()でデータを取得して必要なヘッダをつけて返すといった動作だ。
オブジェクトフィールドに格納した画像ファイルが無事に表示された。実際に使う場合は念のためimage_proxy.phpの$encryptKeyを変更しておこう。
いきなり使う前に、データベース側も要検討
すでにお気づきの方もいるだろうが、この方法は1つのオブジェクトフィールドの内容を取得するのに1回の/fmi/xml/cnt/data.jpgへのリクエストが発生する。極端な例だが、たとえば1回の/fmi/xml/cnt/data.jpgへのリクエストが成功して結果が返るまでに0.5秒、一覧画面で1度に10レコードを表示、1レコードあたり3オブジェクトフィールドを表示したいとなると、表示完了までに単純計算でで0.5+(0.5*3*10)=15.5秒もかかってしまうことになる。
同時ユーザ数が多ければ多いほど、オブジェクトフィールドに格納するバイナリファイルのサイズが大きければ大きいほど、さらにパフォーマンスは悪くなる。数メガを超えるバイナリファイルが格納されたオブジェクトフィールドの連続参照はサーバにかなりの負荷がかかる。最悪の結果、WebサーバごとFileMaker Server(Web公開機能)がクラッシュしてしまう場合もあるのだ。
FileMakerのWeb公開機能をMySQLやPostgreSQLと性能面で比較した場合、残念ながら遠くおよばないだろう。しかしそこであきらめるのではなく「どうやってパフォーマンスをあげるか」がデベロッパの腕の見せどころだ。パッと思い浮かぶ代案は、いまのところ2点ある。
- サーバ側にオブジェクトフィールドの内容をキャッシュさせる。キャッシュが更新されるタイミングはレコードの「修正タイムスタンプ」をもとにする
- 最初からオブジェクトフィールドを使用しない
筆者は実装難度、また運用面の問題をふくめた上で「2」案が妥当な線だと考えている。次回ではオブジェクトフィールドから脱却することでのメリットと、オブジェクトフィールドの代案をベースにしたFileMaker webアプリケーションの実装方法を紹介しよう。