Webアプリケーションを実装するにあたり、コード中でもっとも登場回数がおおくなると思われるレコード検索。実用的なサンプルをもとに、FileMaker API for PHPでの実装例を紹介する。FX.phpとの実装差を比較してもらいたい。
レコードの検索方法 - FileMaker API for PHP
今回はFileMaker API for PHPをベースに、検索と一覧画面を実装する。前回と同様、FX.phpでできることをFileMaker API for PHPでも実現するということコンセプトで、代替案をもちいて実装をおこなっている。ファイル構成はFX.phpでの実装同様「コントローラ」「検索+一覧画面」「エラー画面」としている。
コントローラ - api_find.php
<?php
include_once('../FileMaker.php');
// 文字列エスケープ用関数
function h($string)
{
return htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
}
// 一度に取得する件数を指定
$max = 10;
// 先頭から除外するレコード数を取得
$skip = ( 0 < (int)$_GET['skip']) ? (int)$_GET['skip'] : 0;
// ソート指定を取得
$order = ( FILEMAKER_SORT_DESCEND === $_GET['sortOrder'] ) ? FILEMAKER_SORT_ASCEND : FILEMAKER_SORT_DESCEND;
// 1. new FileMakerでFileMakerのインスタンスを作成、接続先を指定
$data = new FileMaker('fmapi_test', 'http://localhost:80', 'admin', 'admin');
// 2. newFindCommandでFileMaker_Command_Findオブジェクトを作成。このときレイアウトを指定
$findCommand = $data->newFindCommand('fmapi_list');
// 3. addFindCriterionで検索したい内容を指定
if (!empty($_GET['ft_serial']))
{
$findCommand->addFindCriterion('ft_serial', $_GET['ft_serial']);
}
if (!empty($_GET['ft_text']))
{
$findCommand->addFindCriterion('ft_text', '*' . $_GET['ft_text'] . '*' );
}
if (!empty($_GET['ft_date']))
{
$findCommand->addFindCriterion('ft_date', date('m/d/Y' ,strtotime($_GET['ft_date'])));
}
if (!empty($_GET['ft_num']))
{
$findCommand->addFindCriterion('ft_num', $_GET['ft_num']);
}
// 4. addSortRuleでソート順を指定
switch($_GET['sortField'])
{
case 'ft_serial':
case 'ft_text':
case 'ft_date':
case 'ft_num':
case 'ft_repetitionText':
$data->addSortRule($_GET['sortField'], 1, $order);
break;
default:
// ソートフィールドが指定されなかった場合は、ソートをおこなわない
break;
}
// 5. setRangeで先頭から除外するレコード件数・一度に取得するレコード件数を指定
$findCommand->setRange($skip, $max);
// 6. レイアウトにポータルが配置されている場合は、
// setRelatedSetsFiltersでフィルタリングの設定をおこなう
// 7. setResultLayoutでレスポンスレイアウトを指定
// 8. executeでリクエスト発行
// 9. 成功した場合はFileMaker_Resultオブジェクトが、失敗した場合はFileMaker_Errorオブジェクトが返る
$resultSet = $findCommand->execute();
// 10. FileMaker::isErrorでエラー判定
if (!FileMaker::isError($resultSet))
{
include_once('./api_find_view.php');
}
else
{
// エラー処理
// ロケールをjaとセットしておくことで、エラーメッセージを日本語にできる
$data->setProperty('locale', 'ja');
include_once('./api_find_error.php');
}
?>
検索+一覧画面 - api_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 FileMaker API for PHP 検索</title>
<style type="text/css">
<!--
table th
{
color: #000000;
background-color: #cccccc;
}
-->
</style>
</head>
<body>
<form action="./api_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
$layoutObj = $resultSet->getLayout();
foreach( $layoutObj->getFields() as $fieldObj )
{
// タイプが「テキスト」「数字」「日付」「オブジェクト」のみを表示
switch($fieldObj->getResult())
{
case 'text':
case 'number':
case 'date':
echo '<th><a href="./api_find.php?ft_text=' . h($_GET['ft_text']).
'&sortField=' . $fieldObj->getName() .
'&sortOrder=' . $order . '">' . $fieldObj->getName() . '</a></th>';
break;
case 'container':
echo '<th>' . $fieldObj->getName() . '</th>';
break;
default:
break;
}
}
unset($fieldObj);
?>
</tr>
<?php
// 11. getFirstRecordやgetRecordsでFileMaker_Recordオブジェクトを取得
foreach( $resultSet->getRecords() as $recordObj )
{
?>
<tr>
<?php
foreach( $layoutObj->getFields() as $fieldObj )
{
if ( '' !== $recordObj->getField($fieldObj->getName()) )
{
switch($fieldObj->getResult())
{
case 'text':
echo '<td>' . h($recordObj->getFieldUnencoded($fieldObj->getName())) . '</td>';
break;
case 'number':
echo '<td align="right">' . h(number_format($recordObj->getFieldUnencoded($fieldObj->getName()))) . '</td>';
break;
case 'date':
echo '<td align="center">' . date('Y/m/d', $recordObj->getFieldAsTimestamp($fieldObj->getName())) . '</td>';
break;
case 'container':
echo '<td align="center"><img src="' . h($recordObj->getFieldUnencoded($fieldObj->getName())) . '"></td>';
break;
default:
break;
}
}
else
{
echo '<td> </td>';
}
}
?>
</tr>
<?php
}
unset($recordObj);
?>
</table>
<?php
echo '<p>' . number_format($resultSet->getFoundSetCount()) . '件中、' .
number_format($skip+1) . '~' . number_format($skip+$max) . '件を表示</p>';
echo '<ul>';
$linkPrevious = ( $max === (int)( $skip + $max ) ) ?
'' :
$_SERVER['SCRIPT_NAME'] . '?skip=' . (int)( $skip - $max ). '&' . preg_replace('/skip=[\d]*[&]?/', '', $_SERVER['QUERY_STRING'] );
if (!empty($linkPrevious))
{
echo '<li><a href="' . h($linkPrevious) . '">前の' . $max . '件を表示</li>';
}
$linkNext = ( (int)$resultSet->getFoundSetCount() === (int)( $skip + $max ) ) ?
'' :
$_SERVER['SCRIPT_NAME'] . '?skip=' . (int)( $skip + $max ). '&' . preg_replace('/skip=[\d]*[&]?/', '', $_SERVER['QUERY_STRING'] );
if (!empty($linkNext))
{
echo '<li><a href="' . h($linkNext) . '">次の' . $max . '件を表示</li>';
}
echo '</ul>';
?>
</body>
</html>
エラー画面 - api_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 FileMaker API for PHP 検索 - エラー</title>
<style type="text/css">
<!--
table th
{
color: #000000;
background-color: #cccccc;
}
-->
</style>
</head>
<body>
<form action="./api_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->getCode()); ?></td>
</tr>
<tr>
<th>エラー内容</th>
<td><?php echo h($resultSet->getMessage()); ?></td>
</tr>
</table>
</body>
</html>
レイアウトに配置されているフィールドのうち、タイプが「テキスト」「数字」「日付」「オブジェクト」のフィールドのみを一覧表示する。Webブラウザでapi_find.phpにアクセスする。
api_find.phpにアクセスすると検索+一覧画面(api_find_view.php)が表示される。画面表示はFX.phpとまったく一緒だが、中身のコーディングは上記ソースコードのとおりかなり異なる |
「次の10件を表示」をクリックすると、次のページへ移動する。APIにはページ遷移リンクの自動生成機能がないので自作する必要あり |
ft_textに「filemaker」と入力して検索した結果 |
ft_serialにテキスト文字列を入力して検索した結果。エラー画面(api_find_error.php)が表示される。エラー内容はロケールを設定することで日本語で表示することが可能 |
それではFX.phpと同様、実装のポイントを追って解説していこう。興味がある方は随所でFX.phpとの実装を比較してみてほしい。まずは手順ベースでのポイントだ。
1. new FileMakerでFileMakerのインスタンスを作成、接続先を指定
$data = new FileMaker('fmapi_test', 'http://localhost:80', 'admin', 'admin');
まずはnew FileMakerでFileMakerインスタンスを作成する。コンストラクタの引数は「データベース名」「ポート番号をふくめたサーバ接続先アドレス」「ファイルを開くためのID」「ファイルを開くためのパスワード」。慣れないうちはFX.phpのコンストラクタ・SetDBDataと混同しやすいので注意が必要だ。
2. newFindCommandでFileMaker_Command_Findオブジェクトを作成。このときレイアウトを指定
$findCommand = $data->newFindCommand('fmapi_list');
FileMaker API for PHPでは検索条件もオブジェクトで管理する。FileMaker_Command_Findオブジェクトを作成し、レイアウトを指定。
3. addFindCriterionで検索したい内容を指定
if (!empty($_GET['ft_serial']))
{
$findCommand->addFindCriterion('ft_serial', $_GET['ft_serial']);
}
if (!empty($_GET['ft_text']))
{
$findCommand->addFindCriterion('ft_text', '*' . $_GET['ft_text'] . '*' );
}
if (!empty($_GET['ft_date']))
{
$findCommand->addFindCriterion('ft_date', date('m/d/Y' ,strtotime($_GET['ft_date'])));
}
if (!empty($_GET['ft_num']))
{
$findCommand->addFindCriterion('ft_num', $_GET['ft_num']);
}
2で作成したFileMaker_Command_Findオブジェクトに、検索条件を指定する。3点のポイントはFX.phpと同じなので省略。FX.phpのAddDBParamがaddFindCriterionに置きかわったくらいだ。FileMaker API for PHPにはAddParamArrayに相当する機能がないので注意。検索オブジェクトを複数作成すれば複合検索も簡単に実装できる。
4. addSortRuleでソート順を指定
switch($_GET['sortField'])
{
case 'ft_serial':
case 'ft_text':
case 'ft_date':
case 'ft_num':
case 'ft_repetitionText':
$data->addSortRule($_GET['sortField'], 1, $order);
break;
default:
// ソートフィールドが指定されなかった場合は、ソートをおこなわない
break;
}
「指定したフィールドのみ、ソートがおこなわれるように」「ソートが指定されていない場合は、ソートをおこなわない(初期状態ではソートを実行しないように)」というポイントは同じ。FX.phpと違う箇所は、先頭で$orderに格納する値にFileMaker API for PHP側で用意されている定数を利用する点。
$order = ( FILEMAKER_SORT_DESCEND === $_GET['sortOrder'] ) ? FILEMAKER_SORT_ASCEND : FILEMAKER_SORT_DESCEND;
ascend, descendと直書きしても動作する。しかしFileMaker Serverのバージョンアップなどで、Web公開に関連する文法の仕様変更が発生する可能性を考えると、用意されている定数を利用しておくのが吉だろう。
5. setRangeで先頭から除外するレコード件数・一度に取得するレコード件数を指定 ~ 8. executeでリクエスト発行
$findCommand->setRange($skip, $max);
$resultSet = $findCommand->execute();
レスポンスレイアウトとポータルを利用しないので、setRelatedSetsFilters/setResultLayoutは使用せず。検索処理に成功した場合はFileMaker_Resultオブジェクトが、失敗した場合はFileMaker_Errorオブジェクトが$resultSetに返る。
10. FileMaker::isErrorでエラー判定
if (!FileMaker::isError($resultSet))
FX.phpではXMLにふくまれるFileMakerエラーコード(errorCode)を利用してエラー処理を実装した。FileMaker API for PHPでは専用のメソッド「FileMaker::isError」が用意されているので、これを利用してエラー処理を実装する。
11. getFirstRecordやgetRecordsでFileMaker_Recordオブジェクトを取得
foreach( $resultSet->getRecords() as $recordObj )
FileMaker_Resultオブジェクト$resultSetからgetRecord()でレコード情報を取り出し、ループでテーブルセルを生成。ループ中のフィールド内容の取得方法など、こまかい使い勝手が異なる。
このほかのポイントは次のとおり。
エラーメッセージの日本語化
FileMaker API for PHPではあらかじめ多言語のエラーメッセージが用意されている。ロケールに"ja"設定することで日本語のエラーメッセージを表示することが可能だ。
$data->setProperty('locale', 'ja');
指定しない場合は自動的に英語ロケールとなる。
一覧表示部の自動生成
FileMaker API for PHPではレイアウトに配置されているフィールド情報を柔軟に取得できる。FileMaker_Resultオブジェクトからまずレイアウト情報(FileMaker_Layout object)を抜き出し、フィールド情報(FileMaker_Field object)を抜き出す。取得できる情報は次のとおり(一部を抜粋)。
メソッド名 | 内容 |
---|---|
getLocalValidationRulesなど | 入力値の制限情報 |
getRepetitionCount | 最大繰り返し数 |
getName | フィールド名 |
getResult | フィールドタイプ。値はそれぞれ「テキスト(text)」「数字(number)」「日付(date)」「時刻(time)」「タイムスタンプ(timestamp)」「オブジェクト(container)」となる。FX.phpと異なりすべて小文字で返るので注意 |
getType | 対象フィールドが計算フィールドか集計フィールドかを判定する。計算フィールドの場合は「calculation」、集計フィールドの場合は「summary」、それ以外の場合は「normal」となる |
getStyleType | フィールドコントロールスタイル/表示形式 |
isAutoEntered | 入力値の自動化が設定されているか否かを判定 |
isGlobal | グローバルフィールドか否かを判定 |
ここではgetName, getResultを利用し、指定したタイプだけのフィールド列、タイプごとに中央/右寄せやイメージタグを含めたレコード一覧を自動生成している。1度のリクエストでレイアウト・フィールド詳細情報が取得できるのはFileMaker API for PHPの強みの一つだ。FileMaker_Fieldクラス・オブジェクトの詳細はバックナンバー「フィールド情報の取得に特化したクラス、FileMaker_Field」を参照してほしい。
ページャの生成
FX.phpでは結果セットのなかに、対象レコード数や次・前ページ用のリンクが格納されている。残念ながらこの機能はFileMaker API for PHPには用意されていないので、各種リンクは自前で用意する必要がある。
echo '<p>' . number_format($resultSet->getFoundSetCount()) . '件中、' .
number_format($skip+1) . '~' . number_format($skip+$max) . '件を表示</p>';
echo '<ul>';
$linkPrevious = ( $max === (int)( $skip + $max ) ) ?
'' :
$_SERVER['SCRIPT_NAME'] . '?skip=' . (int)( $skip - $max ). '&' . preg_replace('/skip=[\d]*[&]?/', '', $_SERVER['QUERY_STRING'] );
if (!empty($linkPrevious))
{
echo '<li><a href="' . h($linkPrevious) . '">前の' . $max . '件を表示</li>';
}
$linkNext = ( (int)$resultSet->getFoundSetCount() === (int)( $skip + $max ) ) ?
'' :
$_SERVER['SCRIPT_NAME'] . '?skip=' . (int)( $skip + $max ). '&' . preg_replace('/skip=[\d]*[&]?/', '', $_SERVER['QUERY_STRING'] );
if (!empty($linkNext))
{
echo '<li><a href="' . h($linkNext) . '">次の' . $max . '件を表示</li>';
}
echo '</ul>';
上記のリンク生成式はFX.phpの内部関数、BuildLinkQueryStringやAssembleDataSetでおこなっているものを簡略化したものだ。
一見多機能なFileMaker API for PHPだが、サーバにかかる負荷やパフォーマンス面で気をつけるべき点も多い。次回はFX.phpとFileMaker API for PHPの負荷計測・パフォーマンス測定結果を紹介しよう。