はじめに
カスタムWebにおいて、Webフォームに入力した検索条件をデータベースに送信し、検索結果を画面に表示するテクニックは頻繁に使われる基本操作です。ここでは、FileMaker API for PHPを使って、検索フォームの値の処理やレコードの表示、ページ分割などのテクニックを紹介します。
なお、FileMaker Server 9付属のPHP SiteAssistantは利用せず、いちから開発するものとして進めていきます。また、FileMaker ServerにおけるPHP環境などのセットアップは既に完了しているものとして話を進めます。
データベースの準備
ここでは、簡単な商品カタログの検索を例にとって紹介します。
データベース名「Catalog.fp7」
テーブル名「Catalog」
- ID[商品No](数字・自動連番)
- Name[商品名](テキスト)
- Genre_ID[ジャンルID](テキスト)
- Maker_ID[メーカーID](テキスト)
- Price[価格](数字)
- RegistDate[登録日](日付)
テーブル名「Genre」
- Genre_ID[ジャンルID]
- Genre_Name[ジャンル名]
テーブル名「Maker」
- Maker_ID[メーカーID]
- Maker_Name[メーカー名]
リレーション
- 「Catalog」テーブルのGenre_IDと「Genre」テーブルのGenre_ID
- 「Catalog」テーブルのMaker_IDと「Maker」テーブルのMaker_ID
値一覧
- Genre
- Maker
レイアウト「web_catalog」[Catalogテーブル]に以下の項目を配置。
- ID
- Name
- Genre_ID
- Genre_Name[Genreテーブル]
- Maker_ID
- Maker_Name[Makerテーブル]
- Price
アクセス権
- ID「web」、Password「pass」
- アクセス権セット「閲覧のみアクセス」を割り当てる。
- アクセス権セット「閲覧のみアクセス」に拡張アクセス権「fmphp」を割り当てる。
画面の流れ
- 検索フォーム
- 検索結果一覧
- 詳細画面
また、ファイル構成は次のようになります。
/catalog_search/FileMaker/ //API本体 FileMaker.php //API本体 search.php //検索フォーム result.php //検索条件処理および検索結果表示 detail.php //詳細データ表示
FileMaker API for PHPの基本操作の概要
具体的なコーディングに入る前に、まずFileMaker API for PHPの基本的な考え方を解説しておきます。
FileMaker API for PHPはオブジェクト指向で開発されており、データベースを操作する操作単位がクラス(オブジェクト)としてまとられています。実行したい操作に応じてオブジェクトを作成し、各クラスに用意されている関数(メソッド)を利用しながらデータベースを操作していきます。
用意されている主なクラスは以下のものです。
- FileMakerデータベース(データベースへの接続、プロパティの設定)
- コマンド(レコードの追加/削除/複製/編集/検索)
- レイアウト(データベースレイアウトからの情報取得)
- レコード(レコードデータの操作)
- フィールド(定義されているフィールドの情報取得)
- 関連セット(関連データの操作)
- 結果(検索や登録、編集、削除など、コマンド系のクラスを実行した場合のレコードの処理)
- エラー(上の結果でエラーが発生したかとうかの確認やエラー処理)
例えば、データベースからレコードを検索する場合の操作の概要は次のようになります。
FileMaker
クラスを用いてデータベースに接続。- コマンドクラスを用いて検索条件を指定し、検索を実行。
- 結果クラスを用いて、対象レコードを取得。
- レコードクラスを用いて、各レコードのデータを取得。
- 3でエラーが発生した場合(検索結果がないなど)、エラークラスを用いてエラー処理を実行。
各クラス(オブジェクト)の役割と、それぞれに用意されている関数(メソッド)を理解すれば、今どんな処理がしたいのかによって、どのクラス/関数を利用をすればいいのか理解できるようになると思います。
検索フォームの作成
それでは、まず検索フォームを作りましょう。
ジャンルやメーカーはプルダウンで選択して検索できるようにしたいので、検索フォーム表示の際に、データベースにアクセスしてジャンルテーブルとメーカーテーブルの値を取得しておくようにします。
<?php //文字コード指定 ※1 header("Content-Type: text/html;charset=utf-8"); //APIのインクルード ※2 include_once('FileMaker.php'); //FileMakerクラスを用いてFMに接続する。 ※3 $fm = new FileMaker(); $fm->setProperty('database', 'Catalog'); $fm->setProperty('hostspec', 'http://localhost'); $fm->setProperty('username', 'web'); $fm->setProperty('password', 'pass'); //レイアウト情報を取得し、ジャンルとメーカーの値一覧のデータを取得。※4 $layout=$fm->getLayout('web_catalog'); $genres = $layout->getValueList('Genre'); $makers = $layout->getValueList('Maker'); ?> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>カタログ検索</title> </head> <body> <h1>カタログ検索</h1> <form action="result.php" method="get"> ※5 <table> <tr> <th>商品ID</th> <td><input type="text" name="ID" value=""></td> </tr> <tr> <th>商品名</th> <td><input type="text" name="Name" value=""></td> </tr> <!--FMから取得した値一覧の値を展開 ※6--> <tr> <th>ジャンル</th> <td><select name="Genre_Name"> <option value=""></option> <?php foreach($genres as $genre){ echo '<option value="'.$genre.'">'.$genre.'</option>'; } ?> </select> </td> </tr> <tr> <th>メーカー</th> <td><select name="Maker_Name"> <option value=""></option> <?php foreach($makers as $maker){ echo '<option value="'.$maker.'">'.$maker.'</option>'; } ?> </select> </td> </tr> <tr> <th>登録日</th> <td><input type="text" name="RegistDate" value=""></td> </tr> </table> <p><input type="submit" name="_search" value="検索実行"></p> </form> </body> </html>
- 作成したPHPファイルが文字コードUTF-8で記述されていることを明示するために、PHPの
header
関数を使って文字コードを出力するようにしています。 - FileMaker API for PHPを読み込みます。
- 変数
$fm
にFileMaker
クラスのインスタンスが作成されます。setProperty
メソッドを使ってデータベース名やアカウント情報などを設定し、データベースに接続します。 FileMaker
クラスのgetLayout
メソッドを利用して、接続したデータベースのレイアウト情報を取得します。このレイアウト情報からgetValueList
メソッドを利用してフィールドに設定している値一覧などの情報を取得できます。ここでは、$genres
にFMで設定した値一覧「Genre」の値の配列が入り、$makers
に「Maker」の値の配列が入ります。- フォームの送信方法をGETにします。検索結果をページ分割して表示する際に、検索条件の引き継ぎの処理の記述がシンプルになります。
- 取得した値一覧のデータ、
$genres
と$makers
をforeach
関数を使ってプルダウンリストとして展開します。
作成したファイルにアクセスしてみます。
http://【serverIP】/catalog_search/search.php
実行した結果のイメージは次のようになります。
検索結果の表示
検索フォームから送信された検索条件をFileMakerに送り、検索結果を受け取ります。検索結果が多数の場合は、ページ分割して表示し、次のページなどのリンクをたどりながら表示するレコードの範囲を切り替えていけるようにします。
考慮しなければならないのは、
- 1ページに表示する件数
- 取得するレコードの開始位置
- 表示するレコードの内容を切り替える「前/次ページへのリンク」の作成
となります。
この点を踏まえて、検索条件を処理して検索結果を表示するページを作ります。
<?php //文字コードの指定 header("Content-Type: text/html;charset=utf-8"); //APIのインクルード include_once('FileMaker.php'); //レコード取得開始位置の指定 ※1 if(isset($_GET['skip'])){ $skip = $_GET['skip']; }else{ $skip = 0; } //レコード取得件数の指定 ※2 $max = 10; //FileMakerに接続 $fm = new FileMaker(); $fm->setProperty('database', 'Catalog'); $fm->setProperty('hostspec', 'http://localhost'); $fm->setProperty('username', 'web'); $fm->setProperty('password', 'pass'); //検索オブジェクトの作成 ※3 $findCommand =& $fm->newFindCommand('web_catalog'); //レコードの取得範囲の指定。※4 $findCommand->setRange($skip,$max);
- レコードの取得開始番号を指定します。検索条件の引き継ぎなどで、URLでのskipの指定がない場合は、番号を0にして最初のレコードから取得するようにします。
- 1度に取得するレコードの件数を指定します。ここでは10件を指定します。10件を超えるレコードの件数を取得した場合は、次ページなどのリンクを使って表示させるようにします。
FileMaker
クラスのnewFindCommand
を使って検索オブジェクトを作成します。引数にレイアウト名を渡します。- 検索オブジェクトの
setRange
メソッドを使って、取得するレコードの範囲を指定します。引数に開始位置と取得件数を指定します。
//検索条件の指定 ※5 if(!empty($_GET['ID'])){ $findCommand->addFindCriterion('ID','=='.$_GET['ID']); //※6 $search_query .= 'ID='.urlencode($_GET['ID']).'&'; } if(!empty($_GET['Name'])){ $findCommand->addFindCriterion('Name',$_GET['Name']); $search_query .= 'Name='.urlencode($_GET['Name']).'&'; } if(!empty($_GET['Genre_Name'])){ $findCommand->addFindCriterion( 'Genre::Genre_Name',$_GET['Genre_Name']); $search_query .= 'Genre_Name='.urlencode($_GET['Genre_Name']).'&'; } if(!empty($_GET['Maker_Name'])){ $findCommand->addFindCriterion( 'Maker::Maker_Name',$_GET['Maker_Name']); $search_query .= 'Maker_Name='.urlencode($_GET['Maker_Name']).'&'; } if(!empty($_GET['RegistDate'])){ //検索条件の対象が日付フィールドの場合は、 //日付の形式をmm/dd/yyyyに変換。※7 $registdate = date('m/d/Y', strtotime($_GET['RegistDate'])); $findCommand->addFindCriterion('RegistDate',$registdate); $search_query .= 'RegistDate='.urlencode($_GET['RegistDate']).'&'; } $findCommand->addSortRule('ID',1,FILEMAKER_SORT_ASCEND); //※8 //検索実行 ※9 $result = $findCommand->execute(); //検索実行後の処理。結果オブジェクト($result)が //エラーオブジェクトかどうかを判断する。※10 if(FileMaker::isError($result)){ //エラーコード、メッセージを取得。 $error_cd = $result->code; $error_mes = $result->getMessage(); }else{ //検索結果が得られた時の処理 ※11 //対象レコード数をセット $foundCount = $result->getFoundSetCount(); //取得したレコード数をセット $fetchCount = $result->getFetchCount(); //取得したレコードの開始番号を取得 $range_start = $skip+1; //取得したレコードの終了番号を取得 $range_end = $skip + $fetchCount; //レコードデータの取得 $records = $result->getRecords(); } ?>
- 検索オブジェクトの
addFindCriterion
メソッドを使って検索条件を指定します。引数にフィールド名と検索条件を渡します。ここでは、検索結果が多数ある場合に「前/次ページへのリンク」を利用するため、検索条件を引き継げるように$search_query
にフィールド名と検索条件の組み合わせをセットします。 - 送信する値にFileMakerで使用する検索演算子を付与することによって、FileMakerと同様に、完全一致検索や範囲指定などの検索オプションを適用することができます。
- 検索対象のフィールドが日付の場合、日付の書式はmm/dd/yyyyである必要があります。mm/ddで入力されていたり、yyyy/mm/ddとユーザーが入力する場合が多いので、PHPの
strtotime
関数を使って書式をmm/dd/yyyyに変換してセットするようにします。 - 検索オブジェクトの
addSortRule
メソッドを使って検索結果のレコードのソート順を指定します。引数には、ソートするフィールド名、複数のフィールドを指定する場合のソート優先順位、ソート方法を指定します。ソート方法は、昇順なら「FILEMAKER_SORT_ASCEND」、降順なら「FILEMAKER_SORT_DESCEND」、値一覧で指定した並び順として値一覧名を指定することができます。 - 検索オブジェクトの
execute
メソッドを使って検索を実行します。検索結果は結果オブジェクトとして$result
に返ってきます。 - 検索実行時、検索結果がないなどのエラーが発生した場合、結果オブジェクトの
$result
にはエラーオブジェクトが返ってきています。結果オブジェクトがエラーオブジェクトであるかどうか判断することで処理が正常に行われたかどうかが判断できます。エラーが発生した場合、エラーオブジェクトのgetMessage
メソッドを使ってエラーメッセージを取得します。 - 検索結果が得られた場合、結果オブジェクトの
getFoundSetCount
やgetFetchCount
メソッドを使って、対象件数や取得件数を取得します。開始番号の$skip
や取得件数の$max
を組み合わせて、表示するレコードの開始番号や終了番号などを取得します。検索結果のレコードは、getRecords
メソッドを使って取得します。取得したレコードはレコードオブジェクトの配列として$records
に取得することができます。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>検索結果一覧</title> </head> <body> <h1>検索結果一覧</h1> <?php //対象レコードがある場合の処理 ※12 if($foundCount){ ?> <p>検索結果<?=$foundCount?>件のうち、 <?=$range_start?>件目から<?=$range_end?>件目を表示しています。</p> <table border="1"> <tr> <th>商品ID</th> <th>商品名</th> <th>ジャンル</th> <th>メーカー</th> <th>価格</th> <th>登録日</th> </tr> <?php //取得したレコードを展開。※13 foreach($records as $record){ ?> <tr> <td><a href="detail.php?recid=<?=$record->getRecordId()?> &search_query=<?=urlencode($search_query.'skip='.$skip)?>"> <?=$record->getField('ID')?></a></td> <td><?=$record->getField('Name')?></td> <td><?=$record->getField('Genre::Genre_Name')?></td> <td><?=$record->getField('Maker::Maker_Name')?></td> <td><?php $price = $record->getField('Price'); if(is_numeric($price)){ echo number_format($price); }else{ echo $price; } ?> </td> <td><?=date('Y/m/d' , strtotime($record->getField('RegistDate')))?></td> </tr> <?php } ?> </table> <p> <?php //前・次ページへのリンクを表示。※14 //開始番号が、ページ表示件数より大きい場合は、 //前ページへのリンクを表示 if($range_start > $max){ echo '<a href="result.php?'.$search_query. '&skip='.($skip-$max).'">前のページ</a> '; } //終了番号よりも、対象件数が大きい場合は、 //次ページへのリンクを表示 if($range_end < $foundCount){ echo '<a href="result.php?'.$search_query. '&skip='.($skip+$max).'">次のページ</a>'; } ?> </p> <?php }else{ //対象レコードがない場合、エラーメッセージの表示 ※15 echo '<p>'.$error_mes.'('.$error_cd.')</p>'; } ?> <p><a href="search.php">検索フォームに戻る</a></p> <?=$registdate?> </body> </html>
- 対象レコードがある場合は、レコードを表示する処理を行い、ない場合はエラーメッセージを表示するようにします。対象レコードがある場合、取得したレコードの対象件数、表示するレコードの開始番号や終了番号を表示するようにします。
- 取得したレコードを
foreach
関数を使って展開します。$record
にはレコードオブジェクトが返ってきていますので、フィールドの値を表示させたい場合、getField
メソッドを利用します。数字の書式を整形する場合はPHPのnumber_format
関数、日付の書式を指定して表示したい場合は、strtotime
関数とdate
関数を利用して任意の書式で表示することができます。商品IDの表示のところには詳細ページへのリンクを設け、レコードIDを渡すようにしています。また、このリンクには検索条件と現在の取得開始番号も渡すようにしています。先の詳細画面からこの検索結果一覧に戻ってこれるようにするためです。 - 対象レコードが取得件数よりも多い場合は、「前/次ページのリンク」を表示するようにします。開始番号(
$range_start
)が取得件数($max
)よりも多い場合は、前ページのリンクを表示させます。リンクには、5でセットした検索条件を渡し、開始番号を前のページにセット($skip-$max
)します。終了番号よりも対象件数が多い場合は、次のページのリンクを表示させるようにします。次のページのリンクには、前ページのリンク同様、次ページに合わせた値($skip+$max
)を検索条件と開始番号にセットします。 - エラーが発生した場合(対象レコードがないなど)、ここで取得したエラーメッセージを表示させます。
詳細ページの表示
最後に詳細ページを作成します。詳細ページは、検索結果一覧のページから渡されたレコードIDを利用して、レコードのデータを表示します。
<?php //文字コードの指定 header("Content-Type: text/html;charset=utf-8"); //APIのインクルード include_once('FileMaker.php'); //FileMakerに接続 $fm = new FileMaker(); $fm->setProperty('database', 'Catalog'); $fm->setProperty('hostspec', 'http://localhost'); $fm->setProperty('username', 'web'); $fm->setProperty('password', 'pass'); //レコード情報を取得。 ※1 $record = $fm->getRecordById('web_catalog',$_GET['recid']); ?> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>カタログ詳細</title> </head> <body> <h1>カタログ詳細</h1> <!--レコードのデータを表示 ※2--> <table border="1"> <tr> <th>商品ID</th> <td><?=$record->getField('ID')?></td> </tr> <tr> <th>商品名</th> <td><?=$record->getField('Name')?></td> </tr> <tr> <th>ジャンル</th> <td><?=$record->getField('Genre::Genre_Name')?></td> </tr> <tr> <th>メーカー</th> <td><?=$record->getField('Maker::Maker_Name')?></td> </tr> <tr> <th>価格</th> <td> <?php $price = $record->getField('Price'); if(is_numeric($price)){ echo number_format($price); }else{ echo $price; } ?> </td> </tr> <tr> <th>登録日</th> <td> <?=date('Y/m/d', strtotime($record->getField('RegistDate')))?> </td> </tr> </table> <!--検索結果の一覧に戻るリンクを表示 ※3--> <p><a href="result.php?<?=$_GET['search_query']?>"> 検索結果一覧に戻る</a></p> </body> </html>
- 指定した1件のレコードにアクセスするには
getRecordById
メソッドを利用します。引数にアクセスするレイアウト名とレコードIDを渡します。$record
には取得したレコードのレコードオブジェクトが返ってきます。 - レコードオブジェクトの
getField
メソッドを利用して値を表示します。ここは「result.php」の項で解説したことと同じです。 - 「result.php」からGETメソッドにて引き継いだ検索条件「search_query」をリンクにセットします。これにより、検索結果の一覧に戻ることができます。
まとめ
いかがでしたか? FileMaker API for PHPを使ってレコードの検索を行う場合の処理をざっと解説してみました。検索結果のページ分割表示は、取得範囲の指定と検索条件の引き継ぎの処理がキモです。FileMaker 6以前のCDMLという言語においては、[Fmp-LinkPreview][Fmp-LinkNext]などの便利なタグ(関数)があったため、特に処理を意識しなくてもすみましたが、APIで開発を進める際はこの箇所を自身で処理していく必要があります。
次の機会には、フォームデータの処理や日付などの書式を指定したフィールドデータの表示などを、カスタム関数にまとめることにより、コーディングがとても楽になるテクニックを紹介したいと思います。