FileMaker Server大全__新居雅行 著
出版後情報・読者サポート

更新
新居雅行/msyk@msyk.net

本書はFileMaker Server 9に関する解説書です。特に、PHPを利用して、カスタムWebを構築する手法をメインに解説しています。FileMakerのPHPでの利用方法はもちろん、PHP自身の基本機能についてのリファレンスやWebアプリケーション作成に必要な機能を紹介しています。また、FileMaker Serverの運用方法に関しても解説しています。

書籍で紹介しているURL→ 書籍で紹介しているサンプルプログラム→

Update

Unicodeの扱いの問題点

結論としては、FileMaker Server 9/10いずれも、サロゲートペアによるUnicode文字が含まれるレコードを、Web側に取り出すことができません。サロゲートペアについてはリンク先に詳しく書かれています。2つの16ビットコードを合わせて表現する方法で、あまり使われない漢字でこの表現が取られている場合があります。データベースのフィールドにサロゲートペアに対応する文字がある場合、FileMaker API for PHPでそのレコードを取り出すと「Unknown error」となり、そのフィールドだけでなくレコードや検索結果がまったく取得できません。また、XMLの評価をするところでのエラーでありFileMakerが正しいXMLを生成しない限りは根本的な解決になりません。インスタントWebに関しては正しくないエンコードなのですが、うまくすり抜けて文字はきちんと見えます。

この問題点はFileMaker API for PHPを経由して名前を登録するときに、エラーが発生したことから分かりました。名前に「𥔎」がある場合にエラーが出たのです。この文字は、Unicodeでは0xFA11、UTF-16*ではサロゲートペアとなって0xD855 0xDD0E、UCS-4ではU+2550Eと定義されています。「やまざき」さんや、「おざき」さんでこの文字を使われる方はわずかですがいらっしゃると思います。この「𥔎」の文字をUTF-8に変換する時、UCS-4やUTF-16*ではなく、Unicodeのコードを変換しなければならないのです。しかしながら、FileMaker ServerのWeb公開エンジンは、UTF-16BEの16の16ビットデータをそれぞれUTF-8にして6バイトのサイズになっています。WikipediaのUTF-8の説明にある「サロゲートペアの扱い」によれば、UTF-16*での表現を個別にUTF-8にする方法は正しくないということです。つまり、Unicodeの0xFA11をUTF-8にしないといけませんがそうなっていません。

データベースからの取り出しを/fmi/xml/fmresultset.xmlでやってみます。すると、「𥔎」は0xD855 0xDD0EをそれぞれUTF-8に変換したものとなります。前後にどんな文字があっても、これはUnicodeでは文字に割り当てられていないコード範囲であるので、文字化けのようになります。そして、得られた結果のXMLは「正しくない文字が含まれている」と評価してエラーになります。XMLでのダイレクトなやりとりですでに正しくないので、対処はフレームワークの中をいじることになります。面白いのはIWPを使った場合で、同様にサロゲートペアのままエンコードしてしまうのですが、文字列はHTML参照形式(��)で表現されます。結果的に単なるバイト列として扱われバイト列のままOS側はUnicodeとして処理をすることで、状況見ながら適切に文字処理をしているのでしょう。なので、IWPでは文字化けになりません。なお、レコードに追加や変更するときには、PHPでも結果的にバイト列で処理をしているためか問題は発生しません。

もちろん、バグ報告しましたが、次のメジャーアップデートで直るかな? いずれにしても、Webアプリケーションを使っている人は気をつけましょう。(2009/10/4)

FileMaker Server 10について

2009/1/6にFileMaker Server 10およびFileMaker Server 10 Advancedがリリースされました。一部に新しい機能はありますが、本書「FileMaker Server大全」の内容はほとんどそのまま適用していただいてかまいません。違いがある部分については随時、このサイトで公開することにします。Ver.9時代に執筆した書籍ですが、Ver.10でもご愛読していただけるようにいたします。

インストールについて

FileMaker Server 10のインストールは従来のバージョンと同様、インストーラで行います。このとき、Ver.9をそのままにしておくと、警告が出てドキュメントを参照するように要求されます。ドキュメントでは、Ver.9をアンインストールしてからVer.10をインストールしなさいとなっています。しかしながら、Ver.9のアンインストールを行うために、Ver.9のインストーラを持ち出すのも面倒です。その場合、Ver.10のイストーラを使って、まずアンインストールを行います。これでも、Ver.9のアンインストールはできるようです。そして、Ver.10のインストールを行えば、警告は出てきません。

Mac OS Xの場合、ユーザfmserverとグループfmsadminが自動的に作られます(p60参照)。Mac OS Xではプロセスはfmserverのユーザ権限で実行しています。これらユーザとグループのID値は、Ver.9までは305だったのですが、Ver.10では499に変わっています。そのため、fmsadminグループを利用してフォルダのPOSIXアクセス権を設定している場合、Ver.9からVer.10にアップデートすると、結果的にはそのフォルダへのアクセス権がなくなってしまうことになります。そのため、フォルダのアクセス権の再設定が必要になります。この点は注意しましょう。

ファイル構成からの比較

Ver.9とVer.10について、Mac OS X Serverにインストールした結果を、ファイル名で比較してみました。

API for PHPの違い

2つのバージョンのPHPのソースをdiffしてみました。以下は違いですが、他はほぼ同じです。分の値チェックがなおっているのはまあ当然としても、値一覧での2フィールド取得もできるようになりました。

API for PHPで新しく加わったメソッド

レイアウト内で使われている値一覧の内容を得るためのメソッドとして、FileMaker Server v9には、FileMaker_LayoutクラスにgetValueListとgetValueListsがありました。前者は指定した値一覧、後者はすべての値一覧を得ます。レコードによって値一覧の内容が違う場合があるので、引数にはレコードIDを指定できますが、省略してもかまいません。

FileMaker Server v10では、さらにFileMaker_LayoutクラスにgetValueListTwoFieldsとgetValueListsTwoFieldsメソッドが加わりました。Ver.9までのメソッドだと、値一覧の2番目のフィールドの値が一切得られなかったのですが、Ver.10からは得られるようになりました。

これらのメソッドの結果を示しましょう。書籍にも図を出しましたが、次のようなレイアウトがあったとします。

次のようなプログラムを実行してみます。新しい2つのメソッドは「TowFields」がないメソッドと引数のは同様に指定できます。ここでは、レコードIDは省略しました。

$db = new FileMaker( '伝票', NULL, 'admin', 'testadmin' );
$layout = $db->getLayout( '伝票' );
$x = $layout->getValueList( '商品一覧' );	print_r( $x );
$x = $layout->getValueLists( );	print_r( $x );
$x = $layout->getValueListTwoFields( '商品一覧' );	print_r( $x );
$x = $layout->getValueListsTwoFields(  );	print_r( $x );

結果として得られるのは、次のようなものです。getValueListとgetValueListsの結果はVer.9と同じで、連想配列のキーは値一覧の最初のフィールドの値となっています。一方、新しいメソッドのgetValueListTwoFieldsとgetValueListsTwoFieldsについては、連想配列のキーが実際に画面に見えている文字列と同じになります。従って、array_keys関数を使ってキーだけを取り出すと、画面に表示されているメニュー項目の文字列の配列を取得できます。

メソッド結果
getValueListArray ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 )
getValueListsArray (
   [商品一覧] => Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 )
   [カテゴリ一覧] => Array ( [0] => 1 [1] => 2 [2] => 3 )
   [顧客一覧] => Array ( [0] => 1 [1] => 2 [2] => 3 ) )
getValueListTwoFieldsArray ( [2 みかん] => 2 [4 いちご] => 4 [6 スイーティ] => 6 [8 キウイ] => 8 )
getValueListsTwoFieldsArray (
   [商品一覧] => Array ( [2 みかん] => 2 [4 いちご] => 4 [6 スイーティ] => 6 [8 キウイ] => 8 )
   [カテゴリ一覧] => Array ( [1 日本国内産] => 1 [2 輸入品] => 2 [3 プレミア品] => 3 )
   [顧客一覧] => Array ( [1 大東亜青果店] => 1 [2 プリティ果物株式会社] => 2 [3 喫茶室ケニア] => 3 ) )

レコードデータの事前検証について

書籍のp292にあるコラムで、Ver.9では事前検証がされていないのではないかということを紹介しました。Ver.10では事前検証がされていることは確認しました。なお、事前検証の有無は、データベースエンジンへの負担が少し軽減されるだけとなります。空欄不可やあるいは正しくない日付の場合は、事前検証なしの場合はデータベースエンジンがエラーを出して処理は行いません。事前検証があれば、Webアプリケーション側で検知してデータベースエンジンのアクセスはないということです。つまり、事前検証の有無で、レコードが作られる/作られないという違いはないと言えるでしょう。

Mac OS Xでの管理コンソール

Ver.9をすでに使っていて、Ver.10にアップし、16000番ポートに接続して管理ツールのボタンをクリックして動かそうとしてもエラーで動かないということが発生するようです。筆者の場合は、キーチェーンにあるFileMakerの証明書を削除し、Javaのキャッシュのクリアで稼働できるようになりました。Javaのキャッシュは、ユーティリティフォルダの「Java」フォルダの「Java Preferences」を起動します。そして、「セキュリティ」のタブを選択して「キャッシュファイルを表示」のボタンをクリックします。すると、管理コンソールのJavaアプリケーションやPHP Site Assistantなどがキャッシュされているのが見えます。選択して、赤い「×」ボタンをクリックすると消去できます。これらを一度全部消してしまってから再度行ってみてください。ただし、これでもうまく行かなかったという人もいますので、すべての場合の解決策ではないようです。

エラーコード

本書の7-5「エラーコードのリファレンス」(p300〜)には、API for PHPに含まれているエラーを一覧しています。FileMaker Server 10では少し変わっているので、まとめておきます。以下の表のエラーメッセージが加わっています。

