Chapter 4
コンテキストに対する理解を深める

この章は、INTER-Mediator Ver.12をもとに記載しました。

3章まででシンプルなWebページを構築して、INTER-Mediatorの動作を見てもらいました。この章では、定義ファイルやページファイルの設定をさらに細かく見ていきます。定義ファイルのもっとも重要な設定要素は、「コンテキスト」です。コンテキストをどのように作成するのかが、INTER-Mediatorでのサイト制作のポイントになります。

4-1ターゲット指定

ページファイルでは、フィールドのデータを表示するためにタグのdata-im属性に指定を行うことを説明しましたこの指定は単にテキストを要素に設定するだけでなく、さまざまなバリエーションを持っています。それらを説明し、実際に演習で確認してみましょう。

リンクノードにおけるターゲット指定

 data-im属性を指定して、データベースのフィールドとバインドした要素を「リンクノード」と呼ぶことはすでに説明した通りです。このdata-im属性の値を「ターゲット指定」と呼ぶことにします。つまり、この値は、データベースやあるいは属性の何に対する設定なのかを示すものということで、「ターゲット」という名前をつけています。

 「ターゲット指定」は「コンテキスト名@フィールド名@ターゲット」が基本的な形式です。コンテキスト名と、コンテキストより得られるリレーションに含まれるフィールド名は、必ず指定します。最後の「@ターゲット」は省略可能です。本コースのこれまでの箇所では、すべて「@ターゲット」部分を省略していました。複数のターゲット指定をdata-im属性の値に設定することも可能です。その場合は、半角のスペースで区切ります。リスト4-1-1は、2つのターゲット指定を持つリンクノードの例です。

リスト4-1-1 2つのターゲット指定を持つリンクノードの例
<span data-im="person@name person@bgcolor@style.backgroundColor"></span>

 「@ターゲット」の部分に指定可能な要素は、表4-1-1に示しました。ターゲット指定の「@ターゲット」部分を省略した場合の動作を説明します。この場合、通常は、その要素のテキスト要素として、フィールドの値を設置します。DIVやH1などのタグではフィールドの値をそのタグ要素のテキスト要素として設定します。

 一方、フォームで使うINPUT、SELECT、TEXTAREAでは「@ターゲット」部分を省略した場合、そのタグの種類に応じて、適切な属性やあるいはテキスト要素への設定を行います。例えば、type="text"のINPUTタグ要素やSELECT要素の場合は、value属性に設定します。チェックボックスやラジオボタンの場合は、value属性と比較をして、チェックのオン/オフを行います。TEXTAREAタグでは、テキスト要素として設定をします。つまり、データが、ページ上に見えるようにするには、一般には、「@ターゲット」部分を省略すればよいということです。

@ターゲット動作
省略テキストノードあるいはvalue属性などの“目に見える状態”にする
属性名その要素の指定した属性に、フィールドの値を設定する
style.スタイル名スタイル名はJavaScriptでのスタイル名。フィールドの値を指定したスタイル属性として設定する
innerHTMLフィールドの値を、その要素のinnerHTMLプロパティへ設定する
nodeTextフィールドの値を、その要素のnodeTextプロパティへ設定する(基本動作は省略時と同様)
#____ターゲット指定を#で始めると、現在のデータに追記する
$____ターゲット指定を$で始めると、現在のデータの中にある$を、フィールドのデータに置き換える。複数のターゲット指定がある場合、前から順番に処理される
表4-1-1 @ターゲットに指定可能な要素

 リスト4-1-2には、リンクノードと、フィールドのデータを合成した結果の例を記載します。→の右側が、フィールドのデータを合成した結果で、変更されたタグの記述が実際にページ上で解釈されることになります。その結果、データベースの内容を表示することはもちろん、データベースの内容によってスタイルを変更したり、あるいは関数呼び出しのパラメーターの指定もできるようになっています。

リスト4-1-2 ターゲット指定の例(フィールド値がvalだったとする)
<td data-im="tbl@f1"></td>
 → <td data-im="tbl@f1">val</td>
<td data-im="tbl@f1@align"></td>
 → <td data-im="tbl@f1" align="val"></td>
<td data-im="tbl@f1@innerHTML"></td>
 → <td data-im="tbl@f1">*val*</td>
<td data-im="tbl@f1@style.backgroudColor"></td>
 → <td data-im="tbl@f1" style="background-color: val;"></td>
<input type="text" data-im="tbl@f1"/>
 → <input type="text" data-im="tbl@f1" value="val"/>
<td class="cell " data-im="tbl@f1@#class"></td>
 → <td class="cell val" data-im="tbl@f1@#class"></td>
<td class="cell " data-im="tbl@f1@#class tbl@f2"></td>
 → <td class="cell  val1" data-im="tbl@f1@#class tbl@f2">val2</td>
<span onclick="doClick($)" data-im="tbl@f1@$onclick"></span>
 → <span onclick="doClick(val)" data-im="tbl@f1@$onclick"></span>

FileMakerの場合のターゲット指定のフィールド名

 FileMakerでは、ひとつのレイアウトに複数のTO(リレーションシップのタブで定義するボックス)に含まれるフィールドを配置します。レイアウトに割り当てられたTOにあるフィールドは、単にフィールド名のみで指定できます。しかしながら、レイアウトに割り当てたTOとは異なるTOのフィールドの場合、原則として「TO名::フィールド名」の形式で記述します。

 したがって、TO名が長いと、ターゲット指定が「Patient@病棟マスター_表示用::背景色@style.backgroundColor」のようなとてつもなく長い表示になりがちです。この場合、Patientコンテキストが指定するレイアウトに割り当てたTOとは別の「病棟マスター_表示用」という名前のTOにある「背景色」フィールドを、このタグ要素の背景色に設定するという設定となります。このセクションの最後に紹介したエイリアス名を定義する方法もありますが、長い名前がページファイルから定義ファイルに移るだけで、長い記述をしなくても済むわけではありません。

ターゲット指定についての注意点

 「@ターゲット」の指定がない場合や、textNodeを指定した場合は、DOMの「テキストノード追加」の仕組みを使います。そのため、追加する文字列が、HTMLのタグであっても、タグとしては解釈せず、例えば、「<」は「&lt;」として追加され、テキストエリアであれば、タグ文字列として見えます。この状態では、フィールドのデータは、単に表示されるのであって、何か処理をされることはありません。しかしながら、@innerHTMLを使用した場合は、細心の注意を払ってください。この場合、フィールドのデータに、JavaScriptのプログラムがあれば、実行されてしまい、セキュリティホールになりえます。しかしながら、innerHTMLの機能がないと、HTMLの断片をフィールドに保持して、そのHTMLに従ったレイアウトをするような用途に使えなくなります。@innerHTMLを使用する場合は、不特定多数の人がデータベースへの入力ができるようにすることはまず避けましょう。責任ある人だけがデータの書き込みができるようにしておくことで、一定の範囲で悪意のあるスクリプトの混入は防ぐことができます。SCRIPTタグを抜くことでも、この危険性をいくぶん避けることはできますが、属性内にもスクリプトの書き込みができる場合があり、JavaScriptのプログラムを完全に排除することは、難しいと考えます。結論としては、@innerHTMLを使う場合には、システム全体のセキュリティ上の問題がないかをよく考えた上で使用をするということになります。

演習ターゲット指定のバリエーション

 演習環境を利用して、データの書き戻しを伴うページの作成を行います。新たなページを作成して、更新の動作を検証します。

テキストフィールドとテキストエリアのあるページ

1ここからの作業は、Webブラウザー上で行います。ブラウザーで、「http://localhost:9080」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def05.phpを編集する」をクリックし、定義ファイルエディターでdef05.phpファイルを編集します。(もし、他の用途で5番目を利用しているのなら、例えば、def11.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれ背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6name、table、viewともに「postalcode」、keyを「id」、pagingを「true」、recordsを「5」とします。Contextsのその他のテキストフィールドは空白にします。
7Database Settingsに設定を行います。
[MySQL]の場合
db-classは「PDO」のままでかまいません。dsnに「mysql:host=db;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
[FileMaker]の場合
db-classを「FileMaker_DataAPI」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「gateway.docker.internal」、portに「443」、protocolに「https」、cert-vefifyingに「false」と入力します。
8Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。
9「http://localhost:9080」で開いたページに戻り「page05.htmlを編集する」をクリックし、ページファイルのpage05.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<!DOCTYPE html>
<!--
/*
 * INTER-Mediator Ver.@@@@2@@@@ Released @@@@1@@@@
 * 
 *   Copyright (c) 2010-2015 INTER-Mediator Directive Committee, All rights reserved.
 * 
 *   This project started at the end of 2009 by Masayuki Nii  msyk@msyk.net.
 *   INTER-Mediator is supplied under MIT License.
 */  -->
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def05.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <thead>
      <tr><th>Postal Code</th><th>Place</th><th>Memo</th></tr>
    </thead>
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8"></span>
          <span data-im="postalcode@f9"></span>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
</body>
</html>
10「http://localhost:9080」で開いたページに戻り、「page05.htmlを表示する」をクリックします。page05.htmlファイルが別のタブあるいはウインドウで開きます。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)テキストフィールドに見えているデータは、データベースに入力されているデータです。memoフィールドのみ編集可能な状態になっています。ここまでは、以前に作ったページから大きな違いはありません。
ここでも、スタイルシートは、INTER-Mediatorのサンプルの中にあるものをそのまま利用しています。そのため、ページネーションのコントロールのボタンはそれらしく見えています。

フィールドの値を要素のスタイルに指定する

1「page05.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page05.htmlを編集する」をクリックします。ページファイルのコードを以下のように変更します。f8フィールドとバインドしているSPANタグ要素のdata-im属性に、「memoフィールドの値を、スタイル属性のcolorとして設定する」という定義が加わりました。
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8 postalcode@memo@style.color"></span>
          <span data-im="postalcode@f9"></span>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
