Webアプリケーションを実装するにあたり、コード中でもっとも登場回数がおおくなると思われるレコード検索。実用的なサンプルをもとに、FX.phpでの実装例を紹介する。
レコードの検索方法 - FX.php
まずはFX.phpをベースに、検索と一覧画面を実装する。FileMaker API for PHPでできることを極力FX.phpでも実現するということで、いくつか代替案をもちいて実装をおこなった。このサンプルは「コントローラ」「検索+一覧画面」「エラー画面」の3ファイルから構成している。
コントローラ - fx_find.php
<?php
include_once('../fx/FX.php');
include_once('../fx/FX_Fuzzy_Debugger.php');
// 文字列エスケープ用関数
function h($string)
{
return htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
}
// 一度に取得する件数を指定
$max = 10;
// 先頭から除外するレコード数を取得
$skip = ( 0 < (int)$_GET['skip']) ? (int)$_GET['skip'] : 0;
// ソート指定を取得
$order = ( 'descend' === $_GET['sortOrder'] ) ? 'ascend' : 'descend';
// 1. new FXでインスタンスを作成、接続先を指定
$data = new FX('localhost', 80, 'FMPro9', 'http');
// 2. SetDBDataで使用するDB、レイアウト、一度に取得するレコード件数、レスポンスレイアウトを指定
$data->SetDBData('fmapi_test','fmapi_list', $max);
$data->SetDBUserPass('admin','admin');
$data->SetCharacterEncoding('utf8');
$data->SetDataParamsEncoding('utf8');
// 3. AddDBParamまたはAddParamArrayで検索したい内容を指定
if (!empty($_GET['ft_serial']))
{
$data->AddDBParam('ft_serial', $_GET['ft_serial']);
}
if (!empty($_GET['ft_text']))
{
$data->AddDBParam('ft_text', '*' . $_GET['ft_text'] . '*' );
}
if (!empty($_GET['ft_date']))
{
$data->AddDBParam('ft_date', date('m/d/Y' ,strtotime($_GET['ft_date'])));
}
if (!empty($_GET['ft_num']))
{
$data->AddDBParam('ft_num', $_GET['ft_num']);
}
// 4. AddSortParamでソート順を指定
switch($_GET['sortField'])
{
case 'ft_serial':
case 'ft_text':
case 'ft_date':
case 'ft_num':
case 'ft_repetitionText':
$data->AddSortParam($_GET['sortField'] , $order);
break;
default:
// ソートフィールドが指定されなかった場合は、ソートをおこなわない
break;
}
// 5. FMSkipRecordsで先頭から除外するレコード件数を指定
$data->FMSkipRecords($skip);
// 6. レイアウトにポータルが配置されている場合は、
// AddDBParam('-relatedsets.filter')/AddDBParam('-relatedsets.max')でフィルタリングの設定をおこなう
// 7. FMFindまたはDoFXAction('find')でリクエスト発行
// 8. 結果を配列で取得、$resultSetに格納
$resultSet = $data->FMFind();
// $resultSet = $data->DoFXAction('find'); でも可
// 9. $resultSet['errorCode']の値でエラー判定
if ( 0 === (int)$resultSet['errorCode'] )
{
include_once('./fx_find_view.php');
}
else
{
// エラー処理
include_once('./fx_find_error.php');
}
?>
検索+一覧画面 - fx_find_view.php
<!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">
<meta http-equiv="Content-Style-Type" content="text/css">
<title>FileMaker x FX.php 検索</title>
<style type="text/css">
<!--
table th
{
color: #000000;
background-color: #cccccc;
}
-->
</style>
</head>
<body>
<form action="./fx_find.php" method="get">
<table border="1">
<tr>
<th>ft_serial</th>
<th>ft_text</th>
<th>ft_date</th>
<th>ft_num</th>
</tr>
<tr>
<td><input type="text" name="ft_serial" value="<?php echo h($_GET['ft_serial']); ?>"></td>
<td><input type="text" name="ft_text" value="<?php echo h($_GET['ft_text']); ?>"></td>
<td><input type="text" name="ft_date" value="<?php echo h($_GET['ft_date']); ?>"></td>
<td><input type="text" name="ft_num" value="<?php echo h($_GET['ft_num']); ?>"></td>
</tr>
<tr>
<td colspan="4" align="center"><input type="submit" value="検索"></td>
</tr>
</table>
</form>
<br>
<table border="1">
<tr>
<?php
foreach( $data->lastFields as $fieldValue )
{
// タイプが「テキスト」「数字」「日付」「オブジェクト」のみを表示
switch($fieldValue['type'])
{
case 'TEXT':
case 'NUMBER':
case 'DATE':
echo '<th><a href="./fx_find.php?ft_text=' . h($_GET['ft_text']).
'&sortField=' . $fieldValue['name'] .
'&sortOrder=' . $order . '">' . $fieldValue['name'] . '</a></th>';
break;
case 'CONTAINER':
echo '<th>' . $fieldValue['name'] . '</th>';
break;
default:
break;
}
}
unset($fieldValue);
?>
</tr>
<?php
// 10. $resultSet['data']からフィールド内容を取得
foreach( $resultSet['data'] as $recordValue )
{
?>
<tr>
<?php
foreach( $data->lastFields as $fieldValue )
{
if (!empty($recordValue[$fieldValue['name']][0]))
{
switch($fieldValue['type'])
{
case 'TEXT':
echo '<td>' . h($recordValue[$fieldValue['name']][0]) . '</td>';
break;
case 'NUMBER':
echo '<td align="right">' . h(number_format($recordValue[$fieldValue['name']][0])) . '</td>';
break;
case 'DATE':
echo '<td align="center">' . date('Y/m/d', strtotime($recordValue[$fieldValue['name']][0])) . '</td>';
break;
case 'CONTAINER':
echo '<td align="center"><img src="' . h($recordValue[$fieldValue['name']][0]) . '"></td>';
break;
default:
break;
}
}
else
{
echo '<td> </td>';
}
}
?>
</tr>
<?php
}
unset($recordValue);
?>
</table>
<?php
echo '<p>' . number_format($resultSet['foundCount']) . '件中、' .
number_format($skip+1) . '~' . number_format($max) . '件を表示</p>';
echo '<ul>';
if (!empty($resultSet['linkPrevious']))
{
echo '<li><a href="' . h($resultSet['linkPrevious']) . '">前の' . $max . '件を表示</li>';
}
if (!empty($resultSet['linkNext']))
{
echo '<li><a href="' . h($resultSet['linkNext']) . '">次の' . $max . '件を表示</li>';
}
echo '</ul>';
?>
</body>
</html>
エラー画面 - fx_find_error.php
<!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">
<meta http-equiv="Content-Style-Type" content="text/css">
<title>FileMaker x FX.php 検索 - エラー</title>
<style type="text/css">
<!--
table th
{
color: #000000;
background-color: #cccccc;
}
-->
</style>
</head>
<body>
<form action="./fx_find.php" method="get">
<table border="1">
<tr>
<th>ft_serial</th>
<th>ft_text</th>
<th>ft_date</th>
<th>ft_num</th>
</tr>
<tr>
<td><input type="text" name="ft_serial" value="<?php echo h($_GET['ft_serial']); ?>"></td>
<td><input type="text" name="ft_text" value="<?php echo h($_GET['ft_text']); ?>"></td>
<td><input type="text" name="ft_date" value="<?php echo h($_GET['ft_date']); ?>"></td>
<td><input type="text" name="ft_num" value="<?php echo h($_GET['ft_num']); ?>"></td>
</tr>
<tr>
<td colspan="4" align="center"><input type="submit" value="検索"></td>
</tr>
</table>
</form>
<br>
<table border="1">
<tr>
<th>FileMakerエラーコード</th>
<td><?php echo h($resultSet['errorCode']); ?></td>
</tr>
<tr>
<th>エラー内容</th>
<td><?php echo h($errorsList[$resultSet['errorCode']]); ?></td>
</tr>
</table>
</body>
</html>
レイアウトに配置されているフィールドのうち、タイプが「テキスト」「数字」「日付」「オブジェクト」のフィールドのみを一覧表示する。Webブラウザでfx_find.phpにアクセスする。
fx_find.phpにアクセスすると検索+一覧画面(fx_find_view.php)が表示される。画面上部に検索UI、下部はレコードの一覧とページャになっている |
「次の10件を表示」をクリックすると、次のページへ移動する |
ft_textに「filemaker」と入力して検索した結果 |
ft_dateに「2010/1/1」と入力して検索した結果。FX.phpにはFileMaker API for PHPのsetFieldFromTimestampのような関数が用意されていないので、ここではstrtotimeをもちいて実現している |
それでは実装のポイントを追って解説していこう。まずは手順ベースでのポイントだ。
1. new FXでインスタンスを作成、接続先を指定
$data = new FX('localhost', 80, 'FMPro9', 'http');
まずはnew FXでインスタンスを作成する。コンストラクタの引数は「サーバ接続先アドレス」「ポート番号」「データタイプ」「スキーマ」。接続先は自分のマシンなので、ここではlocalhost, 80, FMPro9, httpと指定した。
余談になるが、データタイプには次の文字列を指定することができる。
- fmpro5
- fmpro6
- fmpro5/6
- fmpro7
- fmpro8
- fmpro9
- openbase
- mysql
- postgres
- odbc
- cafephp4pc
FileMakerをDBとして使う感覚でmysqlやpostgresを指定するとfoundCountや-max/-skipの値をうまく処理してくれず、なかなか期待した動作になってくれないことがおおい。しかしFX.phpはオープンソースソフトウェア。ソースコードを読み、自分で修正を加えることでさまざまな応用が可能になっている。このあたりは難読化が施されているFileMaker API for PHPと比較すると大きなメリットだ。
2. SetDBDataで使用するDB、レイアウト、一度に取得するレコード件数、レスポンスレイアウトを指定
$data->SetDBData('fmapi_test','fmapi_list', $max);
SetDBDataの引数順は「データベース名」「レイアウト名」「一度に取得するレコード数」「レスポンスレイアウト」。ここでは「fmapi_test.fp7のfmapi_listレイアウトに対して処理を実行、一度に取得するレコード件数は$max(=10)」という意味となる。
3. AddDBParamまたはAddParamArrayで検索したい内容を指定
if (!empty($_GET['ft_serial']))
{
$data->AddDBParam('ft_serial', $_GET['ft_serial']);
}
if (!empty($_GET['ft_text']))
{
$data->AddDBParam('ft_text', '*' . $_GET['ft_text'] . '*' );
}
if (!empty($_GET['ft_date']))
{
$data->AddDBParam('ft_date', date('m/d/Y' ,strtotime($_GET['ft_date'])));
}
if (!empty($_GET['ft_num']))
{
$data->AddDBParam('ft_num', $_GET['ft_num']);
}
ここでのポイントは3点。
- 検索文字列が指定された場合のみ、はじめて検索条件を指定するように
- テキストタイプのフィールド検索は前後に「*」をつけ、FileMaker側で部分一致検索となるように
- 日付タイプのフィールド検索はdateとstrtotimeを使い、MM/DD/YYYY形式に変換しなおしてから検索するように
検索文字列を入力した段階ではじめてAddDBParamとし、余計な検索をおこなわないように。また、テキストタイプのフィールド検索は前後に「_」をつけて「_FileMaker*」といった部分一致検索をおこなっている。
FileMaker API for PHPにはUNIXタイムスタンプから日付をセットするsetFieldFromTimestampが用意されているのであまり問題にはならないが、FX.phpにはこれに相当する機能がない。日本での日付入力はYYYY/MM/DD形式が一般的だが、この日付はFileMaker Web公開ではそのまま利用できないので、「date('m/d/Y' ,strtotime($_GET['ft_date']))」とすることでMM/DD/YYYY形式に変換してから検索をおこなうようにしている。
4. AddSortParamでソート順を指定
switch($_GET['sortField'])
{
case 'ft_serial':
case 'ft_text':
case 'ft_date':
case 'ft_num':
case 'ft_repetitionText':
$data->AddSortParam($_GET['sortField'] , $order);
break;
default:
// ソートフィールドが指定されなかった場合は、ソートをおこなわない
break;
}
ここでのポイントは2点
- 指定したフィールドのみ、ソートがおこなわれるように
- ソートが指定されていない場合は、ソートをおこなわない
FileMakerのソートは他データベースと比較すると、かなりパフォーマンスが劣る。FileMaker単体で利用する場合はさほど気にならないが、サーバ公開されたファイルやWeb公開で利用する場合は無視できない差となる。そこでここでは、初期状態ではソートを実行しないようにしている。エンドユーザの要望にも左右されるところだが、極力ソートはおこなわないほうが吉だろう。
5. FMSkipRecordsで先頭から除外するレコード件数を指定 ~ 8. 結果を配列で取得
$data->FMSkipRecords($skip);
$resultSet = $data->FMFind();
今回はポータルを利用しないので、AddDBParam('-relatedsets.filter')/AddDBParam('-relatedsets.max')は使用せず。$resultSetに結果セットの配列を格納している。
9. $resultSet['errorCode']の値でエラー判定
if ( 0 === (int)$resultSet['errorCode'] )
$resultSet'errorCode']にはFileMakerエラーコードが返るので、これを使用してエラー処理。ここでは「0: エラーなし」とそれ以外で分岐させている。エラーコードごとに処理を実装する場合は別途[対応表を参考にしてほしい。
10. $resultSet['data']からフィールド内容を取得
foreach( $resultSet['data'] as $recordValue )
$resultSet['data']配列からレコード情報を取り出し、ループでテーブルセルを生成している。
このほかのポイントは次のとおり。
ソート指定の切替
$order = ( 'descend' === $_GET['sortOrder'] ) ? 'ascend' : 'descend';
一覧ヘッダのリンクをクリックするごとに、ソート順の指定が昇順/降順と切り替わるようになる。
一覧表示部の自動生成
FileMaker API for PHPではレイアウトに配置されているフィールド情報を柔軟に取得できる。FX.phpにはこれらのメソッドはないものの、最後にアクセスしたレイアウトのフィールド情報が$data->lastFieldsに格納されるので、これを利用している。$data->lastFieldsに格納される情報は次のとおり。
キー | 型 | 内容 |
---|---|---|
emptyok | string | 空欄不可か否かが格納される。空欄不可の場合は「NO」が、空欄許可の場合は「YES」が格納される |
maxrepeat | string | 最大繰り返し数が格納される |
name | string | フィールド名が格納される |
type | string | フィールドタイプが格納される。値はそれぞれ「テキスト(TEXT)」「数字(NUMBER)」「日付(DATE)」「時刻(TIME)」「タイムスタンプ(TIMESTAMP)」「オブジェクト(CONTAINER)」となる |
extra | string | FileMakerデータベース以外の場合、プライマリキーやオートインクリメントといった情報が格納される |
fx_find_view.phpではこの$data->lastFieldsのname・typeを利用し、指定したタイプだけのフィールド列、タイプごとに中央/右寄せやイメージタグを含めたレコード一覧を自動生成している。
ページャの生成
FX.phpでは結果セットのなかに、対象レコード数や次・前ページ用のリンクが格納されている。
キー | 型 | 内容 |
---|---|---|
foundCount | int | 対象レコード数 |
linkPrevious | string | 前ページ移動用のリンクが格納される。存在しない場合は空欄に |
linkNext | string | 次ページ移動用のリンクが格納される。存在しない場合は空欄に |
echo '<p>' . number_format($resultSet['foundCount']) . '件中、' .
number_format($skip+1) . '~' . number_format($max) . '件を表示</p>';
echo '<ul>';
if (!empty($resultSet['linkPrevious']))
{
echo '<li><a href="' . h($resultSet['linkPrevious']) . '">前の' . $max . '件を表示</li>';
}
if (!empty($resultSet['linkNext']))
{
echo '<li><a href="' . h($resultSet['linkNext']) . '">次の' . $max . '件を表示</li>';
}
echo '</ul>';
画面下部のページャではこれら値とリンクの有無をチェックしてページャの表示切り替えをおこなっている。
次回はFileMaker API for PHPを使った実装例を紹介しよう。