エラー番号説明
20コマンドまたは操作がスクリプトトリガによってキャンセルされました
1450PHP アクセス権を拡張する操作が必要です
1451現在のファイルをリモートにする操作が必要です
1501SMTP の認証に失敗しました
1502SMTP サーバーによって接続が拒否されました
1503SSL でエラーが発生しました
1504SMTP サーバーの接続を暗号化する必要があります
1505指定された認証方法は SMTP サーバーではサポートされていません
1506E メールは正常に送信されませんでした
1507SMTP サーバーにログインできませんでした

FileMaker Server 9に存在していて、FileMaker Server 10でなくなっているエラー番号は以下の通りです。

115, 116, 117, 416, 417, 512, 513, 736, 906, 1224, 12251409, 1410, 1411, 1412, 1413

本書を紹介していただいているサイト

オンライン販売サイト

出版元のラトルズのサイト(オンライン販売可能)Amazon.co.jpジュンク堂セブンアンドワイ喜久屋書店 Online Book Storecbook24.comブックモールPCショッピングトライeブックスビーケーワン楽天ブックス本やタウンBoople.comlivedoor BOOKSメテオ・メディカルブックセンター紀伊國屋書店BookWeb丸善Knowledge ParterYahooブックス

訂正・追加情報

正誤表/補足/情報