2「page05.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。そして、ページ内容の更新を行ってください。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page05.htmlを表示する」をクリックします。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
3Memoの列にあるテキストフィールドに、HTMLのスタイルシートのcolorに対応した値(red、#888888、など)を入力してみます。区名に対応するf8フィールドのSPANタグ要素にのみ、colorのスタイルが適用されていますが、値はmemoフィールドから取り出されています。結果として、Memo列のテキストフィールドに設定した文字列が、f8フィールドの文字色として設定されています。
追加したターゲット指定が「postalcode@memo@style.color」となっていることを改めて確認してください。これにより、memoフィールドの値が、そのタグ要素のスタイル属性colorに対して適用されるということです。

既存のデータに接続する設定

1演習環境が稼働していて、サンプルの画像をブラウザーで表示できることをまず確認します。ブラウザーで新しいタブあるいはウインドウを開いて、次のURLを入力し、画像が表示されることを確認します。タブの追加は、タブの並びの一番右にあるボタンで通常は行えます。(こちらをクリックすることで、以下のURLを開くことができます。)
http://localhost:9080/vendor/inter-mediator/inter-mediator/samples/Sample_products/images/tomatos.png
2「page05.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page05.htmlを編集する」をクリックします。ページファイルのコードを以下のように変更します。新たにIMGタグが追加されましたが、src属性は、前に確認したURLのファイル名の直前までの相対パスになっています。IMGタグ要素のdata-im属性はmemoフィールドの内容をsrc属性につなげるという設定ですが、「#」があるので、すでに入力されている属性値に、memoフィールドの文字列を追加することになります。
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8"></span>
          <span data-im="postalcode@f9"></span>
          <img src="/vendor/inter-mediator/inter-mediator/samples/Sample_products/images/"
               data-im="postalcode@memo@#src"/>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
3「page05.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。そして、ページ内容の更新を行ってください。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page05.htmlを表示する」をクリックします。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
4Memoの列にあるテキストフィールドに、画像ファイル名を入力してみます。INTER-Mediatorの演習環境には、以下のファイル名の画像ファイルが、src属性に指定したディレクトリに存在するので、これらの文字列をmemoフィールドにキータイプして入力し、Tabキーでフィールドを移動すると、即座に画像が出てきます。なお、画像が出てこない場合には、画面の更新を行ってください。
memoフィールドの文字列として画像のファイル名のみを入力します。すると、もともとsrc属性にある「/vendor/inter-mediator/inter-mediator/samples/Sample_products/images/」と、ファイル名(例えば「tomatos.png」)が結合された「/vendor/inter-mediator/inter-mediator/samples/Sample_products/images/tomatos.png」がsrc属性の値になります。このパスは、存在する画像ファイルへのURLと解釈できるので、IMGタグ要素に画像が表示されます。

innterHTMLによるHTML要素の表示

1「page05.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page05.htmlを編集する」をクリックします。ページファイルのコードを以下のように変更します。新たにIMGタグが追加されましたが、src属性は、前に確認したURLのファイル名の直前までの相対パスになっています。IMGタグ要素のdata-im属性はmemoフィールドの内容をsrc属性につなげるという設定ですが、「#」があるので、すでに入力されている属性値に、memoフィールドの文字列を追加することになります。
    <tbody>
      <tr>
        <td data-im="postalcode@f3"></td>
        <td>
          <span data-im="postalcode@f7"></span>
          <span data-im="postalcode@f8"></span>
          <span data-im="postalcode@f9"></span>
          <div data-im="postalcode@memo@innerHTML"></div>
        </td>
        <td>
          <input type="text" data-im="postalcode@memo"/>
        </td>
      </tr>
    </tbody>
2「page05.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。そして、ページ内容の更新を行ってください。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page05.htmlを表示する」をクリックします。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
3Memoの列に、HTMLの文字列を適当に入力してみます。例えば、Bタグで囲った文字列は太字になります。HRタグで水平線が引けます。3行目には箇条書きのタグとして「<ul><li>a</li><li>b</li></ul>」と入力しています。

演習のまとめ

innerHTMLを使う場合のセキュリティ

 innerHTMLを使用することでHTMLで文字列などを記録できて便利と思うかもしれませんが、セキュリティ的な面での考慮が必要です。もし、フィールドのデータにSCRIPTタグが入っていたとしたら、そのページ内のさまざまな要素にJavaScriptでのプログラムから参照でき、第三者のサーバーに送信するということも可能です。自分ではそういうプログラムを作らないと思っていても、状況によってはそうしたことが可能になります。

 例えば、誰でも書き込めるBBSにおいて、HTMLでのメッセージを許可したとします。悪意のある利用者が、何の変哲もないメッセージに見せかけて、メッセージの中にスクリプトを仕掛けたとします。そのスクリプトは、メッセージを読んだ別の人のブラウザーでも実行されます。スクリプトは、例えばクッキーをすべてどこかのサーバーに送るようなものかもしれません。もちろん、今時の通販サイトでは他のドメインからのクッキー利用ができないようにしているのが普通ですが、そうではないようなサイトもあるかもしれません。そうなると、「BBSに任意のスクリプトを書き込めるようにしている」ということから、メッセージを読んだ人のブラウザーに残っているどこかのサイトのパスワードやあるいはクレジットカード情報を取り出して集める可能性がゼロではなくなります。これを、XSS(クロスサイトスクリプティング攻撃)と呼びます。

 したがって、innerHTMLを使用すると、XSSの可能性が発生します。しかしながら、フィールドにデータを書き込み可能なユーザーを限定し、そのユーザーが悪意がないと仮定すれば、問題は発生しません。例えば、Webサイトのコンテンツを管理するCMS(Contents Management System)のような用途だと、サイトを運営している限定されたメンバーだけが書き込み権限があり、不特定多数のユーザーが書き込みはできません。サイト運営者は善意がありかつサイト自体に問題が発生させることを起こさないという仮定があれば、脅威にならないと言えます。

 もし、HTMLの書き込みを許可しつつ、ユーザーがすべて善意で行動することを保証できない場合には、フィールドの内容から、SCRIPTタグを抜き取るという手法も考えられます。しかし、JavaScriptの多岐に渡る有用性をすべて封じて「スクリプトを取り除」いてしまうと、せっかくの使い勝手が損なわれてしまいます。

 例えば、マウスポインタが入ったイベントをトリガーにスクリプトを動かす必要性もあり、JavaScriptを完全に取り除くのが良いのかどうかは疑問です。別の考え方としては、SPAN/DIV/Pなど、安全なタグと属性を残すようなフィルタをかけるという手段があります。

 なお、「安全を確保するためのフィルタ」は、『4-6 データコンバータークラスを使ったフィールド単位の変換』で説明するデータコンバーターで実現できます。データコンバーターを利用すれば、クライアント側の操作でセキュリティバリアの回避はできません。しかしながら、INTER-MediatorはVer.5.1現在、そのコンバーターについては用意はしていません。セキュリティの要件はサイトごとに異なり、標準的なコンバーターを用意する必要性は薄いと考えます。

長いターゲット指定を短いキーワードで記述

 ターゲット指定は長くなりがちです。特にFileMakerでポータルの中にある別のTOを参照するような要素はかなり長い記述になります。そこで、ターゲット指定のエイリアス名の定義を、定義ファイル内でできるようにしてあります。IM_Entry関数の第二引数の連想配列において、aliasesキーで指定した配列でエイリアスを指定できます。リスト4-1-3がその指定例です。aliasesキーの配列は、キーがエイリアス名、値がターゲット指定となるような連想配列の配列です。複数のエイリアスを指定してもかまいません。

リスト4-1-3 aliasesの指定例
IM_Entry(
    array( ...... /* コンテキスト指定 */ ....... ),
    array(
         "aliases" => array(
             array( "bkcolor" 
             => "Patient@病棟マスター_表示用::背景色@style.backgroundColor" ))
    ),
    array( ...... /* データベース接続指定 */ ....... ),
    false
);

 このような定義があると、data-im属性に「bkcolor」とだけ指定すれば、「Patient@病棟マスター_表示用::背景色@style.backgroundColor」と指定したのと同じことになります。

このセクションのまとめ

 ターゲット指定には、コンテキスト名とフィールド名だけでなく、3つ目の要素を指定できます。これにより、タグ要素の属性やスタイルシート属性、あるいはinnerHTMLプロパティに、フィールドの値を設定できます。また、#によって既存の値に追加したり、$によって既存の値の中にある$と置き換えるということもできます。

4-2ページを合成するときのルール

テーブルの1行分がレコードの数に応じて複製される…という最も分かりやすいモデルでこれまで見てきました。しかしながら、これだけではさまざまなアプリケーションの開発には制約があります。この考え方を一般的にしたものがエンクロージャーとリピーターというモデルです。まず、このモデルについて説明をします。

エンクロージャーとリピーターの識別

 本コースのこれまでのところで、説明してきたことですが、ここで一般的な動作として改めて動きを紹介します。

 まず、データベースとユーザーインターフェースとの連携は、一般には「バインド」と呼ばれます。データベースの内容と、ユーザーインターフェースとして表示したオブジェクトとの間で連携が取れる、つまり、フィールドのデータを表示し、場合によってはユーザーインターフェース側で見ているデータを修正すると、それがきっかけとなってデータベースの更新が行われる状況を「バインド」と呼びます。INTER-Mediatorは、リンクノード(data-im属性によるターゲット指定が設定されたタグ要素)によって、バインドの仕組みが利用できます。

 INTER-Mediatorは、ページファイルを探索して、リンクノードがあるかどうかを探します。リンクノードがひとつでも見つかると、自分より上位のノード、つまり、自分を含んでいるタグ要素を上層に向かって探索し、「リピーター」であるノードを探します。表4-2-1には自動的にリピーターと識別しうるタグ要素についてまとめておきました。また、表4-2-2に記載したように、TRやLI、OPTIONタグでなくても、data-im-control属性で値を「repaeter」と指定すれば、DIVやSPANなど別のタグでもリピーターになります。テーブルのTRタグだけでなく、箇条書きのLIやポップアップメニューのLIタグがリピーターになりますし、data-im-control="repater"であるタグ要素は何でもリピーターになることができます。そして、リピーターの1階層上位にあるノードを「エンクロージャー」と識別します。こちらも、表4-2-1にまとめました。あるいは、表4-2-2にあるように、data-im-control="enclosure"と指定したタグ要素もエンクロージャーとなります。原則としてリピーターの親要素がエンクロージャーになりますが、DIVがエンクロージャー、その内部のSPANがリピーターなら、それぞれのタグにdata-im-control要素で「enclosure」ないしは「repater」の値を指定します。

ページでの形態エンクロージャーリピーターリンクノードになるうる要素
TBODYTR任意の要素
番号リストOLLILIないしはLIの内部の要素
箇条書きULLILIないしはLIの内部の要素
ポップアップ、リストSELECTOPTION要素OPTION要素そのもの
表4-2-1 INTER-Mediatorが属性なしでも識別するエンクロージャーとリピーター
data-im-control属性の値動作
enclosureエンクロージャー
repeaterリピーター
noresultレコードがひとつもない場合のリピーター
ignore_enc_rep指定のあるタグ要素はエクロージャーあるいはリピーターとしては判別しない
headerヘッダー(リピーターを繰り返す前に挿入)
separatorリピーターとリピーターの間に挿入
footerフッター(リピーターを繰り返した後に挿入)
表4-2-2 タグ要素のdata-im-control属性に指定可能な値

 エンクロージャーが決まると、続いて、そのエンクロージャーに対するリピーターになりうるノードを収集します。例えば、リンクノードが含まれるTRタグがあると、その上位のTBODYがエンクロージャーになりますが、TBODYに含まれるすべてのTRをひとつのリピーターとします。つまり、1レコードが複数のTRで構成される行に分割されていてもかまいませんし、単に装飾のためだけのTR/TDで構成されたHTMLコード群が含まれても、それらもリピーターの一部として識別します。ただし、表4-2-2に示されたdata-im-control属性の値を持つリピーターの場合はこれに限りません。この表にある項目は、本セクションの最後の方の『検索結果がない場合の表示』と『間に割り込む特殊なリピーター』、さらに次のセクションの最後の『エンクロージャーやリピーターであることを無視する』で説明します。しばらくは「エンクロージャー」「リピーター」があって、リピーター内部にあるリンクノードがデータベースのフィールドとバインドするという状況で説明をします。

 本コースのこれまでのところでは、TBODY/TRによるエンクロージャー/リピーターの利用を中心としてきました。つまり、TRタグ内にリンクノードがあれば、そのTRタグ要素は、属性などに何も指定しなくても、リピーターであるという識別を行います。INTER-Mediatorは、リピーターであるという識別を行った上で、改めてリピーター内部を探し、リンクノードのリストを作ります。リンクノードはひとつと限りませんが、このひとつのリピーターの中で利用するコンテキストに関しては、「一番多く使われているコンテキスト名」を選択し、そのコンテキストが展開されているリピーターであると識別します。そこで、リンクノードを集めて、一番多く使われているコンテキスト名を採用し、そのひとつのコンテキストを取得して展開します。したがって、使用頻度が2番目以降のコンテキスト名が指定されているリンクノードは、バインド動作は行わず、INTER-Mediatorは原則として無視します。

 テーブル以外にも、エンクロージャー/リピーターに関して、いくつかの組み合わせをサポートします。特に、SELECT/OPTIONタグ要素によるポップアップメニューはよく利用されます。このとき、SELECTタグ要素はバインド可能なリンクノードであり、同時にエンクロージャーになりえます。つまり、OPTIONタグ要素がバインドしていれば、SELECTタグ要素はエンクロージャー、OPTIONタグ要素はリピーターになり得ます。この、「リピーターの中にエンクロージャーがある」という状況については、『4-3 複数のコンテキストとリレーションシップ』で詳しく説明をします。

 箇条書きや番号リストなども、エンクロージャーやリピーターとして識別します。さらに、任意のタグについてdata-im-control属性を記述し値として「enclosure」もしくは「repeater」と指定すれば、それぞれ、エンクロージャーとリピーターになります。DIVとSPANや、ARTICLEとSECTIONなど、いろいろな組み合わせでの構成が可能です。

実際のページ合成

 INTER-Mediatorでは、エンクロージャーとリピーターの識別を行い、レコードに応じて、リピーターを複製することで、複数のレコードの表現が可能です。その様子を実際のコードやデータを当てはめながら、解説しましょう。

 まず、郵便番号と住所が入力されたテーブル「postalcode」があったとします。それを、同一の「postalcode」というnameキーの値を持つコンテキストで利用可能になっているとします。そのとき、表4-2-3のように、コンテキストpostalcodeからの検索結果が得られているとします。フィールド名は1行目の通りです。

pcodeaddress
102-0094千代田区紀尾井町
121-0813足立区竹の塚
161-0035新宿区中井
表4-2-3 postalcodeテーブルの内容

 ページファイル内の該当するソース部分を図4-2-1に示します。ここでは、data-im属性があるリンクノードが2つ含まれるリピーターのTRタグ要素と、その親要素であるエンクロージャーのTBODYタグ要素が識別されます。リンクノードのターゲット指定では、postalcodeコンテキストのみが記述されているのでpostalcodeコンテキストに対してクエリーが実行されます。クエリー結果は、表4-2-3の通りだとします。

図4-2-1 エンクロージャーとリピーターの識別

 データベースのデータを表示するために行う最初の作業は、リピーターをいったん取り除いて、保持することです(図4-2-2)。エンクロージャーの子要素は、TBODYの場合TR要素のみなので、結果としてTBODYの中身は空になり、いったん何もない表になります。取り除いたリピーターは、以後、複製の元として使えるように残されています。

図4-2-2 リピーターの取り除きと保持

 表4-2-3に示すクエリー結果について、1行目のレコードから順番に処理をします。1レコード目が存在するので、図4-2-3に示すように、①まずリピーターの複製を用意し、②複製のリンクノードにレコードのフィールドのデータを挿入し、③レコードを挿入したリピーターをエンクロージャーの子ノードとして追加します。こうして、1レコード分に相当するテーブルの1行が生成されます。

図4-2-3 1レコードを合成する作業

 同様に、2レコード目が存在します。そこで、図4-2-4のように、リピーターの複製、フィールドの値の挿入、そしてエンクロージャーへの複製したリピーターの追加を行い、2レコード目も合成されました。これで、表には2行分が表示されることになります。

図4-2-4 2レコード目の合成

 そして、3レコード目を合成した結果が図4-2-5です。ここで、クエリー結果が3レコードなら、処理が終わり、ページファイルの状態から、クエリー結果のデータを合成したテーブルが作られたことになります。

図4-2-5 3レコード目の合成

 ページ合成時には他にもいくつか準備をする必要があります。例えば、バインドしたものがテキストフィールドなどのユーザーインターフェースに関連するタグ要素であれば、クライアント側でデータを修正したときのイベントを受け取ってデータベースに更新を行うためにさまざまな準備が必要です。バインドを行った時点でのデータや、そのレコードを特定するために、定義ファイルのコンテキストにおいてkeyキーで指定したフィールドに設定されている値などを、INTER-Mediatorの側で用意した変数に記録しています。実際には、バインドした先のノードのタグに関わらず、データベースから取得したデータや、それを挿入した先などのさまざまな情報を、背後で行っています。これらは、JavaScriptのレベルでの利用が可能なものもありますので、『6-3 データベースへの書き込みを直接行う』で説明します。

検索結果がない場合の表示

 ここまでの手順では基本的に何か検索される状態を見てきました。ここで、条件を与えてもレコードがまったく得られない場合、つまり検索結果のレコード数が0の場合の対処を紹介します。リピーター要素がテーブルであった場合、これまでの動作だと、レコード数が0であったときには、中身の何もないテーブルが作られてしまいます。しかしこうした場合は、「レコードがありません」などの表示をするのが一般的でしょう。そのときには、リピーターの中にあるdata-im-control属性が「noresult」のものを用意します。テーブルの場合は、「<tr data-im-control="noresult"><td>.....</td></tr>」といったリピーターを用意します。data-im-control属性が「noresult」の場合、通常のレコードに合成するときには、そのタグ要素は削除されます。そして、レコードが0のときには、data-im-control属性が「noresult」のものだけをエンクロージャーの子ノードとして追加します。リスト4-2-1はページファイルの作成例です。

リスト4-2-1 検索結果がないときのノードを追加した例
<table>
    <thead>
    <tr><th>郵便番号</th><th>住所</th></tr>
    </thead>
    <tbody>
    <tr data-im-conrtrol="noresult">
        <td colspan="2">検索条件に合致するレコードはありません</td>
    </tr>
    <tr>
        <td data-im="postalcode@pcode"></td>
        <td data-im="postalcode@address"></td>
    </tr>
    </tbody>
</table>

間に割り込む特殊なリピーター

 TBODYタグをエンクロージャーとした場合、data-im-control属性のないTRタグはすべてリピーターになります。しかしながら、data-im-control属性がそれぞれ「header」「separator」「footer」のTRタグ要素を用意すると、文字通りヘッダーは最初、フッターは最後、セパレーターは途中に配置されます。例えば、レコードが3つあれば、次のように展開されます。

リスト4-2-2 ヘッダー、フッター、セパレーターを挿入した結果
<tbody>
    <tr data-im-control="header"></tr>
    <tr></tr>
    <tr data-im-control="separator"></tr>
    <tr></tr>
    <tr data-im-control="separator"></tr>
    <tr></tr>
    <tr data-im-control="footer"></tr>
</tbody>

 この仕組みは、元はあるフィールドに対して3つのレコードで「A」「B」「C」という値が設定されている時「A, B, C」のように表示したいためにseparator値の動作として実装しましたが、ヘッダーとフッターも同時に実装しました。なお、TBODYの直下のタグ要素はTR要素のみですが、DIVやSPANで展開したときには、data-im-controlがrepeaterのものはリピーターとして、その属性の値がない場合があります。その場合、data-im-control属性のない要素は合成前に取り残されることになり、事実上、ヘッダーと同じ位置に配置されることになります。headerの値が設定されたタグ要素があれば、何も設定されていないタグ要素はheaderのタグより前に来ます。

このセクションのまとめ

 INTER-Mediatorは、エンクロージャー/リピーターというタグ要素を識別することで、自動的にレコードの数だけ行が確保されるなどの動作を行います。このセクションでは、どんなタグ要素がエンクロージャー/リピーターになることができ、実際にレコードに応じてタグ要素が増殖される動作を説明しました。また、レコードがないときの特別な表示の方法についても説明しました。

4-3複数のコンテキストとリレーションシップ

 データベースを利用する場合、リレーションシップがあることで、さまざまな用途に展開できることは長年の実績で証明されています。INTER-Mediatorではリレーションシップの展開をクライアントで行い、ページ上で関連レコードをその都度サーバーから取得して合成するという手法をとっています。そのベースになる仕組みは、リピーターの中にエンクロージャーがあると、そこで異なるコンテキストの展開を始めることができる点です。その際に、リレーションシップを考慮したり、あるいはしなかったりといった動作を選択できます。

階層的に定義可能なエンクロージャー/リピーター

 INTER-Mediatorは、エンクロージャーとリピーターによる合成を、階層的に展開できます。リピーターの中にエンクロージャーが含まれている場合、リピーター合成後に、そのエンクロージャー内の合成処理に進み、階層的にいくらでも深くデータベースの取得と展開ができます。一番典型的な例は、テーブルでのTRタグの中に、TABLEタグで定義したテーブルが含まれている場合で、それぞれのTRタグ以下にそれぞれリンクノードが存在するような形になります。

 ただし、階層化をやりすぎるとパフォーマンスの低下が懸念されます。単純化した説明をすれば、INTER-Mediatorは、エンクロージャーの数だけデータベース接続を行ってクエリーを行います。エンクロージャーの数は、ページファイルに存在する数よりも、一般には多くなります。リピーターにエンクロージャーが含まれていれば、リピーターが複製された数だけエンクロージャーは増えます。したがって、リピーターの中にエンクロージャーがあるような場合、上位のリピーターに20個のレコードがあれば、内部のエンクロージャー向けのデータベース接続が20回行われます。その、内部のエンクロージャー向けのデータベース処理が重い、つまりアクセスに時間がかかったり、結果の数が多いといった場合には、パフォーマンスの低下が懸念されるものと思われます。その20回のアクセスが、毎回決まり切ったマスターからの取り出しであるなら、一般にはデータベースのクエリーはキャッシュが効きますので、ある程度速度低下が抑えられます。INTER-Mediator自体のキャッシュも制限が多いものの機能しますので、これを有効に使うことを考慮すべきです。場面に応じた、さまざまな手法でのパフォーマンス向上を図る必要があります。特に、FileMakerの場合、サーバーの応答時間がMySQLなどに比べて桁違いに遅いので、多数・大量のデータベース処理をいかに最適化するかが設計のポイントにもなります。FileMakerの場合は、このセクションの最後の方で、その対処法を説明しましょう。

リレーションを伴うページの合成

 あるリピーター(上位のリピーター)にエンクロージャー(下位のエンクロージャー)があり、異なるコンテキストに対するクエリーが定義ファイルで定義されていたとします。上位のリピーターに対応するひとつのレコードの合成が終わってエンクロージャーに追加された後、含まれる下位のエンクロージャーに関する合成処理が始まります。

 このとき、上位、および下位のエンクロージャーに対応するクエリー処理は、それぞれのコンテキストに定義された検索条件等に従い、基本的に独立して行われます。常に一定の選択肢をマスターテーブルから取り出すようなポップアップメニューではそうした利用方法がよく行われます。この場合、ポップアップメニューで「どれを選択しているのか」を示す数値が上位のエンクロージャー内にあるいずれかのフィールドで、下位のエンクロージャーとリピーターはSELECTおよびOPTIONタグに相当します。このとき、上位/下位のエンクロージャーは独立したデータベース処理を行えば問題ないはずです。つまり、下位のエンクロージャーのためのデータベース処理は、「常に同じもの」を必要としているからです。

 一方、下位のエンクロージャーに関連したページ合成を行うときに、リレーションシップを考慮することができます。そのためには、下位のエンクロージャーで使用されるコンテキスト定義において、「relation」キーによる値が定義ファイル上で定義されている必要があります。「realation」キーがなければ、独立したデータベースアクセスを行いますが、「relation」キーがあれば上位のリピーターに対応したレコードと関連したデータを取り出して、展開することができます。すなわち、名前通りリレーションシップに基づくデータベースアクセスを行うということです。結果として、1対多のような展開がエンクロージャー/リピーターの階層化とコンテキスト定義への「relation」キーの設定により、クライアントサイドで行われます。

 「relation」キーに指定する値は、表4-3-1のようなキーに対する値を持つ連想配列の配列です。SQL的な説明をすれば、「relation」キーのあるコンテキストに対するクエリーにおいて、「AND [foreign-key] [operator] [join-fieldの値]」という検索条件が付加されるということになりますが、この様子は後の演習で実際に値を見ながら改めて確認することにしましょう。

キー意味
foreign-key内側のコンテキストでの照合フィールドで、通常は外部キー
operatorforeign-keyによるフィールド名とjoin-fieldによるフィールドの値を結ぶ演算子
join-field外側のコンテキストでの照合フィールドで、通常は外部キーに対応するキー。このフィールドの値を実際の展開には使うので、取得したレコードの中にこのフィールドの値が存在する必要がある
表4-3-1 コンテキストのrelationキーに指定する配列のキー

演習リレーションを伴うページの作成

 エンクロージャー/リピーターの階層化や、リレーションシップを考慮した関連レコードの検索などを、実際の演習で確認していくことにします。ここで使用するデータベースは、INTER-Mediatorのサンプルにも使われているもので、若干複雑です。最初に、データベースの内容を確認した上で、実際の演習に取り掛かることにします。

元になるデータベースの確認

 演習に利用するデータベースを確認します。表4-3-2にテーブルをまとめました。MySQLはテーブル名そのままを使いますが、FileMakerでは、定義ファイルに指定するのは表の中の「レイアウト名」になるので、使用するデータベースに合わせて必要な情報を得てください。一番基本的なテーブルは、1人の人間を1レコードをとして管理するpersonです。その1人の人に対して、複数のコンタクト記録があるとして、その記録をcontactテーブルに残します。したがって、personの1レコードに対して、contactへの多重度は「0..*」(0個、1個、...多数、となり得るという意味)となります。そして、contactテーブルにはwayとkindという2つの整数フィールドがあり、それぞれのマスターテーブルとなるwayとkindテーブルのid値を設定するのを基本とします。wayは「direct」などの直接のコンタクトか、間接的なものなのかを示します。kindは、「電話」「メール」など、手段を示します。

テーブル名TO名レイアウト名テーブルの役割
personperson_toperson_layout1人の人間を1レコードをとして管理
contactcontact_tocontact_toそれぞれのpersonとのコンタクト記録
contact_waycontact_waycontact_wayコンタクト方法の分類
contact_kindcontact_kindcontact_kind具体的なコンタクト方法
cor_way_kindcor_way_kindcor_way_kindwayとkindの対応付け
表4-3-2 利用するテーブルの概要

 利用するテーブルのうち、contact_way(表4-3-3)、contact_kind(表4-3-4)、cor_way_kind(表4-3-5)については、INTER-Mediator-Serverでは最初からレコードが設定されており、そのまま利用するので、それらのテーブルに入っているレコードおよびフィールドについて、それぞれの表で見てみましょう。cor_way_kindテーブルは、wayとkindの関係が多対多になるため、それらを1対多の関係に分解するための中間的な対応付けのためのテーブルです。contact_wayテーブルの「Direct」つまり直接的なコンタクトは、idフィールドの値が「4」です。cor_way_kindテーブルで、way_idフィールドが「4」のレコードは3つあり、それぞれのkind_idフィールドの値は「4」「5」「7」となっています。そして、contact_kindテーブルのidフィールドが「4」「5」「7」のレコードは、「Talk」「Meet」「Meeting」です。つまり、Directな(直接的な)コンタクトとしては、Talk(会って話した)、Meet(ばったり会った)、Meeting(会議で会った)の3つのレコードに関連しているということです。この演習ので、「wayの選択肢に応じてkindの選択肢が設定される」というデーマで、この多対多の関連付けのためのテーブルcor_way_kindを利用します。

idname
4Direct
5Indirect
6Others
表4-3-3 contact_wayテーブル
idname
4Talk
5Meet
6Calling
7Meeting
8Mail
9Email
10See on Web
11See on Chat
12Twitter
13Conference
表4-3-4 contact_kindテーブル
idway_idkind_id
144
245
356
447
558
659
7610
8511
9612
10512
11613
表4-3-5 cor_way_kindテーブル

 テーブル間の連携、つまり外部キーがどのフィールドと結合するのかを示したのが図4-3-1です。FileMakerにはそれぞれのテーブル間の関連を図式する機能(リレーションシップ)があります。図はその様子を示したものですが、MySQLでも同様な関連になっていると考えてください。ボックスにはTO名(テーブル名)と、フィールドのいずれも見えるようにしてあります。MySQLの場合はテーブルを示すひとつのボックスの名前は単にテーブルと考えてもらってかまいません。

図4-3-1 FileMakerでのデータベースのリレーションシップ

 実際のデータについても、FileMakerで確認します。レイアウトにperson_to、ポータルにcontact_toが表示されています。MySQLでも同様にpersonテーブルに対して、その各レコードに対してcontactテーブルが選択されて表示されると考えてください。下側の繰り返し表示されている部分(ポータル)がcontactテーブルで、person_idフィールドが外部キーとなっていて、上側のpersonテーブルのidフィールドの値と対応付けられています。

図4-3-2 personテーブルとcontactテーブルの例

personテーブルを1レコードずつ表示する

1演習環境を起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://localhost:9080」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def07.phpを編集する」をクリックし、定義ファイルエディターでdef07.phpファイルを編集します。(もし、他の用途で7番目を利用しているのなら、例えば、def11.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6nameに「person」、keyを「id」、pagingを「true」、repeat-controlを「confirm-delete confirm-insert」、recordsとmaxrecordsを「1」とします。Contextsのその他のテキストフィールドは空白にします。
[MySQL]
tableおよびviewには「person」を指定します。
[FileMaker]
tableおよびviewには「person_layout」を指定します。
7Database Settingsに設定を行います。
[MySQL]の場合
db-classは「PDO」のままでかまいません。dsnに「mysql:host=db;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
[FileMaker]の場合
db-classを「FileMaker_DataAPI」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「gateway.docker.internal」、portに「443」、protocolに「https」、cert-vefifyingに「false」と入力します。
8Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。
9「http://localhost:9080」で開いたページに戻り「page07.htmlを編集する」をクリックし、ページファイルのpage07.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def07.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tbody>
      <tr>
        <th>id</th>
        <td><input type="text" data-im="person@id"/></td>
      </tr>
      <tr>
        <th>name</th>
        <td><input type="text" data-im="person@name"/></td>
      </tr>
    </tbody>
  </table>
</body>
</html>
10「http://localhost:9080」で開いたページに戻り、「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。personテーブルの内容が、1レコードずつ参照できます。ここまでは、すでに説明した通りです。

contactテーブルの関連レコードを表示する

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「contact」、keyを「id」、repeat-controlを「confirm-delete confirm-insert」とします。Contextsのその他のテキストフィールドは空白にします。
[MySQL]
tableおよびviewには「contact」を指定します。
[FileMaker]
tableおよびviewには「contact_to」を指定します。
4contactコンテキストの中のRelationshipのすぐ下にある「追加」ボタンをクリックして、設定のための行を追加します。foreign-keyに「person_id」、join-fieldに「id」、operatorに「=」を指定します。
5「page07.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page07.htmlを編集する」をクリックします。太字で示した部分を追加します。テーブルの中にテーブルがある点に注意してください。外側のテーブルはpersonコンテキスト、内側のテーブルはcontactコンテキストで得られたデータを展開したものです。
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tbody>
      <tr>
        <th>id</th>
        <td><input type="text" data-im="person@id"/></td>
      </tr>
      <tr>
        <th>name</th>
        <td><input type="text" data-im="person@name"/></td>
      </tr>
      <tr>
        <td colspan="2">
          <table>
            <thead>
              <tr><th>id</th><th>person_id</th><th>summary</th>
                <th>datetime</th><th>way</th><th>kind</th></tr>
            </thead>
            <tbody>
              <tr>
                <td><input type="text" data-im="contact@id" size="3"/></td>
                <td><input type="text" data-im="contact@person_id" size="3"/></td>
                <td><input type="text" data-im="contact@summary"/></td>
                <td><input type="text" data-im="contact@datetime"/></td>
                <td><input type="text" data-im="contact@way" size="3"/></td>
                <td><input type="text" data-im="contact@kind" size="3"/></td>
              </tr>
            </tbody>
          </table>
        </td>
      </tr>
    </tbody>
  </table>
</body>
</html>
6「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://localhost:9080」で開いたページに戻り、「page07.htmlを表示する」をクリックします。
7ページネーションのコントローラーを利用して、次のレコードなどに移動してください。外側のテーブルのコンテキストのidフィールドの値と、内側のテーブルのコンテキストのperson_idフィールドの値が同一であることを確認してください。
ここでは、外側のpersonコンテキストに対応付けられたエンクロージャー/リピーターに対して、その内部にcontactコンテキストに結び付けられたエンクロージャー/リピーターが存在します。内部のcontactコンテキストに関連付けられた側では、定義ファイルにrelationキーによる値が設定されていて、その設定を元にして、データベースアクセス時に、「関連するレコードのみを取り出す」という検索条件が加わることになります。具体的には、外側のpersonコンテキストのidフィルールドの値が「2」なら、内側のcontactコンテキストのクエリーを行うときにperson_idが「2」のものに絞り込みます。この、id、person_id、そして条件として=演算子で求めているという動作は、contactコンテキストのrelationキーに指定した値に基づいています。
このような、伝票形式のリレーションシップを、ページ上で展開できます。このとき、personに対応したcontactテーブルのレコードがない場合でも、先にpersonのレコードを展開するので、そのようなpersonテーブルのレコードも、ページ上に表示されます。SQLのテーブル結合の形式でいえば、これはLEFT JOINに相当する動作であり、INNER JOINの動作ではありません。

マスターの内容をそのまま表示するポップアップメニュー

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「contact_way」を設定し、そのコンテキストのその他のテキストフィールドは空白にします。
このコンテキストは、contact_wayテーブルの選択肢を常にそのまま表示していずれも選択できるようにします。選択肢をすべて取り出すためにコンテキストを定義します。このコンテキストには、relationキーの値を指定しない点に注意してください。
4「page07.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page07.htmlを編集する」をクリックします。太字で示した部分を変更・追加します。contactコンテキストの中で、wayフィールドの値をSELECTタグによるポップアップメニューで選択できるようにします。
<td colspan="2">
  <table>
    <thead>
      <tr><th>id</th><th>person_id</th><th>summary</th>
        <th>datetime</th><th>way</th><th>kind</th></tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="text" data-im="contact@id" size="3"/></td>
        <td><input type="text" data-im="contact@person_id" size="3"/></td>
        <td><input type="text" data-im="contact@summary"/></td>
        <td><input type="text" data-im="contact@datetime"/></td>
        <td>
          <select data-im="contact@way">
            <option data-im="contact_way@id@value contact_way@name"/>
          </select>
        </td>
        <td><input type="text" data-im="contact@kind" size="3"/></td>
      </tr>
    </tbody>
  </table>
</td>
5「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://localhost:9080」で開いたページに戻り、「page07.htmlを表示する」をクリックします。wayの列は、ポップアップメニューで表示されています。
contactのコンテキストが複数行あるレコードを表示して、各行のwayのポップアップメニューの選択肢をみてください。いずれも同じです。つまり、このような場合のポップアップメニューの選択肢は、どのレコードでも同じなので、上位のコンテキストの関係は記述する必要はなく、relationキーの値を定義ファイルのコンテキストに追加する必要もありません。
6ポップアップメニューで選択を変更し、前後のレコードに移動して戻るなどして、以前に選択した通りのものになっていることを確認してください。INTER-MediatorはSELECTタグがあれば、そのターゲット指定にあるフィールドの値に応じた選択肢を自動的に選択するようになっています。また、ポップアップメニューを選択すると、その選択項目に応じたデータが、対応するフィールドに書き込まれます。

他のフィールド値に依存した選択肢を出すポップアップメニュー

 この単元についてはSQLデータベースに関する十分な知識が必要な、上級的な話題になります。

 これまでに作ってきたページで、wayの選択肢に応じてkindのポップアップメニューの選択肢を差し替えるということを行います。そのための情報がcor_way_kindテーブルに入っています。したがって、cor_way_kindテーブルのレコードをポップアップメニューの選択肢に展開することが考えられます。しかしながら、cor_way_kindテーブルにはkindテーブルのnameフィールドの情報がないので、そのままでは選択肢に必要な名称(つまり、「Meeting」「Mail」など)が表示できません。ではさらにリレーションシップを設定すればよいではないか、と言いたいところですが、OPTIONタグの中にはタグは記述できず、テキストしか記述できません。

 そこで、リスト4-3-1のようなビュー「cor_way_kindname」を定義します。ビューは、ひとつあるいは複数のテーブルから導出した表形式の結果を出力するものです。データ自体は保持せず、原則として、テーブルのデータを使って新たなリレーション(表形式のデータ)を出力します。集計などの複雑な処理ではよく利用されますが、ここでは、cor_way_kindテーブルとそれに関連したcontact_kindテーブルを結合した結果をビューで得ています。言い換えれば、cor_way_kindテーブルに、「名称」を付加したものです。リスト4-3-1に定義を示します。このビュー「cor_way_kindname」で得られる結果は、表4-3-6に示します。

リスト4-3-1 cor_way_kindnameの定義内容
CREATE VIEW cor_way_kindname AS 
	SELECT cor_way_kind.*,contact_kind.name as name_kind
		FROM cor_way_kind, contact_kind
		WHERE cor_way_kind.kind_id = contact_kind.id;
idway_idkind_idname_kind
144Talk
245Meet
356Calling
447Meeting
558Mail
659Email
7610See on Web
8511See on Chat
9612Twitter
10512Twitter
11613Conference
表4-3-6 cor_way_kindnameビューの出力

 なお、FileMakerについては、TestDBデータベース内のcor_way_kindレイアウトにcontact_kindテーブルのnameフィールドを配置しているので、別途レイアウトを作る必要はありません(図4-3-2)。FileMakerはレイアウトはテーブルを表示するだけでなく、SQL的な視点からはビューとしてみることもできます。ただし、データベースの管理の「リレーションシップ」のタブで、TOを定義してそれらの関連性を定義しておく必要があります。

図4-3-3 FileMakerのcor_way_kindレイアウト

 cor_way_kindに関して、以上のような設定がすでにデータベース側に準備されています。以下、wayの選択結果に応じたkindの選択肢が出てくるように、ページを変更します。

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「cor_way_kind」を設定します。
[MySQL]
viewに「cor_way_kindname」を指定します。その他のテキストフィールドは空白にします。
[FileMaker]
コンテキストのその他のテキストフィールドは空白にします。
4cor_way_kindコンテキスト内のRelationshipのすぐ下にある「追加」ボタンをクリックして、設定のための行を追加します。foreign-keyに「way_id」、join-fieldに「way」、operatorに「=」を指定します。
5「page07.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page07.htmlを編集する」をクリックします。太字で示した部分を変更・追加します。contactコンテキストの中で、kindフィールドの値をSELECTタグによるポップアップメニューで選択できるようにします。
<tr>
  <td><input type="text" data-im="contact@id" size="3"/></td>
  <td><input type="text" data-im="contact@person_id" size="3"/></td>
  <td><input type="text" data-im="contact@summary"/></td>
  <td><input type="text" data-im="contact@datetime"/></td>
  <td>
    <select data-im="contact@way">
      <option data-im="contact_way@id@value contact_way@name"/>
    </select>
  </td>
  <td>
    <select data-im="contact@kind">
      <option data-im="cor_way_kind@kind_id@value
                       cor_way_kind@name_kind"/>
    </select>
  </td>
</tr>
FileMakerの場合には、修正は以下のように行ってください。
    <select data-im="contact@kind">
      <option data-im="cor_way_kind@kind_id@value
                       cor_way_kind@contact_kind::name"/>
    </select>
6「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://localhost:9080」で開いたページに戻り、「page07.htmlを表示する」をクリックします。kindの列は、ポップアップメニューで表示されています。
wayで「Direct」を選択すると、kindの選択肢は「Talk」「Meet」「Meeting」になります。wayで「Indirect」を選択すると、「kind」の選択肢は「Calling」「Mail」「Email」「See on Chat」「Twitter」となります。つまり、wayの選択肢に応じて、kindの選択肢が変化するといった仕組みが実装されました。

レコードの追加とリレーションシップ

1「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。適当なレコードで作業をしてください。以下の図はidフィールドが「3」のレコードが見えています。異なるレコードが見えている状態でもかまいません。
2ここで、内側のテーブルの下側にある「追加」ボタンをクリックします。このボタンにより、contactコンテキストに新しいレコードが作成できます。
3ボタンを押した後に作成していいかどうかの確認がありますので、OKボタンをクリックしてください。
4この場合だと、もともと2行だったcontactコンテキストのテーブルに、新たに3行目のレコードができたことを確認してください。
ここで、新たに作成されたcontactコンテキストのレコード(図ではidフィールドの値が「8」のレコード)の、person_idの値を見てください。なにもしなくても、外側のpersonコンテキストのidフィールドの値が追加されています。このように、relationキーの値を定義して、自身のperson_idと相手のテーブルのidフィールドによって関係性を持つことが定義されていると、新しいレコードを作成するときに、関連するフィールドに適切な値を設定するということを行います。つまり、外部キーに相当するフィールドに、関連付けのための値を設定するということです。FileMakerやMicrosoft Accessでは、リレーションシップで関連付けられているポータルあるいはサブフィールドに新しいレコードを作成すると、関連付けの手がかりとなるフィールドに自動的に値が設定されます。その動作を踏襲したものです。

レコードの複製とリレーションシップ

 レコードの複製は、『3-3 レコードの追加・削除・複製』で説明しました。レコードの複製は、Ver.5.2現在、MySQLのみで利用できます。その他のデータベースエンジンの場合は、対応するまでしばらくお待ち下さい。

1「def07.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def07.phpを編集する」をクリックします。
2personコンテキストのrepeat-controlに「copy-contact」というキーワードを追加します。スペースを前に入れて区切ります。
3contactコンテキストのrepeat-controlに「copy」というキーワードを追加します。スペースを前に入れて区切ります。
4「page07.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。適当なレコードで作業をしてください。以下の図はidフィールドが「1」のレコードが見えていて、全部で6レコードあります。異なるレコードが見えている状態でもかまいません。
5「レコード複製:person」ボタンをクリックすると、画面上に大きな変化はありませんが、idフィールドがそれまでのレコード数よりもひとつ多いレコードが新たに作成されました。つまり、idフィールドが「1」のpersonコンテキストにあるレコードと、さらにそのレコードに関連したcontactコンテキストの3つのレコードが複製され、関連付けられたcontactコンテキストのperson_idの値は、personコンテキストのidフィールドの値である「7」(この例の場合)が設定されています。
6さらに、contactコンテキストの2行目の「複製」ボタンをクリックしました。その行と同じ内容レコード(ただし、idフィールドのみ自動的に設定されるので、変化している)が作成されています。

演習のまとめ

エンクロージャーやリピーターであることを無視する

 TABLEタグの中にテーブルタグがあると、このセクションで説明したように、そのままでは、それぞれ別々のコンテキストとして展開されます。したがって、relationキーの値がコンテキストに設定されていれば、それに従って内側のテーブルには関連するレコードが表示されます。しかしながら、テーブル内のテーブルは、外側と同じコンテキストを表示したいような場合があります。その時は、data-im-control属性にignore_enc_repという値を設定します。例えば、リスト4-3-2の場合だと、内側と外側のテーブルは共通のコンテキストpersonのフィールドをリンクノードに対してバインドします。内側のテーブルのTBODYとTRタグ要素にdata-im-control="ignore_enc_rep"が指定されているので、これらのタグ要素はエンクロージャーやリピーターとして判別しません。内側にテーブルが2つありますが、どちらも外側のテーブルの一部として扱われるため、どちらも同一のpersonコンテキストのレコードを展開します。したがって、このリストの部分はpersonコンテキストで定義された内容にしたがって、1回だけデータベースから取り出しを行いそれを元にページ合成を行います。

リスト4-3-2 ignore_enc_repが指定されたテーブルを含むテーブルの例
<table>
    <tbody>
        <tr><th>Name</th><td data-im="person@name"></td></tr>
        <tr><th>Age</th><td data-im="person@age"></td></tr>
        <tr>
            <td>
                <table>
                    <tbody data-im-control="ignore_enc_rep">
                         <tr data-im-control="ignore_enc_rep">
                             <th>Interest</th><td data-im="person@interest"></td>
                         </tr>
                         <tr data-im-control="ignore_enc_rep">
                             <th>Focus</th><td data-im="person@focus"></td>
                         </tr>
                    </tbody>
                </table>
            </td>
            <td>
                <table>
                    <tbody data-im-control="ignore_enc_rep">
                         <tr data-im-control="ignore_enc_rep">
                             <th>Prefer</th><td data-im="person@preferfood"></td>
                         </tr>
                         <tr data-im-control="ignore_enc_rep">
                             <th>Hate</th><td data-im="person@hatefood"></td>
                         </tr>
                    </tbody>
                </table>
            </td>
        </tr>
    </tbody>
</table>

このセクションのまとめ

 エンクロージャーとリピーターのセットがあれば、レコードの数だけそれらを複製し、レコードに含まれているフィールドの値を合成することで、クエリー結果のレコードを一覧します。さらに、リピーターの中に別のエンクロージャーがあれば、新たにデータベースアクセスを行いレコードを同様に展開します。このデータベースアクセス時には、上位のリピーターに含まれる指定したフィールドの値を手掛かりにした検索条件を付与できるので、リレーションシップに基づく1対多のレコードの取得もできます。また、それらのコンテキスト間で独立した検索も可能で、常に一定の選択肢のポップアップメニューの表示内容をマスターテーブルから取り出すという場合も同様な仕組みを利用します。FileMakerではポータルの値を取り出すことで、ある程度の効率化が可能です。

4-4計算プロパティの設定

データベースから得られた結果に含まれないプロパティを追加するのが「計算プロパティ」の仕組みです。値を求める計算式と共に定義ファイル内で定義することで、計算結果をあたかもフィールドのひとつのようにページファイル上に表示することができます。

計算プロパティの定義

 INTER-Mediatorのようなフレームワークは、データベースの内容を取り出して表示するなどの処理します。このとき、データベースの内容をプログラムの中で再現し、それをもとにさまざまな作業を組み立てています。この内部的なデータ表現は一般に「モデル」と呼ばれます。1レコードがひとつのオブジェクトとしてモデル内で管理されます。そのオブジェクトは、フィールド名をプロパティにした値を持ちます。こうしたオブジェクトを主体にした考え方が「モデル」の基本的な考え方です。現在のJavaScriptベースのいくつかのフレームワークでは、データベースの内容を「モデル」として抽象的に表現し、そのモデルとユーザーインターフェース要素のやりとりをするという考え方を採用しています。

 通常は、フィールドはデータベースにあるものですが、そのひとつを計算式で生成してしまうのが「計算プロパティ」です。計算結果を新たなフィールドとして追加する仕組みは、フィールドという名称を使わずに「計算プロパティ(Calculated Property)」と呼ばれるのが一般的ですので、INTER-Mediatorもそれに習いました。FileMakerで言えば、計算型フィールドに相当するものです。SQLだと、ビューで計算結果の列を追加でき、動作や概念はそれとよく似ています。

 計算プロパティは、定義ファイルのコンテキスト定義の中に記述できます。PHPで記述した定義ファイルの一部の例は、リスト4-4-1に示します。calculationキーに配列で指定します。その配列は、fieldキーと、expressionキーを持つ連想配列です。文字通り、fieldキーで新たに創出される計算プロパティのフィールド名を指定し、計算式をexpressionキーの値で記述します。なお、計算式を組み込むコンテスト定義では、必ずkeyキーによる主キーの設定をしてください。計算式については、この後にまとめて説明をします。

リスト4-4-1 定義ファイルでの計算プロパティの定義例
IM_Entry( array (
  array (
    "name" => "item",
    "key" => "id",	// keyキー指定は必須
    "repeat-control" => "confirm-delete confirm-insert",
    "calculation" => array (
       array (
         "field" => "price",
         "expression" => "qty * product_unitprice",
       ),
       array (
         "field" => "color",
         "expression" => "if ( qty > 100, 'red', if ( qty > 10, 'green', 'yellow' ) )",
       ),
    ), ....

 同様な設定を定義ファイルエディターで行うときの例が、図4-4-1です。このCalculationsの見出しの部分は、通常は見えておらず、Show Allボタンを押さないと見えませんので注意してください。

図4-4-1 定義ファイルエディターでの計算プロパティの定義例

 計算プロパティは、定義ファイルのコンテキスト定義の中に記述します。その結果、そのコンテキストを通じてデータベースへのクエリーを行った表形式のデータに、計算結果が得られるフィールドが増えると考えてください。図4-4-2はその動作の様子を示したものです。priceという計算プロパティによるフィールドが増えていますが、同一のレコードにあるフィールドの値を元に計算した結果を値としてもちます。なお、計算結果が値なので、逆に値を変更することはできず、計算プロパティの値は読み出しのみとなります。

図4-4-2 計算プロパティによるフィールドの増加

計算式の作成

 計算式のルールは、INTER-Mediatorのマニュアルサイトにある「計算式」のページに記載しています。(本コースはデジタル版なので、詳細はリンクとします。)

 まず、式の項として利用できるのは数値や文字のリテラル(記述した通りの一定のデータ)、フィールド、いくつかの定数です。数値は原則として一般的な記述で問題ありませんが、「e」を使用する指数表示や16進数はサポートしていません。文字列は、シングルクォーテーションで囲みます。ダブルクォーテーションはサポートしていません。文字列の中にシングルクォーテーションの文字がある場合には、バックスラッシュに続いてシングルクォーテーションを記述してください。

 四則演算子などの記号はそのまま利用でき、数学のルールにしたがって、「10 + 20」などと記述できます。また、半角の ( ) による計算順の指定も可能です。

 フィールド名はそのまま、フィールド名で記述が可能です。そのとき、フィールド名だけの場合は、同じレコードの該当するフィールド名のデータに置き換えて計算を行います。式の中で、「コンテキスト名@フィールド名」の形式の項を記述すると、その計算プロパティが存在するリピーターの内部にある、コンテキスト名のエンクロージャーを探し出し、そのエンクロージャーに対応するリピーターから該当するフィールドの値を抜き出します。要するに、HTMLで言えば、内部にある別のコンテキストの展開を探してフィールドの値を集めるということになります。通常、そのような場合はフィールドの値はひとつとは限りませんが、sumなどの合計するような関数(複数の値を受け入れる関数)で処理をすることで、合計を求めることなどができます。なお、より上位のエンクロージャーとリピーターにさかのぼっての参照はサポートしていません。

 前述の「計算式」のページには、利用できる関数が一覧されています。条件分岐にはifを利用します。なお、日付や時刻の関数を用意していますが、1日を1として数値で管理する場合と、1秒を1として数値で管理する場合の、両方の関数を用意しました。SQLで言えばDATE型のフィールド値は1日が1として処理をし、DATETIME型だと1秒を1として計算するのが自然と考えて、このような関数の構成になっています。

 計算式の結果をページ上に表示することだけでなく、スタイルや属性の値として設定することもできます。そのときは、ターゲット指定を「コンテキスト名@計算プロパティ名@style.スタイル属性名」のような記述にします。あるコンテキストcontextにあるフィールドisShowが1のときにはあるタグを表示にして、そうでない場合には非表示にしたい場合、コンテキストに、fieldが例えば「showStr」、expressionに「if ( isShow = 1, 'inline', 'none' )」のような定義を行い、要素は「<span data-im="context@showStr@style.display"></span>」のように記述します。これで、isShowフィールドが1の場合、showStrの値は「inline」となり、この要素のdisplayスタイルの値はinlineとなります。isShowフィールドが1でない場合にはshowStrの値は「none」となり、この文字列が要素のdisplayスタイルに設定されるので、要素は見えなくなります。

演習計算プロパティを追加する

 計算プロパティの利用例を、同一レコードの値から計算する場合、別のコンテキストの値を合計する場合、さらにスタイル属性に計算結果を適用する場合の3通りの方法で説明します。

元になるページの準備

1演習環境を起動します(『1-2 演習を行うための準備』を参照)。続いて、ブラウザーで、「http://localhost:9080」に接続します。「トライアル用のページファイルと定義ファイル」というタイトルの部分を特定します。
2「def09.phpを編集する」をクリックし、定義ファイルエディターでdef09.phpファイルを編集します。(もし、他の用途で9番目を利用しているのなら、例えば、def21.phpを利用するなど、別の番号のセットを使用してください。その場合ソースコードの記述が変わる部分がありますが、可能な限り注記します。)
3Contextsの中のQueryと書かれた背景がグレーの部分を特定します。そして、その次の行の右の方にある「削除」をクリックして、Queryの設定がある行を削除します。
4「レコードを本当に削除していいですか?」とたずねられるので、OKボタンをクリックします。
5同様に、Sortingの次の行にある「削除」ボタンを押し、確認にOKボタンをクリックして、こちらの設定も削除しておきます。
6nameに「product」、keyを「id」、pagingを「true」、repeat-controlを「confirm-delete confirm-insert」、recordsとmaxrecordsを「1」とします。Contextsのその他のテキストフィールドは空白にします。
7Database Settingsに設定を行います。
[MySQL]の場合
db-classは「PDO」のままでかまいません。dsnに「mysql:host=db;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
[FileMaker]の場合
db-classを「FileMaker_DataAPI」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「gateway.docker.internal」、portに「443」、protocolに「https」、cert-vefifyingに「false」と入力します。
8Debugについては、「false」にすると、デバッグ情報が出なくなります。なお、デバッグ情報をみながら動作を確認したい方は、「2」のままにしてこの後の作業を行ってください。
9「http://localhost:9080」で開いたページに戻り「page09.htmlを編集する」をクリックし、ページファイルのpage09.htmlを編集するページファイルエディターが開きます。HTMLでの記述内容を以下のように変更します。太字が追加する箇所を示します。(別の番号のセットで作業している場合には、該当する番号のリンクをクリックしてください。)
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <script type="text/javascript" src="def09.php"></script>
</head>
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tr><th>id</th><td data-im="product@id"></td></tr>
    <tr><th>acknowledgement</th>
      <td data-im="product@acknowledgement"></td>
    </tr>
    <tr><th>name</th>
      <td><input type="text" data-im="product@name"/></td>
    </tr>
    <tr><th>unitprice</th>
      <td><input type="text" data-im="product@product_unitprice"/></td>
    </tr>
    <tr><td colspan="2">
      
    </td></tr>
  </table>
</body>
</html>
10「http://localhost:9080」で開いたページに戻り、「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。productテーブルの内容が、1レコードずつ参照できます。ここまでは、すでに説明した通りです。

contactテーブルの関連レコードを表示する

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2Contextsのすぐ下にある「追加」ボタンをクリックして、コンテキスト定義をひとつ増やします。
3nameに「item」、keyを「id」、repeat-controlを「confirm-delete confirm-insert」とします。Contextsのその他のテキストフィールドは空白にします。
4itemコンテキストの中のRelationshipのすぐ下にある「追加」ボタンをクリックして、設定のための行を追加します。foreign-keyに「product_id」、join-fieldに「id」、operatorに「=」を指定します。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。テーブルの中にテーブルがある点に注意してください。外側のテーブルはproductコンテキスト、内側のテーブルはitemコンテキストで得られたデータを展開したものです。
    <tr><th>unitprice</th>
      <td><input type="text" data-im="product@product_unitprice"/></td>
    </tr>
    <tr><td colspan="2">
      <table>
        <thead>
          <tr><th>invoice_id</th><th>qty</th><th>unitprice</th><th></th></tr>
        </thead>
        <tbody>
          <tr>
            <td data-im="item@invoice_id"></td>
            <td><input type="text" data-im="item@qty"/></td>
            <td><input type="text" data-im="item@product_unitprice"/></td>
            <td></td>
          </tr>
        </tbody>
      </table>
    </td></tr>
  </table>
</body>
</html>
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://localhost:9080」で開いたページに戻り、「page09.htmlを表示する」をクリックします。productテーブルのidフィールドの値と、itemテーブルのproduct_idフィールドの値が照合され、下半分に見せる内側のテーブルには、同一のproduct_idフィールドの値を持つレコードが見えています。
7内側のテーブルの下の方にある「追加」ボタンをクリックして、レコードの数を5、6個程度まで増やしておきます。invoice_idの値が空欄のままですが、このフィールドはサンプルのひとつ「Sample_invoice」で、invoiceテーブルと関連づけるためのフィールドです。この演習ではinvoiceテーブルを使わないので、空欄のままでも気にしなくてかまいません。

同一コンテキストの値から計算する

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2ページ冒頭のShow Allボタンをクリックして、すべての設定項目を見えるようにします。
3itemコンテキストの中に、Calculationsという見出しがあります。そこの下にある「追加」ボタンをクリックします。
4新たに登場した行で、fieldは「price」、expressionは「qty * product_unitprice」と入力します。Tabキーで移動して確定することを忘れないでください。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。定義ファイルに定義した「price」が、あたかもitemコンテキストのフィールドのように記述できることを確認してください。
<table>
  <thead>
  <tr><th>invoice_id</th><th>qty</th><th
      >unitprice</th><th>price</th></tr>
  </thead>
  <tbody>
    <tr>
      <td data-im="item@invoice_id"></td>
      <td><input type="text" data-im="item@qty"/></td>
      <td><input type="text" data-im="item@product_unitprice"/></td>
      <td data-im="item@price"></td>
      <td></td>
    </tr>
  </tbody>
</table>
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://localhost:9080」で開いたページに戻り、「page09.htmlを表示する」をクリックします。qtyやproduct_unitpriceフィールドに適当に値を入力します。priceフィールドは、qtyフィールドとproduct_unitpriceフィールドの乗算結果が見えています。テキストフィールドを入力するたびに再計算されています。また、再計算はTabキーにより、フィールドの値を確定させた後に行われており、キータイプごとに実行されていないことも分かります。

別のコンテキストの値を集計する

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2すべての設定項目が見えていない場合には、ページ冒頭のShow Allボタンをクリックして、すべての設定項目を見えるようにします。
3productコンテキストの中に、Calculationsという見出しがあります。そこの下にある「追加」ボタンをクリックします。
4新たに登場した行で、fieldは「total」、expressionは「sum(item@price)」と入力します。Tabキーで移動して確定することを忘れないでください。この式は、productコンテキストから、itemコンテキストを参照し、そこにある複数のpriceフィールド(計算プロパティ)の値をsum関数で合計し、さらにformat関数で、カンマ付き数字に変換しています。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。定義ファイルに定義した「price」が、あたかもitemコンテキストのフィールドのように記述できることを確認してください。
<body>
  <div id="IM_NAVIGATOR"></div>
  <table>
    <tr><th>id</th><td data-im="product@id"></td></tr>
    <tr><th>acknowledgement</th>
      <td data-im="product@acknowledgement"></td>
    </tr>
    <tr><th>name</th>
      <td><input type="text" data-im="product@name"/></td>
    </tr>
    <tr><th>unitprice</th>
      <td><input type="text" data-im="product@product_unitprice"/></td>
    </tr>
    <tr><th>total</th>
      <td data-im="product@total" data-im-format="number()" data-im-format-options="useseparator"></td>
    </tr>
    <tr><td colspan="2">
      <table>
          :
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://localhost:9080」で開いたページに戻り、「page09.htmlを表示する」をクリックします。qtyやproduct_unitpriceフィールドに適当に値を入力し、テキストフィールドを入力するたびに再計算されていることを確認してください。
ここで、totalの計算値は整数ですが、画面上には、3桁ごとの区切りが入って表示されています。このように、データの書式を整えて、HTMLの要素の中に表示する仕組みは、data-im-formatなどの追加属性で指定できます。この機能の詳細は、すぐ後の『4-5 表示形式をページファイルで指定する』で解説します。

計算結果をスタイルに反映させる

1「def09.phpを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「def09.phpを編集する」をクリックします。
2すべての設定項目が見えていない場合には、ページ冒頭のShow Allボタンをクリックして、すべての設定項目を見えるようにします。
3itemコンテキストの中に、Calculationsという見出しがあります。すでに1行分の設定がありますが、その下にある「追加」ボタンをクリックします。
4新たに登場した行で、fieldは「color」、expressionは「if ( qty > 100, 'red', if ( qty > 10, 'green', 'yellow' ) )」と入力します。Tabキーで移動して確定することを忘れないでください。この式は、同じレコードのqtyフィールドの値に応じて、100より大きければ「red」、10より大きければ「green」、それ以外だと「yellow」という文字列を求めます。
5「page09.htmlを編集する」をクリックして表示したタブあるいはウインドウを表示します。もし、閉じていたら「http://localhost:9080」で開いたページに戻り、「page09.htmlを編集する」をクリックします。太字で示した部分を追加します。data-im属性に2つのターゲット指定があり、それらは空白で区切ってください。定義ファイルに定義した「color」は、文字列を値として持ちますが、その値を、data-im属性があるタグ要素のcolorスタイル、つまり文字色としてスタイル情報へ結果を適用します。つまり、qtyフィールドの値に応じてpriceフィールドの文字列の色が、赤、緑、黄色に変化するということです。
    <tr><td colspan="2">
      <table>
        <thead>
          <tr><th>invoice_id</th><th>qty</th>
            <th>unitprice</th><th>price</th></tr>
        </thead>
        <tbody>
          <tr>
            <td data-im="item@invoice_id"></td>
            <td><input type="text" data-im="item@qty"/></td>
            <td><input type="text" data-im="item@product_unitprice"/></td>
            <td data-im="item@price item@color@style.color"></td>
            <td></td>
          </tr>
        </tbody>
      </table>
    </td></tr>
  </table>
</body>
</html>
6「page09.htmlを表示する」をクリックして表示したタブあるいはウインドウを表示します。必要に応じて、ブラウザーの更新ボタンを押してください。開いていない場合には、「http://localhost:9080」で開いたページに戻り、「page09.htmlを表示する」をクリックします。qtyにいろいろな値を入れてみて、priceフィールドの文字列の色が変化することを確認してください。

演習のまとめ

このセクションのまとめ

 コンテキストに計算プロパティを定義することで、他のフィールドなどを元にした計算結果の値を持つフィールドが追加されたものとして、そのフィールドをページファイルのターゲット指定に指定できます。計算式では、同一レコードの他のフィールドの参照や、HTMLのノード上で包含する他のコンテキストのフィールドを参照することもできます。

4-5表示形式をページファイルで指定する

ページ上に見えるデータは、データベース上のものと原則は同じものですが、数値や日付、時刻は、より見やすい形式に書式設定して表示したいと考えます。データコンバータークラスは、サーバーから送り出すときやサーバーが受け取ったときに変更をするものですが、これに加えて、完全にクライアント側で書式設定を稼働できる機能が実装されています。

ページファイルでの書式指定

 書式設定は機能の上ではそれほど難しくないものです。ExcelやFileMaker等での書式設定をご存知であれば、ほぼその機能だと思っていただいてOKです。ページファイルの中のリンクノードについて、data-im-format属性があれば、その属性の記述に従って書式化された値が、ターゲット指定に従った場所に表示されます。設定可能な文字や記号などについては、HTMLの属性に記述する書式を適用する機能に掲載がありますので、本書ではポイントや設定例を中心に説明をします。

 表4-5-1に、ページファイルのタグ内に記述できる属性をまとめました。data-im-formatに加えて、数値に関する細かな書式設定を別の属性として指定が可能です。

属性役割
data-im-format書式設定(この属性のみ設定が必須)
data-im-format-options数値の3桁区切りなど
data-im-format-negative-color負の数の場合の色
data-im-format-negative-style負の数の表示形式
data-im-format-numeral-type数字の種類
data-im-format-kanji-separator数字を日本語で表示する
表4-5-1 書式指定関連のHTML属性

数値の書式設定

 数値やあるいは通貨表示は、data-im-format属性の値を「number()」あるいは「currency()」とします。()内は少数以下の桁数を示すもので、省略すると0を指定したものとみなします。通貨は、ロケールに応じた通貨記号を通常は前に付与します。リスト4-5-1は、itemコンテキストのproduct_unitpriceフィールドとバインドしたテキストフィールドですが、テキストフィールド上に、書式化された数値が表示されます。data-im-format-options属性の値が「useseparator」なので、3桁ごとのカンマも表示されます。

リスト4-5-1 数値をカンマ付きで表示する
<input class="price" type="text" size="8"
        data-im="item@product_unitprice" data-im-format="number()"
        data-im-format-options="useseparator">

 リスト4-5-2は、数値を通貨として表示する例です。通貨記号は、IM_Entry関数でのオプション指定にある'app-locale'キーや'app-currency'キーの値、もしくはparams.phpファイルに記述する$appLocale変数や$appCurrency変数に依存して決められます。もちろん、日本を指定していれば、¥が通貨記号として使われます。こちらもdata-im-format-options属性の値が「useseparator」なので、3桁ごとのカンマも表示されます。

リスト4-5-2 数値をカンマ付きかつ通貨記号をつけて表示する
<input class="price" type="text" size="8"
        data-im="item@product_unitprice" data-im-format="currency()"
        data-im-format-options="useseparator">

 これらの書式設定を行うと、テキストフィールドに「1,200」や「¥ 1,200」と表示されます。ここで編集の動作を説明します。もちろん、「きちんと書式を設定した文字列に書き換える」ということでも問題はありませんが、数値の書式設定を行ったテキストフィールドに関しては、数字とピリオド(小数点)以外の文字を無視します。したがって、「1,200」を「1,2000」と変更してTabキーを押すと、12000が数値としてフィールドに保存され、ページ上では「12,000」と見えるようになります。ただし、特殊は負の数の記号や、日本語の数値についてはデータ修正時の処理は正しく機能しませんので、これらの書式についてはテキストフィールドをreadonly属性を指定しておくか、あるいはDIVタグ等の編集できない要素にバインドするようにします。

日付や時刻の書式設定

 日付や時刻の書式設定は、data-im-format属性のみを使用します。「%Y」で4桁の年などとフォーマット文字列が定義されているので、それを利用して任意の形式の書式が設定できますが、シンプルな方法は、data-im-format属性に「date(...)」「time(...)」「datetime(...)」と指定することです。()内部は「long」「middle」「short」のいずれかの単語が指定できます。リスト4-5-3はdata-im-format属性の「date(long)」を指定した例です。()内の文字列をクォーテーションで囲う必要はありません。

リスト4-5-3 日付を書式設定する
<input type="text" data-im="invoice@issued"
        data-im-format="date(long)">

 これにより、テキストフィールドでは「2017年11月12日 日曜日」と表示されます。date、time、datetimeの使い分けは、フィールドの型とは関係なく使用して構いません。dateやtimeを指定すれば、型がdatetimeであっても、日付のみ、時刻のみを表示するといった動作をします。date型でdatetimeを使うことはあまり意味はありませんが、時刻が単に00:00:00になるだけです。

 long、middle、shortでの表示例を、表4-5-2に示しておきます。表の中の「ロケール指定」は、IM_Entry関数のオプション引数にあるapp-localeの値、あるいはparams.phpファイルの$appLocale変数で指定したカントリーコード、あるいはそれらがない場合にはブラウザーから得られたカントリーコードです。なお、datetimeの場合は、dateとtimeの間に半角のスペースを入れた文字列として生成されます。Ver.5.7-devの段階では、これらの設定は、英語と日本語のみの設定です。他の言語が必要な方は、IMLocaleFormatTable.phpファイルを参照していただき、是非ともソースコードをコミットしてください。

ロケール指定en, en_USja_JP
date(long)Sunday, November 12, 20172017年11月12日 日曜日
time(long)17:45:0017時45分00秒
date(middle)Sun, Nov 12, 20172017/11/12(日)
time(middle)17:45:0017:45:00
date(short)11/12/20172017/11/12
time(short)17:4517:45
表4-5-2 既定の日付時刻書式の例

 年月日等の要素を指定した位置に配置するなどは、dateなどの後の()内に書式指定文字列として設定します。リスト4-5-4は、%gにより元号を表示する方法です。この書式設定で、「平成29年11月12日」などとテキストフィールドに表示されます。どのような書式指定文字列が使えるかは、HTMLの属性に記述する書式を適用する機能を参照してください。書式指定文字列以外の文字はそのまま出力します。なお、書式指定文字列は、いくつかの種類がありますが、いずれも一貫性がない面があるため、それらを参考に独自に定義しました。特に、アルファベットの大文字と小文字は、同一の要素を示すように定義してあり、桁が多くなる傾向にある方を大文字にしてあります。また、タイムゾーンについてはデータベースの日付時刻フィールドにタイムゾーンを含めて記録することはあまり一般的でないと考え、書式設定においてもタイムゾーンの記述は行っていません。

リスト4-5-4 年号を付けた日付として書式設定する
<input type="text" data-im="invoice@issued"
        data-im-format="date(%g年%m月%d日)">

 書式設定した日付時刻をテキストフィールドで表示した場合、限定的に修正してデータベースへの更新が行えます。おおむね、西暦の年月日、24時間制の時刻の数値であれば修正できると考えてください。また、英語の月名は最初の3文字で判別して、月として修正可能です。一方、和暦や2桁年号、12時間制でAM/PM等が付く時刻の修正はできません。テキストフィールドにdate(long)によって「2017年11月12日 日曜日」と表示されていた場合、「2017年11月13日 日曜日」と変更した場合、曜日は無視して、2017-11-13が入力されたものとしてデータベース側を更新します。また、テキストフィールド内も更新されて「2017年11月13日 月曜日」と表示されるはずです。つまり、曜日は無視して変更してOKです。

 なお、日付時刻については、ISO8601形式の「2017-11-13 18:09:00」や、あるいはデータベースが「2017/11/13 18:09:00」の形式を受け入れる場合には、テキストフィールドにこの形式で値を入力できます。日付や時刻の場合、設定されている書式であると仮定して文字列を解析しますが、年月日等の数値が得られない場合は、入力されたデータそのものでデータベース更新をしようとします。したがって、書式の設定に関係なく、データベースが受け入れる形式で入力することができるようになっています。ただし、書式設定が解釈を変えてしまうような設定になっていないことが前提です。

ロケールへの対応

 言語や国によって、日付の形式や通貨記号が変わります。「どの言語、どの国なのか」という点について、INTER-Mediatorでの扱いを説明します。まず、前提として、ブラウザーから得られるクライアントの言語と国の情報があります。通常は、クライアントからのリクエストに含まれているAccept-Languageヘッダーの内容を元に、サーバー側で言語の判定が可能です。INTER-MediatorではAccept-Languageヘッダーの最初の項目を取り出して、これを「ブラウザーの言語および国」としてまず識別します。一般には利用しているOSの国と言語の情報通りですが、ブラウザーは言語の設定をカスタマイズできるので、日本語としてOSを使っているけれども、ブラウザーは英語で使うという方もいらっしゃるかもしれません。その場合はブラウザーの言語および国は、英語でアメリカと認識します。

 一般にはブラウザーの言語および国の情報に従って処理をすることになると考えられているかもしれませんが、Webアプリケーションではむしろそのような状況になって欲しくないことの方が一般的なのではないかと考えました。例えば、データベースのpriceフィールドがINT型だったとして、あるレコードでは100という数字が入力されていたとします。その数字が、ブラウザーを利用する国に応じて、¥100、$100のように表示されてしまうとしたらどうでしょうか? むしろ、ブラウザーの言語が日本語でも英語でも、¥100と表示されてほしい場合の方が多いと思われます。

 そこで、国と言語の設定を、INTER-Mediatorのアプリケーション側で行うようにしました。定義ファイルでのIM_Entry関数に指定する2つ目の引数(オプション設定)に、'app-locale'というキーで、「ja_JP」「en_US」などのUNIXロケール指定を値として指定します。また、通貨のみを特定の国指定ができるよに、'app-currency'というキーも指定できるようになっています。'app-currency'キーがない場合には、通貨記号は'app-locale'キーの指定に従います。なお、これらの設定は、『2-6 設定ファイルparams.php』で説明するparams.phpでの変数$appLocale、$appCurrencyでも設定でき、こちらはシステム全体に渡って言語を指定できます。INTER-Mediatorは、Ver.5.6-devより、params.phpの初期状態として、$appLocaleを「ja_JP」、$appCurrencyを「JP」と設定してあります。この設定を「INTER-Mediatorで指定するロケール」と呼ぶことにします。

 このように、2つの国や言語の設定がありますが、INTER-Mediatorで指定するロケールの方が、ブラウザーの言語および国よりも優先度は高くなります。したがって、'app-locale'あるいは$appLocaleを指定した状態で使用するのが通常の状態であると想定しています。

カンマが出ないなどのトラブル

 INTER-MediatorではOSのロケールの機能を利用して、書式設定の情報を得ています。macOSやWindowsのようなデスクトップ向けOSでは通常各国のロケールが設定されているので、任意の国コードを指定できるでしょうけれども、Linux Serverでは最低限のロケールしか搭載していないことも多く、日本語を指定するとうまく機能しない場合もあります。その場合、OSにロケールを追加する必要があります。『9-2 Webサーバーのインストールと準備』の『システムロケールについて』を参考にしてください。

このセクションのまとめ

 data-im-formatやあるいは関連するHTML属性を指定することで、データベースから得られた値を、数値や通貨、日付時刻等へと書式化して表示することができます。また、一定のルールに従っていれば、書式化した文字列を変更することで、元データの更新もできるようになっています。

4-6データコンバータークラスを使ったフィールド単位の変換

データベースから取り出したデータを書式化するなど、1フィールド単位での変換機能が「データコンバータークラス」という仕組みです。サーバーサイドで稼働し、読み取った直後あるいは書き込む直前に、コンバータを介してデータの変更が行えます。なお、以前は書式設定はデータコンバーターを中心に行なっていたのですが、data-im-format属性で指定する書式設定の方が手軽でわかりやすいことから、カンマ付き数値や日付の書籍設定などはdata-im-formatを使う方が良いでしょう。

データコンバータークラス

 フィールド単位でのデータ変換をサポートするのがデータコンバータークラスで、表4-6-1に示すような、さまざまなクラスがINTER-Mediatorに付属しています。自分で作成することもできます(『Chapter 8 サーバーサイドでのプログラミング』を参照)が、一般的なコンバーターは付属のクラスで行えるでしょう。クラス名は、必ずDataConverter_で始めるのが規則です。データコンバータークラスの動作を具体的に説明しましょう。DataConverter_MySQLDateTimeクラスはMySQLのDATEやDATETIME型などの日付時刻のフィールド用です。SQLデータベースでは、フィールドから得られるデータは「2015-06-12 13:45:00」といった形式です。このまま表示することは日本での開発ではほとんどなく、「2015/06/12」等の書式設定をしたくなります。データコンバータークラスは、適用するフィールドを指定することで、初期設定が可能です。また、多くの場合、「2015/06/12」から「2015-06-12」への変更といった、もともとの形式に戻すということも行います。フィールドにコンバーターの適用を設定するだけで、双方向の変換ができるのです。なお、現在のバージョンは書式設定にはdata-im-format属性を使う方が手軽です。データコンバータークラスは、マークダウン形式の文字列をHTMLに変換する場合や、文字列の改行をHTMLのタグに変換するなどの処理においては、利用する意味があります。

クラス名動作parameterに指定する値
DataConverter_AppendPrefix指定文字を前につける付加する文字列
DataConverter_AppendSuffix指定文字を後につける付加する文字列
DataConverter_HTMLStringクエリ時のみ、改行のBRタグと& < > の参照形式への変換(不要)
DataConverter_MarkdownStringマークダウン形式をHTML文字列に変換する(読み出しのみ)(不要)
DataConverter_FMDateTimeFileMakerの日付、時刻、タイムスタンプ日付時刻の書式、省略可
DataConverter_MySQLDateTimeMySQLの日付、時刻、タイムスタンプ日付時刻の書式、省略可
DataConverter_Numberカンマ区切りの数値小数点以下の桁数
DataConverter_Currency通貨表記の数値小数点以下の桁数
DataConverter_NumberBase数値表示の基底クラス(そのままでは使えない)-
DataConverter_templateインターフェース定義のみ(そのままでは使えない)-
表4-6-1 添付されているデータコンバータークラス

 DataConverter_FMDateTimeやDataConverter_MySQLDateTimeのparameterキーに対する値は、書式指定文字列を使用します。完全なリファレンスは、PHPのマニュアルにあるstrftime関数のリファレンスを参照してください。よく使うのは、年が「%Y」、月が「%m」、日が「%d」、時分秒がそれぞれ「%H」「%M」「%S」です。これらとスラッシュやコロンの組み合わせで多くの場合は対処できるでしょう。

 DataConverter_MarkdownStringクラスについても説明しましょう。入力用にはTEXTAREAタグ要素を使ってフィールドに入力しますが、この時、行頭に*や-などの記号を使ったいわゆるマークダウン形式で入力します。出力時にこのデータコンバータークラスを利用してHTML形式に変換します。したがって、ターゲット指定の3つ目の設定ではinnerHTMLで結果を受け取る必要があります。サポートしているマークダウン表記を表4-6-2に、さらに生成されるHTMLに含まれるCSSクラス名を表4-6-3に示します。

行頭の記号動作
*その行をHnタグで囲む。nは*の個数に対応する
-その行を箇条書きにする。-を重ねて階層的に記述することも可能
#クラスが「_im_markdown_p1」のPタグで囲む。#は2つおよび3つにも対応し、クラス名の末尾の数字と#の個数が対応する
@@IMG[file]href属性がfileのIMGタグを生成し、さらにクラスが「_im_markdown_para_img」のPタグで囲む
|TABLEタグで表を作る。冒頭、セルの区切り、末尾に|を入れる
表4-6-2 サポートしているマークダウン表記
CSSのクラス名設定される要素
_im_markdownマークダウンの領域全体を囲むDIVタグ要素のクラス
_im_markdown_ul箇条書き全体
_im_markdown_li箇条書きの項目
_im_markdown_h1〜ヘッダー要素
_im_markdown_p1〜3#で始まる場合のPタグ要素
_im_markdown_para_imgIMGタグを囲むPタグに設定されるクラス
_im_markdown_tableテーブルのクラス
_im_markdown_trテーブルの行
_im_markdown_tdテーブルのセル
_im_markdown_para特別な表記がない場合、Pタグに入れられこのクラス名が指定される
表4-6-3 マークダウンより生成されるHTMLに含まれるCSSクラス名

 データコンバータークラスは、コンテキストではなく、オプション(定義ファイルエディターではOptions)での設定になります。「オプションでの設定」とは、定義ファイルに記述するIM_Entry関数の2つ目の引数に指定するということです。取り出したデータというよりも、データベース側のテーブルやビューにあるフィールドに対して設定をすることで、例えばひとつのテーブルから複数のコンテキストを利用しているときにも、ひとつの設定が複数のコンテキストに対して適用されることを意図しています。データコンバータークラスは、表4-6-4に示すキーを使った連想配列の配列を定義し、その値をformatterキーの値として指定します。リスト4-6-1は定義ファイルでの設定例で、定義ファイルエディターでの設定結果の例は図4-6-1に示します。

キー指定する値
field「テーブル名@フィールド名」の形式、コンテキスト名ではなくテーブルやビューの名前
converter-classコンバータークラスの名前。PHPのクラス名からDataConverter_を除く
parameterコンバーターが要求するパラメター(不要なクラスもある)
表4-6-4 formatterキーの配列に指定可能なキー
リスト4-6-1 定義ファイルに指定したコンバータークラスの例
require_once('INTER-Mediator/INTER-Mediator.php');

IM_Entry(
  array (  // ここからIM_Entryの2つ目の引数(コンテキスト)
    array (
      'name' => 'history',
      'key' => 'id',
    ), .....
  ),
  array (  // ここからIM_Entryの2つ目の引数(オプション)
    'formatter' => array (
      array (
        'field' => 'history@startdate',
        'converter-class' => 'MySQLDateTime',
        'parameter' => '%Y/%m/%d',
      ),
    ),
  ),
  array (  // ここからIM_Entryの2つ目の引数(データベース設定)
    'db-class' => 'PDO', .....
  ),
  false
);
図4-6-1 定義ファイルエディターで指定したコンバータークラスの例

このセクションのまとめ

 データコンバータークラスにより、フィールド単位でのデータの変換をサポートします。データベースからの取り出し時はもちろん、データベースに書き込むときにも適切な変換ができる仕組みを持っています。日付や数値を中心として、汎用的なクラスはINTER-Mediatorに付属します。なお、本機能は初期の頃からあるものの、さまざまな代替機能が実装されており、他の方法で実現したほうがより手軽な場合もあります。

4-7データベースの機能の利用

このセクションでは、定義ファイルに指定する項目のうち、データベース側の仕組みを利用する機能についてまとめておきます。このセクションは演習はありません。

処理実行前後のスクリプトの処理

 コンテキスト定義にscriptキーで記述する内容は、FileMakerとそれ以外のSQLデータベースで大きく動作が異なります。FileMaker以外のデータベースでは、値として記述するのはSQLステートメントです。一方、FileMakerで値として記述するのは、データベース内にあるスクリプト名です。この指定により、例えば、データベースに新規レコードを作成したときに、作成直後にあるSQLステートメントを実行したり、FileMakerの場合はスクリプトを実行させるということができます。どちらかといえば、FileMakerでは複雑なロジックを組み込みたいような場合によく利用されます。一方、SQLデータベースの場合には原則として一定の文字列のステートメントになるため、あまり複雑な処理は作りにくい、というのが実情です。

 scriptキーは配列を値としてもちます。その配列の各要素は、表4-7-1のキーを持つ連想配列です。

キー取りうる値意味
db-operationloadデータを読み出すときにスクリプトを実行
updateレコードを更新するときにスクリプトを実行
create新規レコードを作成するときにスクリプトを実行
deleteレコードを削除ときにスクリプトを実行
situationpreデータベース処理の前にスクリプトを実行
presortデータベース処理の前でソート後にスクリプトを実行(FileMakerのみ)
postデータベース処理の後にスクリプトを実行
definitionSQLスクリプト、あるいはFileMakerのスクリプト名
表4-7-1 scriptキーの値で利用する連想配列のキー

 リスト4-7-1は、定義ファイルへの記述で、scriptキーを指定した例です。もちろん、定義ファイルエディターでも指定できます。

 ここでは、2つの配列がscriptキーの値に含まれています。最初の配列は、このテーブルmessageに対して新たなレコードを作成したとき、作成後に「INSERT log SET msg='new record'」というSQLコマンドを発行します。イメージ的にはデータベース操作のログをどこかのテーブルに書き込んでいるような感じで動くと考えてください。さらに2つ目の配列は、messageテーブルに対するクエリー処理の前に、「set names 'utf8'」というコマンドを実施します。

 通常、この種のコマンドはMySQLを正しくセットアップすると不要というのが現在の状況と思われます。しかし、レンタルサーバーなどのように自分で設定ファイルを書き換えられないような場合で文字化けするときに、この設定を行って回避できた場合もあります。もちろん、その場合はクエリー時だけでなく、必要なデータベース処理すべてに対して、この記述がコンテキストごとに必要となります。

リスト4-7-1 MySQLデータベースでのscriptキーの指定例
array(
    'name' => 'message',
    'key' => 'message_id',
    'script' => array(
        array(
            'db-operation' => 'new', 
            'situation' => 'post', 
            'definition' => "INSERT log SET msg='new record' "
        ),
        array(
            'db-operation' => 'load', 
            'situation' => 'pre', 
            'definition' => "set names 'utf8' "
        ),
    ),
),

 FileMakerでの指定例をリスト4-7-2に指定します。FileMakerには、message_layoutというレイアウトが存在し、さらに、「メール送信」というスクリプトを用意しているという前提です。このコンテキスト、messageに対して、例えばPost Onlyモードで新たなレコードを作成した場合、レコードを作成後に、「メール送信」スクリプトが呼び出されます。スクリプトが呼び出されたとき、それはFileMaker Server上で無人の操作が行われたとみなされます。ここで、レイアウトはmessage_layoutレイアウトに切り替わっており、新たに作成されたレコードがカレントレコードになっています。つまり、Post Onlyモードで入力したデータがレイアウト上で参照できる状態になっているわけです。

 ここで、「メール送信」スクリプトが実行されると、ページ上で入力されたメールアドレス宛に、メールが送信されるというわけです。

 例えば、入力を受け付けた旨のメッセージを送信するといったソリューションを組み立てることができます。なお、メール送信自体はINTER-Mediatorで実装する方法もありますが、FileMakerデータベース側のスクリプトを使った方が手早く実装できます。また、FileMakerのスクリプトはかなり自由度が高いので、さまざまなデータ処理を記述することも考えられます。

リスト4-7-2 FileMakerでのscriptキーの指定例
array(
    'name' => 'message',
    'table' => 'message_layout',
    'key' => '-recid',
    'script' => array(
        array(
            'db-operation' => 'new', 
            'situation' => 'post', 
            'definition' => "メール送信"
        ),
    ),
),

 なお、FileMakerのスクリプト処理を実行すると、選択されているレコードや、選択されているレイアウトを、スクリプトステップで違うものにすることもできてしまいます。もちろん、それはそれで便利なのですが、例えば新規レコード作成時にINTER-Mediatorのメール送信処理も組み込んでいる場合、スクリプトの指定がなければ、作成したレコードがそのままメール送信処理に引き継がれます。しかしながら、スクリプトでレイアウトを移動したり、検索を行うなどして選択されているレコードを変えた状態で終えると、その時の状態のレコードがメール送信処理に引き継がれます。もちろん、意図的にそうしたい場合にはひとつの方法ですが、複雑になるので注意が必要です。原則として、FileMakerのスクリプトでは、レイアウトや現在のレコードを一連の処理が終わったら元に戻すということを意識しておくのが良いと考えられます。

グローバルフィールドへ値を設定する

 FileMakerは、グローバルフィールドとして、あるテーブルにフィールド定義をしたとしても、レコードごとに同一の値を持つフィールドを定義できます。つまり、グローバルフィールドは、値をひとつ持つ、まさにグローバルなオブジェクトとして機能します。一方、テーブルに定義されたものだけに、一部制限はありますが、リレーションシップの手がかりとなるフィールドにも設定されます。

 このグローバルフィールドは、原則としてデータベースへ接続したときには値は未確定です。FileMaker Proで開いたら以前の値が見えるので、保持されるものと思われている向きもありますが、Web経由でのアクセスでは、常にデフォルトデータ値が設定されているわけではありません。そのため、データベースアクセスをWeb経由で行うときに、グローバルフィールドに設定する値を引き渡す必要があります。その値を指定するのがコンテキストのglobalキーの値です。表4-7-2には、連想配列で指定するキーを示しました。このglobalキーは、FileMakerでのみ有効で、MySQL等のデータベースでは設定は一切無視されます。

キー取りうる値意味
db-operationloadデータを読み出すときにスクリプトを実行
updateレコードを更新するときにスクリプトを実行
new新規レコードを作成するときにスクリプトを実行
deleteレコードを削除ときにスクリプトを実行
fieldグローバルフィールド名
value設定する値
表4-7-2 globalキーの値で利用する連想配列のキー

 定義ファイルでのコンテキストの定義での指定例を、リスト4-7-3に示します。この例では、message_layoutレイアウトにクエリーを行うときに、「g基準日」フィールドに2015年1月1日というデータをセットして、データの取り出しを行います。「g基準日」フィールドは、message_layoutフィールドに存在する必要があります。FileMakerのWeb利用は、アクセスするときにデータベースを開いて処理し、そして閉じるということを行います。前述したように、FileMakerの場合は常に、グローバルフィールドの値は何も設定されていないので、処理を開始するごとに設定してやる必要があります。

リスト4-7-3 globalキーの指定例
array(
    'name' => 'message',
    'table' => 'message_layout',
    'key' => '-recid',
    'global' => array(
        array(
            'db-operation' => 'load', 
            'field' => 'g基準日', 
            'value' => "1/1/2015"
        ),
    ),
),

 なお、ここでのvalueキーの値は一定値です。しかしながら、現実的には一定値ではなく、「本日の日付」などの動的な値を設定したいことが多いと思われます。INTER-Mediatorでは、検索条件のようなJavaScript側でのサポートをグローバルフィールドについては行っておらず、JavaScriptと、サーバーサイドでのPHPによるプログラムの拡張で対処できるようになっています。これについては、『[利用例] FileMaker Serverで動的にグローバルフィールドを指定する』で説明します。

このセクションのまとめ

 このセクションでは、定義ファイルに記述できるscriptとglobalキーについて説明をしました。SQLデータベースに関しては、scriptキーにより、データ処理の前後に指定したSQLステートメントの実行ができます。FileMakerについてはscritキーによってデータ処理前後にスクリプトを実行したり、globalキーによりグローバルフィールドへの設定を行うことができます。

4-8操作を記録する機能とカスタマイズ

 データの変更や削除等の記録をしたい場合はよくあり、通常は、実際に行いたいデータ処理の後などに、別のテーブルに必要なデータを残すなどで対処します。INTER-Mediatorはこの処理も自動化しました。規定されたテーブルを用意して設定を行うことで、クライアントからINTER-Mediatorサーバに対して行った処理が、原則として全て記録されます。

操作ログを利用する

 通常、サーバではさまざまなログが取られています。データベースエンジンのログ、Apacheのアクセスログやエラーログ、PHPでApacheと別にログを作成することもできます。また、オペレーティングシステムのシステムログなどさまざまなものがあります。これらのログから必要な情報を得ることも可能ですが、一般にアプリケーションで必要になるログはアプリケーション内部での操作や処理に対応したログです。それをいつ、誰が、どのようにやって、結果はどうだったのかということを知りたいわけです。そのためのログとなると、データベースやWebサーバのログとはちょっと粒度や対象が違うでしょう。そこで、INTER-Mediatorの仕組みに連動するログ機能をつけました。INTER-Mediatorはクライアントとサーバ間のやり取りは完全に自動的に展開しますが、基本的にはデータ処理の基本であるCRUDのそれぞれの場面で通信が発生します。それをキャッチすれば、CRUDの各場面をログとして記録できます。実際には、認証が絡んだり、ファイルのやり取りなどもあり、もう少し細かいやり取りを記録することになります。そして、さまざまなオプションで、アプリケーションに必要なログ作成に持ち込みます。ここで作るログのテーブルにはユーザは自由に読み書きができないので、監査目的のログとしても利用可能です。

 ログを保存するテーブル名は、operationlogという固定の名前にします。そして、表4-8-1に示すフィールドは必須です。後で説明しますが、さらに特定の値をフィールド値として残すこともできるので、その場合はさらにフィールドを追加します。operationlogテーブルを作成するためのCREATEステートメントは、INTER-Mediatorのdist-docsディレクトリにあるサンプルのスキーマ(sample_*.sqlファイル)にあるので、それを流用すれば良いでしょう。

フィールド名型と指定記録されるもの
idINT AUTO_INCREMENT PRIMARY KEY連番の主キー値(自動入力)
dtTIMESTAMP DEFAULT CURRENT_TIMESTAMPレコード作成日時(自動入力)
userVARCHAR(48)ログインユーザ名(authuserのusernameフィールドの値)
client_id_inVARCHAR(48)認証で使用するクライアントID(送信前)
client_id_outVARCHAR(48)認証で使用するクライアントID(送信後)
require_authBIT(1)ログインパネルによる認証作業が必要と判断したらTRUE
set_authBIT(1)認証を行なっている場合にTRUE
client_ipVARCHAR(60)クライアント側のIPアドレス
pathVARCHAR(256)サーバリクエストのパス(定義ファイルを特定できる)
accessVARCHAR(20)サーバリクエストの種類(readやupdateなど)
contextVARCHAR(50)対象のコンテキスト名
get_dataTEXT$_GETグローバルで得られる値
post_dataTEXT$_POSTグローバルで得られる値
resultTEXTクライアントに返すデータ
errorTEXT記録されているエラー
表4-8-1 operationlogテーブルに必要なフィールド

 ログ機能をアクティブにするなどのさまざまな設定は、params.phpファイルで行います。以下のような変数が定義されています。他の設定と同様、レポジトリにあるparams.phpファイルには、一部コメント等になっていますが、変数の定義テキストがあるので、その部分を適当に修正すれば良いでしょう。

変数既定値意味
$accessLogLevelfalsefalseはログ処理なし、1ならログ記録するがデータは含まれない、2ならログ記録をしてデータも記録する
$dbClassLognullログ処理に使うデータベースクラスで、通常は'PDO'にする。省略するとログ記録はできない
$dbDSNLognullログ処理に使うPDOのDSN。省略するとログ記録はできない
$dbUserLognullログ処理でデータベースに接続するユーザ。省略するとログ記録はできない
$dbPasswordLognullログ処理でデータベースに接続するユーザのパスワード。省略するとログ記録はできない
$recordingContextsnullnullだと全てのコンテキストが記録対象。記録するコンテキストを限定したい場合は、記録するコンテキストの文字列をこの変数に配列で指定する
$recordingOperationsnullnullだと全ての操作(accessフィールドに入る値)を記録する。記録する操作を限定したい場合は、それらの操作を示す文字列の配列を指定する
$dontRecordThemefalsetrueにすると、テーマに関わるCSS要素の取り出し等の記録を行わない
$dontRecordChallengefalsetrueにすると、認証におけるチャレンジ送信の処理の記録を行わない
$dontRecordDownloadfalsetrueにすると、ファイルのダウンロード処理の記録を行わない
$dontRecordDownloadNoGetfalsetrueにすると、GETメソッドを使っていないファイルのダウンロード処理の記録を行わない
$accessLogExtensionClassnullログ処理を拡張する場合のクラス名
表4-8-2 ログ作成におけるparams.phpファイルでの設定

 まず、ログ記録をアクティベートするために、$accessLogLevel変数をtrueにします。そして、データベース接続に関わるPDO運用に必要な4つの変数に適切な値を入れます。もし、アプリケーションで使っているデータベース上にoperationlogテーブルを確保しているのなら、その値をそのまま使えば良いので、リスト4-8-1のような結果になると思われます。この設定が一般的なパターンです。なお、ログだけ別のデータベースにも保存できますが、対応するエンジンはINTER-Mediatorがサポートするもののみです。

リスト4-8-1 params.phpでの一般的な必須の変数への設定
$accessLogLevel = true; // ログ機能をアクティブにする
$dbClassLog = $dbClass; // 以下、PDOでのデータベース設定
$dbDSNLog = $dbDSN;
$dbUserLog = $dbUser;
$dbPasswordLog = $dbPassword;

 これら以外の設定は、記録する内容を絞り込むための変数と、拡張クラス名を指定するものです。拡張クラスを利用する方法はこのセクションの後の方で説明します。なお、コンテキストの制限を指定する$recordingContextsと、それ以外の設定で分けて考える必要があります。なぜなら、$recordingContexts以外の判定を行なった後に、$recordingContextsによる判定を行うからです。$recordingOperationsに含まれていないキーワードの処理は、$recordingContextsの内容如何に関わらず、記録されないことになります。$recordingOperationsに指定可能な操作のキーワード(すなわち、operationlogテーブルのaccessフィールドに入力される値)は表4-8-3に示します。

キーワード動作
readデータベースからの読み出し
createレコードの新規作成
updateフィールドの更新
deleteレコードの削除
describeFileMaker Serverのみで利用、フィールド一覧を得る
copyレコードの複製を作る
replaceCSVファイル等の読み込みで主キーが一致すれば置き換える
challenge認証のためのチャレンジデータの取得
changepasswordパスワードの変更
credential認証を実際に行う(storingキーの値がcredentialの場合)
themeテーマのCSSや画像の取得
download定義ファイルアクセスによるフレームワークのダウンロード、メディアアクセスによるダウンロード
uploadfileファイルのアップロード
unregisterクライアント間同期を行なっている時、ページを閉じるときに登録を削除するための情報
maintenanceスキーマ自動生成を行なっているときにデータベース定義ステートメントを生成する
表4-8-3 accessフィールドに設定される値

ログを参照するビューア

 INTER-Mediatorのレポジトリには、ログビューアのアプリケーションが入っています。/samples/Log_Supportにあります。単に参照するだけなので、実際のアプリケーションではこれを元に必要な情報だけを見えるようにするなどの対処をすれば良いでしょう。

図4-8-1 定義ファイルエディターでのコンテキスト定義内の認証関連設定

 例えば、updateやreadの処理で実際のデータのやり取りがpost_dataやresultフィールドに入っています。このフィールドは文字列であり、連想配列を文字列化して入力してあるだけです。もちろん、ここを検索して何かするということも可能ではありますが、特定のフィールドでどんな処理が行われたのかをログで確認する場合は、そのフィールドの値をカスタムクラスで別のフィールドで取り出す方が、より効率的に処理ができるでしょうし、場合によってはリレーションシップを設定することもできます。

 なお、このログビューアによるデータベース処理はログに記録されてしまいます。記録されて困る場合は$recordingContexts変数に、記録したいコンテキスト名を配列で記述するようにしてください。

ロギングの結果をカスタマイズする

 ログのテーブルoperationlogに書き込む直前に処理を組み込むことができます。具体的には、必須のフィールド以外に新たにフィールドを設けて、そのフィールドにログ情報の一部を書き込むということができるようになっています。その処理を行うクラスをparams.phpの$accessLogExtensionClassに代入しておく必要があります。

 リスト4-8-2は拡張クラスの例です。この場合、params.phpでは$accessLogExtensionClass = "LoggingExt" と記述し、このクラスのファイルがコンテキスト定義をしている定義ファイルと同じディレクトリにあることを前提とします。その上で、OperationLogExtensionというクラスを継承してクラスを定義し、ここに示す2つのメソッドを実装します。

リスト4-8-2 ログ処理の拡張クラスの記述
class LoggingExt extends INTERMediator\\DB\\Support\\OperationLogExtension
{
  public function extendingFields(): array
  {
    return ['condition0','field0','field1']; // 追加するフィールドの配列
  }

  public function valueForField(string $field): string // フィールドに対応する値を返す
  {
    if(strpos($field, 'condition') === 0) {
      return isset($_POST['condition0value']) ? $_POST['condition0value'] : NULL;
    } else if(strpos($field, 'field_') === 0) {
      $n = substr($field, 6);
      return isset($_POST['value_'.$n]) ? $_POST['value_'.$n] : NULL;
    }
  }
}

 実装するメソッドは、追加されるフィールド名の配列を返すextendingFieldsと、引数のフィールド名に対する値を返すvalueForFieldの2つです。ログ自体は、INTER-Mediatorサーバを呼び出す最後に実施され、その時のさまざまな値をもとに、2つのメソッドを実装します。リスト4-8-2のクラスは、フィールドの修正において、主キー値、フィールド名、修正後の値を記録するためのものです。accessフィールドがupdateのものに限定して考えてください。

 ここで、実際にフィールドの更新をした時のoperationlogに残るレコードのpost_data、result、errorフィールドをまずはチェックします。一例としてこのようになります。エラーなく処理された場合、errorフィールドは空になります。

リスト4-8-3 フィールド更新時のPOSTデータ
updateの時のpost_dataフィールド
[access => update,name => person,condition0field => id,condition0operator => =,condition0value => 5,field_0 => mail,value_0 => ***,notifyid => defc55a855ee9747eb65216619fea693930b432b05407c3be66f318ca29208fd,tzoffset => -540]

updateの時のresultフィールド:
[dbresult => Query result includes 1 records.,getRequireAuthorization => ,requireAuth => ]

 post_dataフィールドは、実際のINTER-Mediatorの通信を記録しています。本来は、この通信プロトコルを知らなくてもINTER-Mediatorのシステムは組めますが、ログ処理についてはどうなっているかをある程度掴まないと処理は記述できないかもしれません。access=updateの場合、コンテキストはnameキーで示されます。そのコンテキスト定義に主キーフィールドがkeyキーで記載されているので内部的にはその方法で主キーフィールドを得ることができますが、condtion0*の3つのフィールドで、id = 5という主キーフィールドの値が5のレコードを指定していることになります。記録としては、コンテキストpersonにおいて、condition0valueキーの値「5」を覚えておけば、どのレコードの修正だったのかは後からでもわかることになります。

 そして、変更しようとしたフィールドはfield_0キー、変更後のデータはvalue_0でわかります。update処理の場合には1回の通信で1フィールドしか変更しないので、他のキーが登場することはあり得ません。ということで、これらの3つのキーの値を記録したいところです。

 そして、リスト4-8-2のクラス定義に戻ります。ここで、accessがupdateに対して、condition0、field0、field1を用意し、それぞれ主キー値、フィールド名、設定する値を記録するものとします。extendingFieldsメソッドはそれらの文字列を配列として返すだけなので簡単ですが、もちろん、operationlogテーブルにこれらのフィールドを適切な型を伴って定義できている必要があります。

 valueForFieldで、実際のデータをどう取り出せばいいのかですが、post_dataやget_dataにあるものは、PHPのグローバル$_POSTや$_GETを利用すれば取り出すことはできます。なお、それ以上の処理までは考えていませんので、今後拡張のリクエストがあれば、それに応じてクラスを作りやすくあるいは自動で取得できるような方法を考えます。

このセクションのまとめ

 INTER-Mediator上のサーバ呼び出しが発生する操作をした後に、その結果をログとして残す機能が実装されています。残すデータを絞り込んだり、あるいはデータ処理をした結果を保存する拡張クラスの定義もできます。

4-9データベーススキーマの自動生成

 本書籍そしてシステム開発は、データベースのスキーマ設計がなされて、スキーマ定義を適用してテーブルなどが使える状態になっていることを前提にして説明していますが、INTER-Mediatorは逆の仕組みも持っています。この項目を配置する適当な章がないので、ここで説明をします。逆の仕組みは、言い換えれば、INTER-Mediatorのアプリケーションからスキーマの定義ができることに他なりませんが、実用的には「フィールドを追加」したような場合にデータベース管理のコマンドやページを利用しなくても、データベースの適切なテーブルにフィールドを自動追加できるというあたりが実用的な利用方法となると考えられます。

スキーマの自動生成の動作

 スキーマの自動生成機能は、定義ファイルやページファイルの解析結果から、存在しないと思われるデータベース上のオブジェクトを生成するという昨日になっています。しかしながら、推定可能なものは、データベース、テーブル、フィールドであり、例えばビューがないということは推定できません。したがって、通常通りアプリケーションを開発した結果から、データベースを消してスキーマの自動生成をさせても、必ずしも100%稼動する状況にはならないことは理解してください。

 また、MySQLでの動作を想定しています。PostgreSQLについては、実装はしていますが、若干中途半端な状態であることはお許しください。SQLiteについてはデータベース定義言語があまり充実していないので、動作は無理です。FileMaker Serverについても対応はしていません。結果的にMySQLのみの対応に近いですが、それでも、動作環境ごとに考慮しないといけないことはいくつありますので、それを踏まえてここの説明を読んでください。

 では、実際にスキーマの自動生成を行ってみます。ここでは、すでにサンプルで存在するアプリケーションを利用して、データベースの生成から自動処理を進めてみます。まず、params.phpを以下のように編集します。

リスト4-9-1 スキーマ自動生成を行うときに必要なparms.phpでの変数定義
$dbUser = 'root'; // 既存の変数だが管理者ユーザにする
$dbPassword = ''; // $dbUserのパスワード
$dbDSN = 'mysql:host=127.0.0.1;dbname=test_db2;charset=utf8mb4';

$activateGenerator = true; // スキーマ自動生成モードに切り替える
$generatorUser = $dbUser; // 生成で使うユーザ
$generatorPassword = $dbPassword; // 生成で使うパスワード

 $dbUser、$dbPassword、$dbDSNについては、まず、スキーマ変更ができるユーザを指定します。ここでは、macOSでHomebrewを使っていると仮定すると、ユーザはroot、パスワードはなしとなっているので、リスト4-9-1のように指定をします。そして、$dbDNSの文字列の中に、存在しないデータベース(ここでは「test_db2」)記述したとします。もちろん、MySQLは稼動して使える状態になっていることが前提です。なお、スキーマ生成を行うアカウントは$generatorUserと$generatorPasswordという別の変数に定義できるようにしていますが、スキーマ生成のセッションは権限が高いアカウントを使う方が無難と思われるので、rootアカウントを本来の変数に入力して流用しています。$activateGenerator変数により、INTER-Mediatorは解析結果をもとにスキーマ定義を行うようになります。

 この状態で、アプリケーションをそのまま稼動します。つまり、ページを開きます。このとき、コンソールを開いて見えるようにしておきましょう。すると、図4-9-1のように、ダイアログボックスが表示され、メッセージにより、test_db2データベースが作られ、加えてこのデータベースを利用するためのユーザが定義されたことがわかります。

図4-9-1 スキーマ定義モードで最初にページを開いたとき

 データベースは指定したものが作られているので、以後はtest_db2は利用できる状態です。また、アプリケーションから識別されたテーブルだけでなく、INTER-Mediatorの動作に必要なテーブル(registeredcontext registeredpks authuser authgropu authcor issuedhash operationlog)についても定義します。データベースのユーザが定義はされますが、それを利用するためには、params.phpの書き換えが必要です。ダイアログボックスにある情報をコピーできればいいのですが、できない場合はブラウザのコンソールにある情報からコピーできます。Chromeでは赤字になって表示されている箇所から、ユーザ定義部分をどこかにコピーして残しておき、後からそのユーザとパスワードに変更します。

 ダイアログボックスでOKボタンをクリックすると、引き続いてテーブル等の定義コマンドを自動生成して実行します。実行後にダイアログボックスが出ますが、こちらはCREATE TABLEなどのステートメントが実行されたことが記載されます。こちらもコンソールに結果を表示しますが、データベースのダンプをすれば参照はできるので、ユーザとパスワードのように「必ず取り出しておく」という必要はないとも言えます。

図4-9-2 引き続いてデータベース内のオブジェクトを生成する

 このように、生成過程では2回のダイアログボックス表示が行われます。その後は、リスト4-9-2のように、params.phpを修正しておきます。$activateGeneratorをfalseにして通常モードに切り替え、アカウントは途中で生成したものに切り替えます。これで、普通にアプリケーションが使えるはずです。

リスト4-9-2 スキーマ自動生成後のparams.phpの変更
$dbUser = 'webuser'; // 生成したユーザのユーザ名に置き換える
$dbPassword = 'xxxxNNNNNDJDJDJDJDJJJJ'; // 生成したパスワードに置き換える
$dbDSN = 'mysql:host=127.0.0.1;dbname=test_db2;charset=utf8mb4'; // そのまま

$activateGenerator = false; // スキーマ自動生成モードを無効にする
$generatorUser = $dbUser; // (そのままで問題ない)
$generatorPassword = $dbPassword; // (そのままで問題ない)

 上記が基本的な動作ですが、「動く状態のアプリケーション」をデータベースがない状態で作り上げられる人はいないと思います。ともかく仕組み上はこのようなデータベースをスクラッチから作る仕組みは必要なのですが、実用的には次に説明する部分的なデータベース変更への追随ができる部分を利用することが一般的でしょう。

アプリケーション途中でのフィールド追加

 あるアプリケーションで、新たに記録する項目が増えたとします。そのとき、通常はデータベースにコマンドを投入するなどして、フィールドを増やし、そのフィールドを利用する要素をページファイルに記述することになります。しかしながらスキーマ自動生成を利用すると、ページファイルに記述することで、データベースにフィールドを作ることができます。

 手順としては、まず、ページファイルに要素を追加します。例えば、「<input type="text" data-im="person@subtitle">」のように追記します。ここでは、既存のコンテキストpersonを利用しています。そして、params.phpで、$activateGeneratorをtrue、$generatorUserを'root'、$generatorPasswordを''に設定します。そして、ページをもう一度表示すると、図4-9-3のように足りないフィールドを追加したことが表示されます。そして、params.phpで$activateGeneratorをfalseにすると、フィールドが追加して、稼動できる状態になります。

 

図4-9-3 スキーマ自動生成により足りないフィールドが追加された

 ここでは存在しないフィールドの作成を行いましたが、存在しないテーブルの作成も可能です。なお、型が勝手にTEXTになっているという点がお気づきと思いますが、型については既定の型を指定して動作するようになっています。それらは続くオプションの部分で説明を行います。

スキーマ自動生成のオプション指定

 スキーマ自動生成の動作については、params.phpの以下の変数で定義可能です。ここで示されているキーに対する値は、全て既定値でもあります。

リスト4-9-3 params.phpでのオプション設定
$generatorOptions = [
  'default-type' => "TEXT", // 既定のフィールドタイプ
  'pk-type' => 'INT NOT NULL AUTO_INCREMENT PRIMARY KEY', // 主キーの型等
  'fk-type' => 'INT', // 外部キーの型等
  'datetime-suffix' => '_dt', // DATETIME型になるフィールド名の末尾
  'date-suffix' => '_date', // DATE型になるフィールド名の末尾
  'time-suffix' => '_time', // TIME型になるフィールド名の末尾
  'int-suffix' => '_int', // INT型になるフィールド名の末尾
  'double-suffix' => '_double', // DOUBLE型になるフィールド名の末尾
  'text-suffix' => '_text', // TEXT型になるフィールド名の末尾
  'datetime-prefix' => 'dt_', // DATETIME型になるフィールド名の先頭
  'date-prefix' => 'date_', // DATE型になるフィールド名の先頭
  'time-prefix' => 'time_', // TIME型になるフィールド名の先頭
  'int-prefix' => 'int_', // INT型になるフィールド名の先頭
  'double-prefix' => 'double_', // DOUBLE型になるフィールド名の先頭
  'text-prefix' => 'text_', // TEXT型になるフィールド名の先頭
  'dummy-table' => 'dummy', // ダミーテーブル名(テーブル生成を無視する)
];

 default-typeキーは、フィールドの型を示しますが、他のオプションに当てはまらない場合に、default-typeの型になります。*-suffix、*-prefixキーのそれぞれの値は、フィールド名の先頭あるいは末尾がこの名前であれば、値に指定した型になるということです。例えば、price_intフィールドはINT型として定義されますが、priceフィールドは先頭や末尾の設定に合致するものがないので、default-typeのTEXT型になるということです。

 解析時には、コンテキスト定義のkeyやrelationを参照して、主キーか外部キーかを判定しています。主キーの場合の型はpk-type、外部キーの場合の型はfk-typeキーの値になります。なお、外部キーについては対応する主キー制約までの記述はできないので、必要なら後からコマンドやツール等で対処をしてください。

 dummy-tableキーは、コンテキスト定義等にこの名前があれば、スキーマ生成では無視します。コンテキストが読み出ししかしない場合は、安全のためにtableキーを「存在しない」と想定しているdummy等に設定して、書き込みアクセスがあってもエラー終了するようにするのが基本ですが、そのためのテーブルを生成してしまっては意味がないので、そうした文字通り「ダミー」の記述の識別もできるようにしてあります。

このセクションのまとめ

 アプリケーションからデータベースのスキーマを自動生成する機能が搭載されています。フルに生成することも可能ですが、実用的な使い方は、フィールドを増やすような作業をINTER-Mediator側の操作だけで可能になることでしょう。ただし、データベースのアクセス権に関する知識は必要ではあります。また、生成結果を自力で修正しないといけない場合もあると思われますので、万能ではないことは意識しておく必要があります。