ページ場所
123fmsadmin backupの説明(訂正)書式 fmsadmin backup FILE...
引数の「BACKUP」を「PATH」に修正してください。
154setlocale関数の引数(追加)Windows上でPHPを動かしている場合には、引数$localeには日本語の場合は「jpn_jpn」を指定します。ただし、検討事項があるのでこちらをご覧下さい。
156表のヒアドキュメントの説明(追加)文字列の中に「{$」があれば、そこから変数展開をしようとします。その必要がない場合は、{ と $ の間にスペースを入れたり改行を入れたりして分離をします。
205array_map関数の$arr1引数の2行目配列をしてした配列を指定した
213表の下から4行目(追加)「date, gmdateでの文字列」の「j」に相当する「strftime,gmstrftimeでの文字列」は「%e」です。表ではブランクになってますが、「%e」を記入してください。
227XSLT公開(情報)本書ではXSLT公開によりWeb公開の手法は紹介していませんが、「FileMaker Server カスタムWebテクニック改訂版」(著者:松尾篤、ビー・エヌ・エヌ新社)が2008年10月に発売されました。情報を知りたい方はこちらをご覧下さい。
235コラム(補足)URLとして「~user」等を使う場合には、現在のパスの取得は「__FILE__」を使う必要があります。
236(右端の2つのボックス「PHPのプログラム」と「FileMaker API for PHP」の間に矢印付きの線があるはずですが、消えています)
260addSortRuleメソッドの$order引数(追加)昇順の場合FILEMAKER_SORT_ASCEND、降順の場合はFILEMAKER_SORT_DESCENDを定義定数として指定できる
270複合検索(補足があります。この下に記述してあります)
288〈FileMaker_Result〉->getFields()
〈FileMaker_Record〉->getFields()
(追加)得られる配列は、インデックス形式のフィールド名の文字列の配列
288〈FileMaker_Laoyout〉->getFields()
〈FileMaker_RelatedSet〉->getFields()
(追加)得られる配列は、フィールド名をキーとしたFileMaker_Fieldクラスのオブジェクトの配列
290コラム「時刻データの確認処理」(補足があります。この下に記述してあります)
340図中の「PHP」の枠addslasheaddslashes

p270の「複合検索」に関する補足

単純な検索は、FileMakerでの検索をプログラムでできると考えれば概ね大丈夫です。一方、複合検索が、FileMaker上でのどの処理に対応するのかということはかなり複雑です。FileMakerで検索モードに入ると、「検索条件」メニューが登場し、「検索実行」だけでなく「対象レコードの絞り込み」「対象レコードの拡大」という項目があり、ウインドウ内で指定している内容に対して項目名に従った検索ができます。このとき、フィールドに対する条件だけでなく、ステータスバーには「省略」というチェックボックスも出てきます。これらのFileMaker上の操作と、FileMaker API for PHPでのメソッドがそれぞれどれに対応するのかということを説明します。

「検索実行」は基本的にレイアウトに関連付けられたテーブルの全てに対しての処理を行います。最初に検索実行をしたとして、FileMaker Proでの検索結果をまとめておきましょう。このテーブル「検索テスト」には数値フィールドのnumだけがあり、フィールドとの値として1/2/3/4/5/6/7/8/9となっている9レコードが最初から存在します。以下のように、4つの検索作業をした結果を書いておきます。いずれも、2回の検索を行った結果で、たとえば番号が1のものは、最初に「検索実行」を行い、その後に引き続いて「対象レコードの絞り込み」を行ったということです。「対象レコードの絞り込み」は文字通り絞り込みますし、除外があれば、検索条件に合わないものが対象になるということだけです。「対象レコードの拡大」は条件に合ったレコードをさらに追加します。除外はその条件に対しての「NOT」つまり反対条件として適用されます。

番号条件コマンド除外結果
1num: >3「検索実行」チェックなし4/5/6/7/8/9
num: <7「対象レコードの絞り込み」チェックなし4/5/6
2num: >3「検索実行」チェックなし4/5/6/7/8/9
num: <7「対象レコードの絞り込み」チェックあり7/8/9
3num: <3「検索実行」チェックなし1/2
num: >7「対象レコードの拡大」チェックなし1/2/8/9
4num: <3「検索実行」チェックなし1/2
num: >7「対象レコードの拡大」チェックあり1/2/3/4/5/6/7

複合検索ではFileMaker_Command_FindRequestオブジェクトに条件を指定しますが、最初は結果的にすべてのレコードが対象になるので、「検索条件」メニューの「検索実行」を選択するのと同じことになります。また、最初の検索条件ではisOmit(TRUE)にしても結果は変わらないので、PHPでの複合検索に指定される1つ目の検索はFileMaker上で「検索実行」を行うと見ていいでしょう。引き続くFileMaker_Command_FindRequestオブジェクトに対しては、条件とsetOmit(TRUE)をつけるかどうかの選択しかありません。setOmit(TRUE)を付けなければ、「対象レコードの拡大」で「除外」をチェックしない時と同じ結果(表の3番目)になります。setOmit(TRUE)を付ければ「対象レコードの絞り込み」で「除外」をチェックしたときの結果(表の2番目)と同じになります。

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newCompoundFindCommand( '検索テスト' );

$freq = $db->newFindRequest( '検索テスト' );
$freq->addFindCriterion( 'num', '<3' );
$findCommand->add( 1, $freq );

$freq = $db->newFindRequest( '検索テスト' );
$freq->addFindCriterion( 'num', '>7' );
$findCommand->add( 2, $freq );

$result = $findCommand->execute();
//この結果は、1/2/8/9
require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newCompoundFindCommand( '検索テスト' );

$freq = $db->newFindRequest( '検索テスト' );
$freq->addFindCriterion( 'num', '>3' );
$findCommand->add( 1, $freq );

$freq = $db->newFindRequest( '検索テスト' );
$freq->addFindCriterion( 'num', '<7' );
$freq->setOmit(TRUE);
$findCommand->add( 2, $freq );

$result = $findCommand->execute();
//この結果は、7/8/9

結果的に、FileMaker Pro上で行えるすべての検索指定がFileMaker API for PHPの上でできるわけではありません。複合検索での条件指定の方法が、FileMaker Proを実行する上でのどの処理に対応するかを以下の表にまとめてみました。setOmit(TRUE)をしない場合、「除外」のチェックを入れた状態と同じ動作、つまり条件に合致しないものがレコードのセットに追加されるというあたりが分かりにくいところかと思います。結果的にsetOmitは「対象レコードの拡大」か「対象レコードの絞り込み」を選ぶという機能とも言えますし、「除外」のチェックボックスなのだけどそれに連動してメニューが変わってしまうとも言えるかもしれません。

適用するクラスFileMaker API for PHPでのメソッド「検索条件」メニューの項目除外
FileMaker_Command_FindaddFindCriterion「検索実行」チェックなし
FileMaker_Command_FindRequest
の1つ目
addFindCriterion
(setOmit()は機能しない)
「検索実行」チェックなし
FileMaker_Command_FindRequest
の2つ目以降
addFindCriterion
setOmit(FALSE):既定値
「対象レコードの拡大」チェックなし
FileMaker_Command_FindRequest
の2つ目以降
addFindCriterion
setOmit(TRUE)
「対象レコードの絞り込み」チェックあり

これらの結果を総合すると、複合検索は原則としてORになるということです。たとえば、条件として、A OR B OR Cの場合、A〜Cのそれぞれの条件に対してnewFindRequestメソッドで作ったインスタンスにaddFindCriterionします。しかしながら、これくらいでは複合条件を使う意味はありません。そこで、条件として、ANDとORが混ざったものを考えてみます。フィールドf1、f2に対してSQLのWHERE句的に記述すると「(f1=a1 OR f1=a2) AND (f2=b1)」のような条件があったとします。これを、先にANDが来るようにまとめ直して「(f1=a1 AND f2=b1) OR (f1=a2 AND f2=b1)」のように変更します。こうすれば、複合条件での条件指定ができるようになります。パラメータは若干おおまかではありますが、次のように記述することで、この条件での検索が可能です。

require_once ( 'FileMaker.php' );
$db = new FileMaker( 'any_db', NULL, 'admin', 'testtest');
$findCommand = $db->newCompoundFindCommand( 'search_layout' );

$freq = $db->newFindRequest( 'search_layout' );
$freq->addFindCriterion( 'f1', 'a1' );
$freq->addFindCriterion( 'f2', 'b1' );
$findCommand->add( 1, $freq );

$freq = $db->newFindRequest( 'search_layout' );
$freq->addFindCriterion( 'f1', 'a2' );
$freq->addFindCriterion( 'f2', 'b1' );
$findCommand->add( 2, $freq );

$result = $findCommand->execute();

p290のコラム「時刻データの確認処理」の補足

FileMaker API for PHPの中身について、FileMaker>Implementationとフォルダをたどった先にあるFieldImpl.phpをチェックします。このクラスの比較的後ろの方にあるcheckTimeValidityメソッドは次のようになっています。ここは、書き込み等をしようとする時刻の文字列をチェックする部分です。かなり重大なバグかと思うので、ソースとその修正方法を示す事にします。

function checkTimeValidity($V2063c160, $V981c1e7b, $Vcb5e100e, $Vcaf85b7b) {
	$V52124c01 = 0;
	if ($Vcaf85b7b) {
 		$V52124c01 = 12;
	} else {
		$V52124c01 = 24;
	} 
	ereg('([0-9]{1,2})[:]([0-9]{1,2})([:][0-9]{1,2})?', $V2063c160, $V9c28d32d);
	$V896c55cc = $V9c28d32d[1];
	$V640fd0cc = $V9c28d32d[2];
	if (count($V9c28d32d) > 4) {
		$V783e8e29 = $V9c28d32d[3];
	} 
	if ($V896c55cc < 1 || $V896c55cc > $V52124c01) {
		$Vcb5e100e->addError($this, $V981c1e7b, $V2063c160);
	} else if ($V640fd0cc < 1 || $V640fd0cc > 59) {
		$Vcb5e100e->addError($this, $V981c1e7b, $V2063c160);
	} else
		if (count($V9c28d32d) > 4) {
			if ($V783e8e29 < 1 || $V783e8e29 > 59)
 				$Vcb5e100e->addError($this, $V981c1e7b, $V2063c160);
		}
}

最初の部分で、12時間制か24時間制かを調べています。そして、引き渡された値を正規表現によって分割し、結果を配列で得ています。1番目が時、2番目が分になりますが、この配列の値は4より大きくなることはないと考えます。正規表現の記述が正しくないのです。この正規表現によるマッチングを行ってみました。プログラムと実行結果を示しておきます。

=============================プログラム
<html><body>
<?php
$testData  = array( '10:44', '0:12', '10:44:34', '0:12:8', '10:44:3 AM', '0:12AM', '10:44:22pm' );
foreach( $testData as $s)	{
	ereg('([0-9]{1,2})[:]([0-9]{1,2})([:][0-9]{1,2})?', $s, $a);
	print_r($a);			}
?>
</body></html>

=============================実行結果
Array ( [0] => 10:44 	[1] => 10 [2] => 44 [3] => )
Array ( [0] => 0:12 	[1] => 0 [2] => 12 [3] => )
Array ( [0] => 10:44:34	[1] => 10 [2] => 44 [3] => :34 )
Array ( [0] => 0:12:8 	[1] => 0 [2] => 12 [3] => :8 )
Array ( [0] => 10:44:3 	[1] => 10 [2] => 44 [3] => :3 )
Array ( [0] => 0:12 	[1] => 0 [2] => 12 [3] => )
Array ( [0] => 10:44:22	[1] => 10 [2] => 44 [3] => :22 )

配列の個数をcountして4より大きいという条件判断をしていますが、これによって秒の数値の有無を判断することはできません。秒の指定の有無についてはインデックスが3の要素が空かどうかで判断すべきです。

そして、時、分、秒の順にチェックをして、何か問題があれば、addErrorメソッドを呼び出し判定は終了します。少なくとも、秒についてはチェックはされないというのはここで分かります。時の判断ですが、1より小さくか、あるいは12ないしは24より大きければエラーとしています。これだと、24時間制のときの0時代の時刻はエラーになってしまいます。12時間制で「0時を言わない」としても、今度は12時代でエラーが出ます。(余談ですが、こちらを参照しましょう。)ちなみに、別の部分を見る限りは、時刻の文字列の末尾にスペースを開けてAMやpmといった文字列があれば、12時間制であると判断しています。

いずれにしても、24時間制で処理することにすれば、< 1 の部分は < 0 とすべきでしょう。また、同様に分の判定も、1より小さければエラーになります。これも、0より小さい場合と変更しておくのが良いでしょう。秒の判定はこのままでは同様な問題になりますし、そもそもeregで得られた結果にはコロンが混ざっているので適切なプログラムではありません。ですが、ここまでプログラムの実行には至らないので放置でもいいかと思います。結果的に、2つの「1」を両方とも「0」に変更することで、概ねちゃんと動いてくれるかなというところです。もちろん、24時間制で運用し、秒の判断は行わないという状況においてということになります。

なお、FileMakerへの入力でvalidateメソッドを適用しない状態にして、たとえば、3:65:00のような文字列を与えて時刻フィールドにデータを入れてみてください。「4:05:00」といったデータになるはずです。時間に関するvalidateを使わないのなら、このメソッドの最初にreturnを入れておいてもいいかもしれません。

(2008/9/18公開:本来、プロダクトの中身となるソースについては公開してはいけないと認識しますが、あまりに激しいバグであるのと、対処が可能なので、公開する事にしました。)

書籍で紹介したURL

第1章/FileMaker Serverのセットアッップ

第5章/PHPの基礎

第7章/FileMaker PHP APIによるデータベース処理

第8章/Webアプリケーションの作成

書籍で紹介したプログラム

第5章/PHPの基礎

5-1/PHPプログラミングの基礎

●構文(p146)

<html><body>
これはHTMLです。計算結果は<?php echo 32+4 ?>です。
</body></html>
<?php
echo '<html><body>これはHTMLです。計算結果は', 32+4, 'です。</body></html>';
?>
<?php
echo '<html><body>';
echo 'これはHTMLです。計算結果は', 32+4, 'です。';
echo '</body></html>';
?>

●式と演算子(p150)

$a1 = 5 / 3;	//計算結果は「1.6666666666667」
$a1 = 5 % 3;	//計算結果は「2」
$a1 = 5.2 / 2.3;	//計算結果は「2.2608695652174」
$a1 = 5.2 % 2.3;	//計算結果は「1」つまり、5と2の剰余を求めている
$a1 = fmod( 5.2, 2.3 );	//計算結果は「0.6」

5-2/文字列とmb_string

●ソースの中での文字列表現(p157)

$str = 'レタス';
$message = "あなたのラッキー野菜は、{$str}です。";	// 値は「あなたのラッキー野菜は、レタスです。」
$errormsg = "あなたの好物は、{$vegetable}です。\n";		//値は「あなたの好物は、です。[改行]」

●文字列の結合(p158)

$str1 = '茄子';	$str2 = '焼き';
$cooking1 = $str2 . $str1;	// 式の結果は「焼き茄子」
$cooking2 = "$str2$str1";	// 式の結果は「焼き茄子」
$cooking3 = "{$str1}田楽";	// 式の結果は「茄子田楽」

●文字列の長さと部分文字列(p163)

mb_internal_encoding( 'UTF-8' );
$str = '言語はPHP、サーバはApache';
$message = substr( $str, 3, 3);	// 出力は「語」
$message = mb_substr( $str, 4, 4);	// 出力は「HP、サ」
$message = mb_strcut( $str, 7, 5);	// 出力は「はPH」
$message = mb_strimwidth ( $str , 2 , 6 , '…' );	// 出力は「はPHP…」

●正規表現によるマッチングと置換(p171)

mb_regex_encoding( 'UTF-8' );
$patternEMail = '^([a-z0-9_]|\-|\.)+@(([a-z0-9_]|\-)+\.)+[a-z]+$';
$result = mb_ereg_match( $patternEMail, 'msyk.net' );	//出力はなにもなし
$result = mb_ereg_match( $patternEMail, 'INFO@mail.msyk.net', 'i' );	//出力は「1」
$str = 'これは「赤色」、これは「緑色」、これは「黄色」';
$replaced = mb_ereg_replace ( '「([^」]+)」' , '〈\1〉' , $str );	//出力は「これは〈赤色〉、これは〈緑色〉、これは〈黄色〉」

●数値や文字列の書式化(p173)

setlocale( LC_ALL , 'ja_JP');
$fmtd = money_format( '%=_(#10.2i', -22456.789 );
	//出力は「(JPY_______22,456.79)」
$fmtd = money_format( '%=_#8.2n', 22456.789 );
	//出力は「¥____22,456.79」

●数値や文字列の書式化(p175)

$msg = sprintf( '気温:%2.1f、湿度%2d%%' , 18.38, 62.78 );
	//出力は「気温:18.4、湿度62%」
$msg = sprintf( 'コード番号: %08d、サブコード: %.4s' , 64578, 'AC8734X' );
	//出力は「コード番号: 00064578、サブコード: AC87」

5-3/制御構造、関数、クラス

●複数のファイルにプログラムを記述する(p180)

/*
[Webサーバのルート]
|-- mylib.php
|-- mypage.php
`-- tentativelib
    `-- cutelib.php
*/
//==================mypage.php
<html><body>
<?php
require_once 'mylib.php';
require_once 'tentativelib/cutelib.php';

echo blueDiv( "いらっしゃいませ、{$name}様" );
?>
</body></html>
//==================mylib.php
<?php
require_once 'tentativelib/cutelib.php';

$name = 'お客';

function blueDiv( $str )	{
	return tagElement( 'div', 'style="background-color: blue;"', $str);
}
?>
//==================cutelib.php
<?php
$name = 'ご主人';

function tagElement( $tag, $param, $data )	{
	return "<{$tag}" . ( strlen($param)>0 ? ' '.$param : '' ) . ">{$data}</{$tag}>";
}
?>

●変数のスコープ(p181)

================プログラム
<html><body>
<?php
$number = 1;
?>
現在の変数numberの値:<? echo $number ?><br>
<?php
$number++;
?>
現在の変数numberの値:<?= ++$number ?><br>

</body></html>

=================出力結果
現在の変数numberの値:1
現在の変数numberの値:3

●変数のスコープ(p182)

function MiddleValue( $x, $y)	{
	global $paramIsInteger;
	$midValue = ( $x + $y ) / 2;
	if ( $paramIsInteger )
		$midValue = (int)$midValue;
	return $midValue;
}

$paramIsInteger = FALSE;
$result1 = MiddleValue( 3.5, 5.2 );	//関数の呼び出し結果は「4.35」
$paramIsInteger = TRUE;
$result2 = MiddleValue( 3.5, 5.2 );	//関数の呼び出し結果は「4」

●変数のスコープ(p182)

function AddAndAvarage( $x )	{
	static $myData = 0, $count = 0;
	$myData += $x;		$count++;
	return $myData / $count;
}

echo AddAndAvarage( 3.5 ), '<br>';	//3.5を1で割って3.5を出力
echo AddAndAvarage( 4 ), '<br>';	//3.5と4の平均値として3.75を出力
echo AddAndAvarage( 4.5 ), '<br>';	//3.5と4と4.5の平均値として4を出力
echo $count, $myData;	//何も出力されない

●例外処理(p187)

function integerQuotient ( $dividend, $divisor )	{
	if ( $divisor == 0 )	{
		throw new Exception( 'ゼロでの序算はできません。' );
	}
	return floor( $dividend / $divisor );
}

try	{
	echo integerQuotient ( 4, 3 );
	echo integerQuotient ( 4, 0 );
	echo integerQuotient ( 10, 3 );	//ここは実行されない
} catch ( Exception $ex )	{
	echo $ex->getMessage();	//「ゼロでの序算はできません。」と出力
}

5-4/配列と配列関数

●配列の作成と参照(p191)

$a = array ( 5, 9, 13, 'classroom', 7 );
echo $a[0];	//「5」を出力
echo $a[3];	//「classroom」を出力
echo $a[20];	//何も出力しないがエラーにもならない

●配列の作成と参照(p191)

$a = array ( 5, 9 );
$a[1] = -34;		//既存の要素$a[1](値は9)が上書きされる
$a[] = 120;		//現在の最後の要素$a[1]の次の要素$a[2]が付け加えられる
$a[9] = 333;	//とびとびのインデックスでもかまわない。新しい要素$a[9]が追加される
$a[] = 199;	//$a[9]が最後の要素としてすでに存在するので、新たに$a[10]として要素が追加される
print_r($a);	//「Array ( [0] => 5 [1] => -34 [2] => 120 [9] => 333 [10] => 199 )」と出力
$b[3] = 98;
print_r($b);	//「Array ( [3] => 98 )」と出力

●配列の作成と参照(p192)

$c = array ( 'name' => 'Masayuki Nii', 'handle' => 'msyk', 'dog', 'cat' );
$c[ 'domain' ] = 'msyk.net';	//新たなキーの値を配列に追加する
$c[ 1 ] = 'something';	//既存の要素を書き換えた(元のデータは「cat」)
$c[] = 'anything';	//配列の最後に追加するがインデックスは2となる
print_r( $c );
/*
===================出力結果
Array
(
    [name] => Masayuki Nii
    [handle] => msyk
    [0] => dog
    [1] => something
    [domain] => msyk.net
    [2] => anything
)*/

●配列の中身を順番に処理する(p195)

$ar = array ( 'Windows 2000', 'Windows XP', 'Windows Vista' );
foreach( $ar as $target )	{
	echo $target, '/';
}	
//出力結果は「Windows 2000/Windows XP/Windows Vista/」

$ar = array ( 'Windows 2000' => 1999, 'Windows XP' => 2002, 'Windows Vista' => 2007 );
foreach( $ar as $key => $value )	{
	echo "キー:{$key}, 値:{$value}/";
}	
//出力結果は「キー:Windows 2000, 値:1999/キー:Windows XP, 値:2002/キー:Windows Vista, 値:2007/」

●配列の中身を順番に処理する(p195)

$ar = array ( 'Windows 2000' => 1999, 'Windows XP' => 2002, 'Windows Vista' => 2007 );
foreach( $ar as $target )	{
	echo $target, '/';
}	
//出力結果「1999/2002/2007/」

●文字列の分配と配列の連結(p197)

$htmltag = <<<EOT
<TR><TD>a1</TD><TD>b1</TD><TD>c1</TD></TR>
<TR><TD>a2</TD><TD>b2</TD><TD>c2</TD></TR>
EOT;

$step1 = str_replace( array( "\n", "\r", '<TR>', '</TR>', '</TD>' ), '', $htmltag );
$eachItems = explode( '<TD>', $step1 );
echo implode( ', ', $eachItems ), '<BR>';	//出力は「 , a1, b1, c1, a2, b2, c2」

$str[] = 'Please';		$str[] = 'let';		$str[] = 'me';		$str[] = 'know';
echo implode( ' ', $str );	//出力は「Please let me know」
echo implode( '', $str );	//出力は「Pleaseletmeknow」

print_r (mb_split ( '(<(/|)[^<>]+>\s*){1,}' , $htmltag ) );
	//出力は「Array ( [0] => [1] => a1 [2] => b1 [3] => c1 [4] => a2 [5] => b2 [6] => c2 [7] => )」

●配列同士の演算処理(p198)

$ar1 = array ( 'ギター' => 'Andy', 'ドラム' => 'Andrew' );
$ar2 = array ( 'ベース' => 'Colin', 'キーボード' => 'Peter' );
$ar3 = array ( 'ベース' => 'Richard', 'サックス' => 'Mel' );
print_r( $ar1 + $ar2 );
	// Array ( [ギター] => Andy [ドラム] => Andrew [ベース] => Colin [キーボード] => Peter )
print_r( $ar1 + $ar2 + $ar3 );
	// Array ( [ギター] => Andy [ドラム] => Andrew [ベース] => Colin [キーボード] => Peter [サックス] => Mel )

●配列同士の演算処理(p199)

$ar1 = array ( 'ギター', 'ドラム' );
$ar2 = array ( 'ベース', 'キーボード' );
print_r( $ar1 + $ar2 );	// Array ( [0] => ギター [1] => ドラム )

●配列同士の演算処理(p199)

$ar1 = array ( 'CPU' => 'Core 2 Duo', 'OS' => 'Windows XP', 'Office XP', 'Office 2003' );
$ar2 = array ( 'OS' => 'Windows Vista', 'Office 2007' );
print_r( $ar1 + $ar2 );	
	// Array ( [CPU] => Core 2 Duo [OS] => Windows XP [0] => Office XP [1] => Office 2003 )
print_r( array_merge ( $ar1, $ar2 ) );
	// Array ( [CPU] => Core 2 Duo [OS] => Windows Vista [0] => Office XP [1] => Office 2003 [2] => Office 2007 )

●配列の数を数える(p201)

$ar = array ( '鈴木', '山田', '吉田', '鈴木', '山田', '吉田', '鈴木', '山田', '田中' );
print_r ( array_count_values ( $ar ) );	// Array ( [鈴木] => 3 [山田] => 3 [吉田] => 2 [田中] => 1 )

●配列の値の計算(p201)

function average( $cum, $value )	{
	static	$counter = 0;
	$returnValue = ($cum * $counter + $value) / ++$counter;
	echo "{$counter}回目の呼び出し、\$value = {$value} \$cum = {$cum} \$returnValue = {$returnValue}<BR>";
	return $returnValue;
}
$ar = array( '東京' => 340, '大阪' => 240, '名古屋' => 200 );
echo array_reduce( $ar, "average" );		//出力は「260」
/*
途中で以下のように出力される

1回目の呼び出し、$value = 340 $cum = $returnValue = 340
2回目の呼び出し、$value = 240 $cum = 340 $returnValue = 290
3回目の呼び出し、$value = 200 $cum = 290 $returnValue = 260
*/

●配列の要素を並べ替える(p206)

$ar1 = $ar2 = array( '東京' => 340, '大阪' => 240, '名古屋' => 200 );
$ar3 = $ar4 = array( 4 => 340, 5 => 240, 6 => 200 );
sort( $ar1 );	print_r ( $ar1 );		// Array ( [0] => 200 [1] => 240 [2] => 340 )
asort( $ar2 );	print_r ( $ar2 );	// Array ( [名古屋] => 200 [大阪] => 240 [東京] => 340 )
sort( $ar3 );	print_r ( $ar3 );		// Array ( [0] => 200 [1] => 240 [2] => 340 )
asort( $ar4 );	print_r ( $ar4 );	// Array ( [6] => 200 [5] => 240 [4] => 340 )

●配列の要素を並べ替える(p207)

$ar = array ( 1, 9, '200', 'after', 'before', 'After', 'Before', 'くーらー', 'クーラー', 'クアッド', 'クリスマス' );
sort ( $ar );	print_r ( $ar );
// 200/After/Before/after/before/くーらー/クアッド/クリスマス/クーラー/1/9
sort ( $ar, SORT_NUMERIC );	print_r ( $ar );
// クアッド/クリスマス/クーラー/before/くーらー/After/Before/after/1/9/200
sort ( $ar, SORT_STRING );		print_r ( $ar );
// 1/200/9/After/Before/after/before/くーらー/クアッド/クリスマス/クーラー
setLocale( LC_ALL, 'ja_JP' );
sort ( $ar, SORT_LOCALE_STRING );	print_r ( $ar );
// 1/200/9/After/Before/after/before/くーらー/クアッド/クーラー/クリスマス
natcasesort ( $ar );	print_r ( $ar );
// くーらー/クアッド/クリスマス/クーラー/1/9/200/after/After/Before/before

5-5/日付、時刻

●日付時刻の数値から、年月日や時分秒を取得する(p211)

print_r( localtime( time() ) );
// Array ( [0] => 51 [1] => 58 [2] => 23 [3] => 22 [4] => 3 [5] => 108 [6] => 2 [7] => 112 [8] => 0 )
print_r( localtime( time(), TRUE ) );
// Array ( [tm_sec] => 51 [tm_min] => 58 [tm_hour] => 23 [tm_mday] => 22 [tm_mon] => 3
// 	[tm_year] => 108 [tm_wday] => 2 [tm_yday] => 112 [tm_isdst] => 0 )

●日付時刻の数値から、年月日や時分秒を取得する(p211)

$d = new DateTime( '2008-4-15 10:00' );
echo $d->format( 'Y/m/d H:i:s' ), '<br>';	//出力は「2008/04/15 10:00:00」
$d->modify( 'last Saturday' );
echo $d->format( 'Y/m/d H:i:s' ), '<br>';	//出力は「2008/04/12 10:00:00」

DateTimeクラスのメソッドと関数(p218)

$d = new DateTime( 'last month' );
echo $d->format( 'Y/m/d H:i:s' ), '<br>';	//出力は「2008/03/23 12:03:15」
$year = $d->format( 'Y' );
$month= $d->format( 'm' );
$startOfPrevMonth = new DateTime ( "$year-$month-1" );
echo $startOfPrevMonth->format( 'Y/m/d H:i:s' ), '<br>';	//出力は「2008/03/01 00:00:00」
$lastDay = clone $startOfPrevMonth;
$lastDay->modify( 'yesterday');
echo $lastDay->format( 'Y/m/d H:i:s' ), '<br>';	//出力は「2008/02/29 00:00:00」

DateTimeクラスのメソッドと関数(p220)

$d1 = new DateTime( '2008-4-15 10:00 GMT' );
$d2 = new DateTime( '2008-4-15 10:00' );
echo $d1->format( 'Y/m/d H:i:s T e O U' ), '<br>';	//出力は「2008/04/15 10:00:00 GMT Asia/Tokyo +0000 1208253600」
echo $d2->format( 'Y/m/d H:i:s T e O U' ), '<br>';	//出力は「2008/04/15 10:00:00 JST Asia/Tokyo +0900 1208221200」

$d1->setTimeZone( new DateTimeZone( timezone_name_from_abbr ( $d1->format( 'T' ) ) ) );
echo $d1->format( 'Y/m/d H:i:s T e O U' ), '<br>';	///出力は「2008/04/15 10:00:00 UTC UTC +0000 1208253600」

$d2->setTimezone( new DateTimeZone( 'GMT' ) );
echo $d2->format( 'Y/m/d H:i:s T e O U' ), '<br>';	//出力は「2008/04/15 01:00:00 UTC UTC +0000 1208221200」

第7章/FileMaker PHP APIによるデータベース処理

7-1/FileMakerクラスとFileMaker_Commandクラス

●FileMakerオブジェクトのプロパティ(p253)

$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
print_r( $db->getProperties() );
/* 出力例
Array
(
    [charset] => utf-8
    [locale] => en
    [logLevel] => 3
    [hostspec] => http://localhost
    [recordClass] => FileMaker_Record
    [prevalidate] => 
    [database] => 伝票
    [username] => admin
    [password] => testtest
)

7-2/レコードのデータの取り出し

●レコードからフィールドの値を取り出す(p261)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newFindAllCommand( '商品マスター' );
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
echo '<table>';
$records = $result->getRecords ();
foreach( $records as $oneRecord )	{
	echo '<tr><td>', $oneRecord->getField ( '商品名' ), '</td>';
	echo '<td>', $oneRecord->getField ( '単価' ), '</td></tr>';
}
echo '</table>';

●日付や時刻、タイムスタンプのフィールドの取り扱い(p263)

$x = createDateTimeFromFMField( $oneRecord->getField( '変更日時' ) );

function createDateTimeFromFMField( $dt )	{
	$sp = strpos ( $dt , ' ' );
	$slash = substr_count ( $dt , '/' );
	$colon = substr_count ( $dt , ':' );
	if ( ( $sp !== FALSE ) && ( $slash == 2 ) && ( $colon == 2 ) )	{
		$sep = explode( ' ', $dt );
		$date = explode( '/', $sep[0] );
		return new DateTime( $date[2] . '-' . $date[0] . '-' . $date[1] . ' ' . $sep[1] );
	} elseif ( ( $sp === FALSE ) && ( $slash == 2 ) && ( $colon == 0 ) )	{
		$date = explode( '/', $dt );
		return new DateTime( $date[2] . '-' . $date[0] . '-' . $date[1] );
	} elseif ( ( $sp === FALSE ) && ( $slash == 0 ) && ( $colon == 2 ) )	{
		return new DateTime( $dt );
	}
	return FALSE;
}

●関連レコードやポータルの取得(p265)

require_once ( 'FileMaker.php' );
setLocale( LC_ALL, 'ja_JP' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newFindAllCommand( '伝票' );
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$oneRecord = $result->getFirstRecord();
$issueDate = createDateTimeFromFMField( $oneRecord->getField( '発行日' ) );
echo '発行日:', $issueDate->format( 'Y年m月d日' ), '<br>';
echo '請求先会社名:', $oneRecord->getField( '顧客::会社名' ), '<br>';
echo '請求金額:', money_format( '%#10.0n', $oneRecord->getField( '総計' ) ), '<br>';

foreach( $oneRecord->getRelatedSet( '明細' ) as $oneDetail )	{
	echo '>商品名:', $oneDetail->getField( '商品::商品名' );
	echo '/金額:', money_format( '%#10.0n', $oneDetail->getField( '明細::金額' ) ), '<br>';
}
/*
===============出力結果
発行日:2004年09月02日
請求先会社名:大東亜青果店
請求金額: ¥ 35,196
>商品名:りんご/金額: ¥ 27,920
>商品名:みかん/金額: ¥ 5,600
*/

●検索条件やソート条件を指定して検索を行う(p268)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newFindCommand( '商品マスター' );
//$findCommand->addFindCriterion( '合計売上', '>2000000' );
//$findCommand->addFindCriterion( '合計売上', '>20000000' );
$findCommand->addFindCriterion( '合計売上', '1000000..2000000' );
$findCommand->addSortRule( '合計売上', 1 );
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	if ( $result->code == 401 )
		exit ( "検索しましたがデータは1つもありませんでした。");
	else
		exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$records = $result->getRecords ();
foreach( $records as $oneRecord )	{
	echo 'id:', $oneRecord->getField ( 'id' );
	echo '/商品名:', $oneRecord->getField ( '商品名' );
	echo '/合計売上:', number_format( $oneRecord->getField ( '合計売上' ) ), '<br>';
}

●複合検索(p271)

$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newCompoundFindCommand( '商品' );
/*<商品レイアウトのすべてのレコード>
id:1/商品名:りんご/単価:6,980
id:2/商品名:みかん/単価:2,800
id:3/商品名:バナナ/単価:2,300
id:4/商品名:いちご/単価:800
id:5/商品名:イチジク/単価:7,230
id:6/商品名:スイーティ/単価:6,589
id:7/商品名:グレープフルーツ/単価:4,500
id:8/商品名:キウイ/単価:2,300
id:9/商品名:びわ/単価:12,800
*/
$freq = $db->newFindRequest( '商品' );
$freq->addFindCriterion( '単価', '>3000' );
$freq->addFindCriterion( '商品名', 'ア..ン' );
$findCommand->add( 1, $freq );
/*<ここまでの検索条件を適用した結果>
id:5/商品名:イチジク/単価:7,230
id:6/商品名:スイーティ/単価:6,589
id:7/商品名:グレープフルーツ/単価:4,500
*/
$freq = $db->newFindRequest( '商品' );
$freq->addFindCriterion( '商品名', '*イ*' );
$freq->setOmit( TRUE );
$findCommand->add( 2, $freq );
/*<さらにここまでの検索条件を適用した結果>
id:7/商品名:グレープフルーツ/単価:4,500
*/
$result = $findCommand->execute();
print_r($findCommand->getRelatedSetsFilters());
if ( FileMaker::isError ( $result ) )	{
	if ( $result->code == 401 )
		exit ( "検索しましたがデータは1つもありませんでした。");
	else
		exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$records = $result->getRecords ();
foreach( $records as $oneRecord )	{
	echo 'id:', $oneRecord->getField ( 'id' );
	echo '/商品名:', $oneRecord->getField ( '商品名' );
	echo '/単価:', number_format( $oneRecord->getField ( '単価' ) ), '<br>';
}

●複合検索(p272)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newCompoundFindCommand( '商品' );

$freq = $db->newFindRequest( '商品' );
$freq->addFindCriterion( '単価', '>3000' );
$findCommand->add( 2, $freq );

$freq = $db->newFindRequest( '商品' );
$freq->addFindCriterion( 'id', '>4' );
$findCommand->add( 1, $freq );

$result = $findCommand->execute();
/*<ここまでの検索条件を適用した結果>
id:1/商品名:りんご/単価:6,980
id:5/商品名:イチジク/単価:7,230
id:6/商品名:スイーティ/単価:6,589
id:7/商品名:グレープフルーツ/単価:4,500
id:8/商品名:キウイ/単価:2,300
id:9/商品名:びわ/単価:12,800
*/

●オブジェクトフィールドのデータへのアクセス(p274)

$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$result = $db->getRecordById('商品マスター', 1);
$objectData = $result->getField( '写真' );

●オブジェクトフィールドのデータへのアクセス(p275)

<?php
require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$result = $db->getRecordById('商品マスター', 1);
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
//header('Content-Type: application/pdf');	//取り出したデータがPDFの場合
header('Content-Type: image/jpeg');	//取り出したデータがJPEGの場合

echo $db->getContainerData ($result->getField( '写真' ))
?>

7-3/レコードの作成、削除、変更

●レコードの新規作成(p279)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$newRecord = $db->createRecord( '商品マスター', 
	array( 商品名=>'メロン', 単価=>12800, 変更日=>'12/31/2009', 変更履歴=>array( "最初の行", "次の行" ) ) );
$result = $newRecord->commit();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}

$addRecordCommand = $db->newAddCommand( '商品マスター' );
$addRecordCommand->setField( '商品名', 'レモン' );
$addRecordCommand->setField( '単価', '5400' );
$addRecordCommand->setField( '変更日', '12/31/2009' );
$addRecordCommand->setField( '変更履歴', '新規作成' );
$addRecordCommand->setField( '変更履歴', 'PHPから作成', 1 );
$result = $addRecordCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
//新規作成されたレコードの内容を得ることができる
$oneRecord = $result->getFirstRecord ();
echo 'RecID:', $oneRecord->getRecordId ();
echo '/id:', $oneRecord->getField ( 'id' );
echo '/商品名:', $oneRecord->getField ( '商品名' );
echo '/単価:', number_format( $oneRecord->getField ( '単価' ) ), '<br>';
//出力例 RecID:35/id:35/商品名:レモン/単価:5,400

●ポータルにレコードを作成する(p281)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$newRecord = $db->createRecord( '伝票', array( 伝票名称=>'納品書', 顧客id=>2, 発行日=>'4/30/2008' ) );
$result = $newRecord->commit();
$newPortalRecord = $newRecord->newRelatedRecord( '明細' );
$newPortalRecord->setField( '明細::商品id', '5' );
$newPortalRecord->setField( '明細::数量', '3' );
$newPortalRecord->commit();
$newPortalRecord = $newRecord->newRelatedRecord( '明細' );
$newPortalRecord->setField( '明細::商品id', '7' );
$newPortalRecord->setField( '明細::数量', '12' );
$newPortalRecord ->commit();
$newPortalRecord = $newRecord->newRelatedRecord( '明細' );
$newPortalRecord->setField( '明細::商品id', '8' );
$newPortalRecord->setField( '明細::数量', '6' );
$newPortalRecord->commit();

$findCommand = $db->newFindCommand( '伝票' );
$findCommand->setRecordId( $newRecord->getRecordId () );
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$oneRecord = $result->getFirstRecord();
echo 'RecID:', $oneRecord->getRecordId ();
echo '/id:', $oneRecord->getField ( 'id' );
echo '/顧客:', $oneRecord->getField ( '顧客::会社名' );
echo '/総計:', number_format( $oneRecord->getField ( '総計' ) ), '<hr>';

●レコードの削除(p283)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newFindAllCommand( '商品' );
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
foreach ( $result->getRecords() as $oneRecord )	{
	$id = $oneRecord->getField ( 'id' );
	if ( ( $id >=20 ) && ( $id <30 ) )	{
		$delResult = $oneRecord->delete();
		if ( FileMaker::isError ( $delResult ) )	{
			exit ( "エラーコード:{$delResult->code}<br>エラーメッセージ:{$delResult->getErrorString()}");
		}
	}
}

7-4/その他の機能

●データベースへ入力する値の確認(p291)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$newRecord = $db->createRecord( '伝票', array( id=>'aaa', 伝票名称=>'123456', 発行日=>'2008/1/1' ) );
$resultOfValidate = $newRecord->validate();
if ( FileMaker::isError( $resultOfValidate ) &&  $resultOfValidate->isValidationError () )	{
	echo "フィールドに入力される値の制限を{$resultOfValidate->numErrors()}個検知しました。<br>";
	foreach( $resultOfValidate->getErrors() as $oneError )	{
		echo "→対象フィールド:{$oneError[0]->getName()}<br>";
		echo " 確認項目:{$oneError[1]}<br>";
		echo " 設定値:{$oneError[2]}<br>";
	}
}
/*
================出力例
フィールドに入力される値の制限を3個検知しました。
→対象フィールド:id
 確認項目:2
 設定値:aaa
→対象フィールド:伝票名称
 確認項目:3
 設定値:123456
→対象フィールド:発行日
 確認項目:7
 設定値:2008/1/1
*/

●値の確認に独自のルールを適用する(p294)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$newRecord = $db->createRecord( '伝票', array( 顧客id=>'999', 発行日=>'1/31/2008' ) );

$fields = $newRecord->getLayout()->getFields();

$validatingData = $newRecord->getField( '顧客id' );
$resultOfValidate = $newRecord->validate();
if( $validatingData<1 or $validatingData>100 )
	$resultOfValidate->addError ($fields['顧客id'], 101, $validatingData);

$resultOfValidate = $fields[ '伝票名称' ]->validate( '123456', $resultOfValidate );
$resultOfValidate = $fields[ '発行日' ]->validate( '1/21/2008', $resultOfValidate );

if ( FileMaker::isError( $resultOfValidate ) &&  $resultOfValidate->isValidationError () )	{
	echo "フィールドに入力される値の制限を{$resultOfValidate->numErrors()}個検知しました。<br>";
	foreach( $resultOfValidate->getErrors() as $oneError )	{
		echo "→対象フィールド:{$oneError[0]->getName()}<br>";
		echo " 確認項目:{$oneError[1]}<br>";
		echo " 設定値:{$oneError[2]}<br>";
	}
}
/*
===========出力例
フィールドに入力される値の制限を3個検知しました。
→対象フィールド:id
 確認項目:1
 設定値:
→対象フィールド:顧客id
 確認項目:101
 設定値:999
→対象フィールド:伝票名称
 確認項目:3
 設定値:123456
*/

●値の確認に独自のルールを適用する(p295)

require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$newRecord = $db->createRecord( '伝票', array( 顧客id=>'3', 発行日=>'1/11/2008' ) );

$resultOfValidate = $newRecord->validate();

$fields = $newRecord->getLayout()->getFields();
$validatingData = $newRecord->getField( '顧客id' );
if( $validatingData<1 or $validatingData>100 )
	$resultOfValidate->addError ($fields['顧客id'], 101, $validatingData);

if ( $resultOfValidate->numErrors() < 2 )
	$resultOfValidate = TRUE;

if ( $resultOfValidate !== TRUE )	{
	echo 'フィールドに入力される値の制限を' . ($resultOfValidate->numErrors()-1). '個検知しました。<br>';
	foreach( array_slice( $resultOfValidate->getErrors(), 1) as $oneError  )	{
		echo "→対象フィールド:{$oneError[0]->getName()}<br>";
		echo " 確認項目:{$oneError[1]}<br>";
		echo " 設定値:{$oneError[2]}<br>";
	}
	exit;
}
$result = $newRecord->commit();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}

●ログの出力(p297)

require_once ( 'Log-1.10.0/Log.php' );
$logFile = Log::factory('file', '/tmp/fmsphp.log', 'FileMaker PHP');
/*$logFile = Log::factory('mail', 'msyk@msyk.net', 'FileMaker PHP', 
		array( subject=>'FM PHP Debug', from=>'msyk@msyk.net')); 
*/
$db = new FileMaker( '伝票 ', NULL, 'admin', 'testtest');
$db->setProperty( 'locale', 'jp' );
$db->setProperty( 'logLevel', FILEMAKER_LOG_DEBUG );
$db->setLogger( $logFile );

第8章/Webアプリケーションの作成

8-2/HTTPプロトコル

●クッキーを利用する(p325)

<?php
if ( $_COOKIE[ 'counter' ] == '' )	{
	setcookie ( 'counter' , 200, time() + 60 * 60 * 24 * 10 );
	$message = '設定なし';
} else		{
	setcookie ( 'counter' , $_COOKIE[ 'counter' ] + 1 );
	$message = $_COOKIE[ 'counter' ];
}
?>
<html><body>
現在のクッキーのcounterの値は<?= $message ?>です。
</body></html>

●セッションを利用する(p327)

<?php
session_start();
if ( ! isset ( $_SESSION[ 'counter' ] ) )	{
	$_SESSION[ 'counter' ] = 200;
	$message = '設定なし';
} else		{
	$_SESSION[ 'counter' ] ++;
	$message = $_SESSION[ 'counter' ];
}
?>
<html><body>
現在のセッションから取得したcounterの値は<?= $message ?>です。
</body></html>

●クッキーを使わないセッション(p329)

<?php
session_start();
if ( ! isset ( $_SESSION[ 'counter' ] ) )	{
	$_SESSION[ 'counter' ] = 200;
	$message = '設定なし';
} else		{
	$_SESSION[ 'counter' ] ++;
	$message = $_SESSION[ 'counter' ];
}
?>
<html><body>
現在のセッションから取得したcounterの値は<?= $message ?>です。
<?php
$myUrl = $_SERVER[ 'PHP_SELF' ] . '?PHPSESSID=' . session_id();
echo "<a href='$myUrl'>ここをクリック</a>";
?>
</body></html>

8-3/フォーム作成とフォーム処理

●テキストとHTMLテキストの変換を行う機能(p338)

$str = '<div><a href="http://msyk.net">こちら</a>をドラッグ&ドロップしてください。</div>';
echo htmlspecialchars( $str );
//関数の出力:<div><a href="http://msyk.net">こちら</a>をドラッグ&amp;ドロップしてください。</div>
//画面に見える文字列:<div><a href="http://msyk.net">こちら</a>をドラッグ&ドロップしてください。</div>

echo htmlentities( $str );
//……文字化け……
echo htmlentities( $str, ENT_QUOTES, 'UTF-8', true );
//<div><a href="http://msyk.net">こちら</a>をドラッグ&amp;ドロップしてください。</div>
echo htmlentities( $str, ENT_QUOTES, 'UTF-8', false );
//<div><a href="http://msyk.net">こちら</a>をドラッグ&ドロップしてください。</div>

●テーブルの内容によるポップアップメニューと値一覧(p343)

error_reporting (E_ALL ^ E_NOTICE);
require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
$findCommand = $db->newFindCommand( '商品' );
$findCommand->addFindCriterion( '単価', '>5000' );
$findCommand->addFindCriterion( 'id', '<10' );
$findCommand->addSortRule( 'id', 1 );
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	if ( $result->code == 401 )
		exit ( "検索しましたがデータは1つもありませんでした。");
	else
		exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$records = $result->getRecords ();
$quote = '"';
echo '<select name="choice4">';
foreach( $records as $oneRecord )	{
	echo '<option value=' , $quote, $oneRecord->getField ( 'id' ), $quote , '>';
	echo $oneRecord->getField ( '商品名' ), '</option>';
}
echo '</select>';

●テーブルの内容によるポップアップメニューと値一覧(p344)

error_reporting (E_ALL ^ E_NOTICE);
require_once ( 'FileMaker.php' );
$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');

$findCommand = $db->newFindAllCommand( '商品' );	//商品テーブルをすべて読み込む
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	if ( $result->code == 401 )
		exit ( "検索しましたがデータは1つもありませんでした。");
	else
		exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$records = $result->getRecords ();	//商品テーブルの内容をid=>商品名となるように連想配列に入れておく
foreach( $records as $oneRecord )	{
	$master[ $oneRecord->getField( 'id' ) ] = $oneRecord->getField( '商品名' );
}

$layout = $db->getLayout ( '伝票' );	//伝票レイアウトの情報を取得
if ( FileMaker::isError ( $layout ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$relSet = $layout->getRelatedSet( '明細' );	//伝票レイアウトにあるポータルの「明細」を取得
if ( FileMaker::isError ( $relSet ) )	{
	exit ( "エラーコード:{$relSet->getCode()}<br>エラーメッセージ:{$relSet->getErrorString()}");
}
$vList = $layout->getValueList( '商品一覧', 1 );	//ポータルで使っている値一覧を取得する
$quote = '"';
echo '<select name="choice5">';
foreach ( $vList as $oneItem )	{
	echo '<option value=' , $quote, $oneItem, $quote , '>';	//valueは値一覧の値を使う
	echo $master[ $oneItem ], '</option>';	//選択肢に使うのは商品テーブルから作っておいた連想配列
}
echo '</select>';
/*
=======================実行後に作られたタグの例
<select name="choice5">
	<option value="2">みかん</option>
	<option value="4">いちご</option>
	<option value="6">スイーティ</option>
	<option value="8">キウイ</option>
</select>
*/

8-4/ユーザ認証とセキュリティ

●フォーム入力による認証(p348)

<?php
error_reporting (E_ALL ^ E_NOTICE);
session_start();
if ( Auth_HTTP() )	{
	/* ページの中身 */
	echo "<html><body>OK: {$_SESSION[ 'username' ]}</body></html>";
}

//認証を判断する関数
function Auth_Form()	{
	if (	isset ( $_SESSION[ 'username' ] ) ) {	
		return TRUE;
	} elseif ( isset( $_POST['user'] ) )	{
		require_once ( 'FileMaker.php' );
		$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
		$findCommand = $db->newFindCommand( 'ユーザ' );
		$findCommand->addFindCriterion( 'ユーザ名', '==' . $_POST['user'] );
		$findCommand->addFindCriterion( 'パスワード', '==' . $_POST['pw'] );
		$result = $findCommand->execute();
		if ( ! FileMaker::isError ( $result ) )	{	//レコードが存在しない場合はエラーになる
			$_SESSION[ 'username' ] = $_POST['user'];
			return TRUE;
		}
	}
	echo Get_Auth_Page();
	return FALSE;
}

//ログインフォームを表示するページのHTMLを返す関数
function Get_Auth_Page()	{
return <<<EOT
<html><body>
<form method="POST" action="{$_SERVER['PHP_SELF']}">
<input type="text" name="user">
<input type="password" name="pw">
<button type="submit">Login</button>
</form>
</body></html>
EOT;
}
?>

●HTTP認証(p352)

<?php
error_reporting (E_ALL ^ E_NOTICE);
session_start();
if ( Auth_HTTP() )	{
/* ページの中身 */
$status = "{$_SESSION[ 'username' ]}/{$_SERVER['PHP_AUTH_USER']}/{$_SERVER['PHP_AUTH_PW']}";
echo "<html><body>OK: $status</body></html>";
}

function Auth_HTTP()	{
	if (	isset ( $_SESSION[ 'username' ] )	//ユーザ認証が通っていれば
		&& ( $_SERVER['PHP_AUTH_USER'] == $_SESSION[ 'username' ] ) ) {
		return TRUE;
	} elseif ( isset( $_SERVER['PHP_AUTH_USER'] ) )	{
		require_once ( 'FileMaker.php' );
		$db = new FileMaker( '伝票', NULL, 'admin', 'testtest');
		$findCommand = $db->newFindCommand( 'ユーザ' );
		$findCommand->addFindCriterion( 'ユーザ名', '==' . $_SERVER['PHP_AUTH_USER'] );
		$findCommand->addFindCriterion( 'パスワード', '==' . $_SERVER['PHP_AUTH_PW'] );
		$result = $findCommand->execute();
		if ( ! FileMaker::isError ( $result ) )	{	//レコードが存在しない場合はエラーになる
			$_SESSION[ 'username' ] = $_SERVER['PHP_AUTH_USER'];
			return TRUE;
		}
	}
	header( 'WWW-Authenticate: Basic realm="FileMaker Server DB"');
	header( 'HTTP/1.0 401 Unauthorized' );
	return FALSE;
}
?>

●FileMakerのユーザで認証する(p355)

<?php
error_reporting (E_ALL ^ E_NOTICE);
session_start();
if ( Auth_Form() )	{
	/* ページの中身 */
	echo "<html><body>Another OK: {$_SESSION[ 'username' ]}</body></html>";
}

function Auth_Form()	{
	if (	isset ( $_SESSION[ 'username' ] ) ) {	
		return TRUE;
	} elseif ( isset( $_POST['user'] ) )	{
		require_once ( 'FileMaker.php' );
		$db = new FileMaker( '伝票', NULL, $_POST['user'], $_POST['pw']);
		$findCommand = $db->newFindAnyCommand( '伝票' );
		$result = $findCommand->execute();
		if ( ! FileMaker::isError ( $result ) )	{	//レコードが存在しない場合はエラーになる
			$_SESSION[ 'username' ] = $_POST['user'];
			return TRUE;
		}
	}
	echo Get_Auth_Page();
	return FALSE;
}

function Get_Auth_Page()	{
return <<<EOT
<html><body>
<form method="POST" action="{$_SERVER['PHP_SELF']}">
<input type="text" name="user">
<input type="password" name="pw">
<button type="submit">Login</button>
</form>
</body></html>
EOT;
}
?>

●Captchaをフォームに含める(p357)

<?php
session_start();
if ( Auth_HTTP() )	{
	/* ページの中身 */
	$sInfo = "{$_SESSION[ 'username' ]}/{$_SESSION[ 'captcha_keystring' ]}";
	echo "<html><body>Another OK: $sInfo</body></html>";
	session_destroy();
}

function Auth_Form()	{
	if (	isset ( $_SESSION[ 'username' ] ) ) {	
		return TRUE;
	} elseif ( isset( $_POST['user'] ) )	{
		if ( isset($_SESSION['captcha_keystring']) && 	//Captchaの文字入力をチェック
				$_SESSION['captcha_keystring'] ==  $_POST['keystring'])	{
			require_once ( 'FileMaker.php' );
			$db = new FileMaker( '伝票', NULL, $_POST['user'], $_POST['pw']);
			$findCommand = $db->newFindAnyCommand( '伝票' );
			$result = $findCommand->execute();
			if ( ! FileMaker::isError ( $result ) )	{	//レコードが存在しない場合はエラーになる
				$_SESSION[ 'username' ] = $_POST['user'];
				return TRUE;
			}
		}
	}
	echo Get_Auth_Page();
	return FALSE;
}

function Get_Auth_Page()	{
$imagepath = 'kcaptcha/index.php?' . session_name(). '=' . session_id();
//文字列の例:kcaptcha/index.php?PHPSESSID=pkfa4l9hl27bdbqc3bpa14hel1
return <<<EOT
<html><body>
<form method="POST" action="{$_SERVER['PHP_SELF']}">
<input type="text" name="user">
<input type="password" name="pw">
<img src="$imagepath">
<input type="text" name="keystring">
<button type="submit">Login</button>
</form>
</body></html>
EOT;
}
?>

8-5/レコードの一覧を複数ページで表示する

●リストを表示するページ「db_list.php」(p365)

<html><body>
<?php
error_reporting (E_ALL ^ E_NOTICE);
if ( isset( $_GET[ 'start' ] ) )	{
	$recStart = $_GET[ 'start' ];
	if ( $recStart < 0 )	$recStart = 0;
} else
	$recStart = 0;
if ( isset( $_GET[ 'rnum' ] ) )
	$recPerPage = $_GET[ 'rnum' ];
else
	$recPerPage = 10;

require_once ( 'FileMaker.php' );
$db = new FileMaker( '郵便番号', NULL, 'admin', 'testtest');
$findCommand = $db->newFindCommand( 'KEN_ALL' );
$findCommand->addFindCriterion( '都道府県', '==東京都' );
$findCommand->addSortRule( '郵便番号', 1 );
$findCommand->setRange ( $recStart, $recPerPage );
$result = $findCommand->execute();
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
$countAll = $result->getFoundSetCount ();
$countCurrent = $result->getFetchCount ();
$records = $result->getRecords ();

echo Link_or_Not( $recStart != 0, '最初へ', 
		$_SERVER[ 'PHP_SELF' ] . '?start=0&rnum='. $recPerPage );
echo Link_or_Not( $recStart != 0, '前へ', 
		$_SERVER[ 'PHP_SELF' ] . '?start='. ($recStart - $recPerPage) .'&rnum=' . $recPerPage );
echo Link_or_Not( $recStart < ($countAll - $countCurrent), '次へ', 
		$_SERVER[ 'PHP_SELF' ] . '?start='. ($recStart + $recPerPage) .'&rnum=' . $recPerPage );
echo Link_or_Not( $recStart < ($countAll - $countCurrent), '最後へ', 
		$_SERVER[ 'PHP_SELF' ] . '?start=' . ( (floor( ($countAll - 1 ) / $recPerPage)) * $recPerPage ) 
			.'&rnum=' . $recPerPage );
echo "- {$countAll} レコード中、{$recStart} 番目から {$countCurrent} レコードを表示";
echo '<table border="1"><tr><th></th><th>郵便番号</th><th>都道府県</th><th>市区町村</th><th>町域名</th></tr>';
foreach( $records as $oneRecord )	{
	echo '<tr>';
	echo '<td>', Link_or_Not( TRUE, '詳細', 
		'db_detail.php?recid=' . $oneRecord->getRecordId() . '&' . $_SERVER[ 'QUERY_STRING' ] ), '</td>';
	echo '<td>', $oneRecord ->getField ( '郵便番号' ), '</td>';
	echo '<td>', $oneRecord ->getField ( '都道府県' ), '</td>';
	echo '<td>', $oneRecord ->getField ( '市区町村' ), '</td>';
	echo '<td>', $oneRecord ->getField ( '町域名' ), '</td>';
	echo '</tr>';
}	
echo '</table>';

//引数に応じてリンクしたテキストないしは、単なるテキストを返す
function Link_or_Not( $test, $str, $linkAddr )	{
	$q = '"';		//ダブルクォート1文字だけを文字列
	if( $test )
		return "[<a href={$q}{$linkAddr}{$q}>$str</a>]";
	else
		return "[{$str}]";
}
?>
</body></html>

●レコードごとの表示とレコードの変更が可能なdb_detail.php(p368)

<html><body>
<?php
error_reporting (E_ALL ^ E_NOTICE);
require_once ( 'FileMaker.php' );
$db = new FileMaker( '郵便番号', NULL, 'admin', 'testtest');

$q = '"';		//ダブルクォート1文字だけを文字列
$thisActionAddr = "{$_SERVER[ 'SELF_PHP' ]}?{$_SERVER[ 'QUERY_STRING' ]}";
$retAddr = "db_list.php?start={$_GET[ 'start' ]}&rnum={$_GET[ 'rnum' ]}";
echo "<div><a href={$q}{$retAddr}{$q}>戻る</a></div>";

$mode = $_POST[ 'mode' ];

if ( $mode == 'commit' )	{
	$editCommand = $db->newEditCommand( 'KEN_ALL', $_GET[ 'recid' ] );
	$editCommand->setField( 'メモ', stripslashes($_POST[ 'memo' ]) );
	$editCommand->setModificationId( $_POST[ 'modid' ] );
	$result = $editCommand->execute();
	if ( FileMaker::isError ( $result ) )	{
		exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
	}
}
$result = $db->getRecordById( 'KEN_ALL', $_GET[ 'recid' ] );
if ( FileMaker::isError ( $result ) )	{
	exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
}
echo "<form method={$q}POST{$q} action={$q}$thisActionAddr{$q}>";
echo '<table border="1">';
echo '<tr><th>郵便番号</th><td>', $result ->getField ( '郵便番号' ), '</td></tr>';
echo '<tr><th>都道府県</th><td>', $result ->getField ( '都道府県' ), '</td></tr>';
echo '<tr><th>市区町村</th><td>', $result ->getField ( '市区町村' ), '</td></tr>';
echo '<tr><th>町域名</th><td>', $result ->getField ( '町域名' ), '</td></tr>';
echo '<tr><th>メモ</th><td>';
if ( $mode == 'edit' )	{
	echo '<textarea name="memo" rows="4" cols="30">';
	echo $result ->getField ( 'メモ' ), '</textarea>';
} else
	echo nl2br( $result ->getField ( 'メモ' )  );
/*	テキストフィールドを使う場合
if ( $mode == 'edit' )
	echo '<input name="memo" value=' . $q . $result ->getField ( 'メモ' ). $q . '>';
else
	echo $result ->getField ( 'メモ' );
*/
echo '</td></tr>';
echo '<tr><th></th><td>';
$nextMode = $mode == 'edit' ? 'commit' : 'edit';
echo "<input type={$q}hidden{$q} name={$q}mode{$q} value={$q}{$nextMode}{$q}>";
echo "<input type={$q}hidden{$q} name={$q}recid{$q} value={$q}{$result ->getRecordId()}{$q}>";
echo "<input type={$q}hidden{$q} name={$q}modid{$q} value={$q}{$result ->getModificationId()}{$q}>";
echo '<button type="submit">', ($mode == 'edit' ? '確定' : '編集') , '</button>';
echo '</td></tr>';
echo '</table></form>';
?>
</body></html>

8-6/メール送信を伴うアプリケーション

●メール送信アプリケーションのソース(p375)

<html><body>
<?php
error_reporting (E_ALL ^ E_NOTICE);
mb_language( 'ja' );
mb_internal_encoding( 'UTF-8' );
require_once ( 'FileMaker.php' );
$q = '"';
if ( isset( $_POST[ 'proceed' ] ) )	{
	$message = '';
	if ( $_POST[ 'name' ] == '' )
		$message .= '「名前」が空欄になっています。必ず記入してください。<br>';
	if ( $_POST[ 'email' ] == '' )
		$message .= '「メールアドレス」が空欄になっています。必ず記入してください。<br>';
	if( ! eregi ( '^([a-z0-9_]|\-|\.)+@(([a-z0-9_]|\-)+\.)+[a-z]+$', $_POST[ 'email' ] ) )
		$message .= '「メールアドレス」が正しい形式になっていません。<br>';

	if ( $message == '' )	{
		$db = new FileMaker( '参加者管理', NULL, 'admin', 'testtest');
		$newRecord = $findCommand = $db->createRecord( '参加申し込み', 
			array( '名前'=>$_POST[ 'name' ], 'メール'=>$_POST[ 'email' ], 'イベントid'=>$_POST[ 'eventid' ] ) );
		$result = $newRecord->commit();
		if ( FileMaker::isError ( $result ) )	{
			exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
		}
		$eventInfo = Get_Event( $_POST[ 'eventid' ] );
		$body = "{$_POST[ 'name' ]}様\n以下のイベントの参加申し込みを受け付けました。\n\n"
			. " イベント名:{$eventInfo[0]}\n"
			. " 開催日:{$eventInfo[1]}\n";	
		$aheaders = 
			'From: ' . mb_encode_mimeheader( '受付担当', 'ISO-2022-JP', 'B' ). " <support@msyk.net>\n"
			. 'Cc: ' . mb_encode_mimeheader( '受付担当', 'ISO-2022-JP', 'B' ). ' <support@msyk.net>';
		$aparam = '-f msyk@msyk.net';
		if ( mb_send_mail ( $_POST[ 'email' ] , 'イベント申し込みOK' , $body , $aheaders, $aparam ) )
			echo '申し込みは受け付けられました。メールを送りました。';
		else
			echo '申し込みは受け付けられました。メールの送信はなぜかだめでした。';
		exit;
	} else {
		$message = "<div style={$q}color: red;{$q}>$message</div>";
	}
}
?>
<h1>参加申し込み</h1>
<?= $message ?>
<form method="post" action="<?= $_SERVER[ 'PHP_SELF' ]?>">
お名前:<input type="text" name="name" value="<?= htmlspecialchars($_POST[ 'name' ]) ?>"><br>
メールアドレス:<input type="text" name="email" value="<?= htmlspecialchars($_POST[ 'email' ]) ?>"><br>
参加イベント:<select name="eventid">
<?php
$eventInfo = Get_Event();
foreach ( $eventInfo as $eventId => $oneEvent )
	echo "<option value={$q}{$eventId}{$q}>$oneEvent</option>";
?>
</select><br>
<input type="hidden" name="proceed" value="gogo!">
<button type="submit">申し込む</button>
</form>

<?php
function Get_Event( $multiDimension = '' )	{
	$db = new FileMaker( '参加者管理', NULL, 'admin', 'testtest');
	$findCommand = $db->newFindCommand( 'イベント' );
	$findCommand->addFindCriterion( '受付終了', '==' );
	$findCommand->addSortRule( '開催日', 1 );
	if ( $multiDimension != '' )
		$findCommand->addFindCriterion( 'id', $multiDimension );
	$result = $findCommand->execute();
	if ( FileMaker::isError ( $result ) )	{
		exit ( "エラーコード:{$result->getCode()}<br>エラーメッセージ:{$result->getErrorString()}");
	}
	if ( $multiDimension == '' )	{
		$records = $result->getRecords ();
		foreach( $records as $oneRecord )	{
			$targetDate = createDateTimeFromFMField( $oneRecord->getField( '開催日' ) );
			$eventAr [ $oneRecord->getField ( 'id' )] =
					$oneRecord->getField ( 'イベント名' ) . ' (' . $targetDate->format( 'Y年m月d日' ) . ')';
		}
	} else {
		$oneRecord = $result->getFirstRecord ();
		$targetDate = createDateTimeFromFMField( $oneRecord->getField( '開催日' ) );
		$eventAr = array( $oneRecord->getField ( 'イベント名' ), $targetDate->format( 'Y年m月d日' ) );
	}
	return $eventAr;
}
function createDateTimeFromFMField( $dt )	{
	$sp = strpos ( $dt , ' ' );
	$slash = substr_count ( $dt , '/' );
	$colon = substr_count ( $dt , ':' );
	if ( ( $sp !== FALSE ) && ( $slash == 2 ) && ( $colon == 2 ) )	{
		$sep = explode( ' ', $dt );
		$date = explode( '/', $sep[0] );
		return new DateTime( $date[2] . '-' . $date[0] . '-' . $date[1] . ' ' . $sep[1] );
	} elseif ( ( $sp === FALSE ) && ( $slash == 2 ) && ( $colon == 0 ) )	{
		$date = explode( '/', $dt );
		return new DateTime( $date[2] . '-' . $date[0] . '-' . $date[1] );
	} elseif ( ( $sp === FALSE ) && ( $slash == 0 ) && ( $colon == 2 ) )	{
		return new DateTime( $dt );
	}
	return FALSE;
}
?>