Next Previous Contents

3. カスタマイズ

ここまでで以下の流れを実装できました。

入力値のチェック形式は、$TableStructに詰め込んだ連想配列の


"dcf" => _DCF_NOT_NULL | _DCF_NUMBER,

この要素で指示できます。組み合わせられるチェック形式は
_DCF_NOT_NULL, _DCF_NUMBER, _DCF_ZENKAKU, _DCF_DATE, _DCF_EISUU, 
_DCF_KANA, _DCF_MAIL, _DCF_UNIQUE, _DCF_CHK_SQL_INJECTION,
_DCF_INTEGER, _DCF_URL
などです。しかし、現在チェック処理が実装されているのは以下の形式のみです。 上記以外のチェック形式を組み合わせた場合、PHPスクリプト エラーが発生するので注意して下さい。
上記「未サポート」チェック形式を実装したり、既存のチェック形式の 内部処理を変更したい。或はチェック方法の中身を覗いてみたい場合は 「入力値チェックのカスタマイズ 」を参照して下さい。

脇道にそれましたが、とにかくここまでで何とか、動かすことはできる ようになりました。
しかし「動く」だけです。
「グループ」部分は本来、sample_subテーブルのgidですので、 できればそこからグループ名を文字列で引っ張って来て表示して欲しいところ です。INSERT時にはselectフォームでプルダウンで選択できて欲 しいです。
「管理ID」も、INSERT時にいちいち重複しないような値を覚えておくのは 苦痛です。できればデフォルトで +1 されたIDを入力済みにしておいて くれると楽です。

いろいろと出力のカスタマイズがしたくなりますし、する必要があります。
フォームを利用したりすると、やはり見栄えにも凝りたくなります。
というわけで、見栄えをカスタマイズして行くことにしましょう。

3.1 トラッピングハンドラの調整

言語に拘らず、ある程度完成したフレームワークにはプログラマーがライブラリ 本体には触らずにカスタマイズできる仕掛けが備わっています。
「フック(HOOK)」と呼ばれるときもあれば、「アドバイス(advice)」と 呼ばれるときもあります。
poko2ライブラリでは「トラッピング(Trapping)」或は 「トラッピングハンドラ(Trapping Handler)」と呼びます。

それらはフィールドの値が表示されるタイミングで呼び出され、表示対象の フィールド名と、そのレコードにおける値が渡されてくるクラスメソッドです。
プログラマーはそれを適宜オーバーライドし、フィールド値でswtich し、カスタマイズします。
トラッピングハンドラは大体同じ構造をしています。以下に、実際にCDbManagerで定義されているトラッピングハンドラの宣言を載せます。

// INSERT | UPDATE 時のデータ入力用フォーム出力用
function DataInputFormTrap($array, $data, $mode, &$form)

// LIST | DETAIL | INSERT_CONFIRM | UPDATE_CONFIRM時のデータ整形出力用
function GetFieldHtmlOutput($array, $data, $mode, &$html)

// INSERT | UPDATE 時のhiddenフォーム出力用
function GetHiddenFormOutput($array, $data, $mode, &$form)

// INSERT | UPDATE 用SQLの実行直前の内容調整用
function PreExecuteSqlTrap($array, $data, &$result, $mode)

// INSERT | UPDATE | DELETE 後のアクション発動用
function PostExecuteSqlTrap($mode)

以下に共通して現れる各引数を説明します。

$array

フィールド情報を格納した配列が渡されて来ます。

$array[field]

フィールド名("id"などのテーブルの フィールド文字列)

$array[alias]

フィールド名エイリアス("管理ID"など)

$array[size]

汎用の「サイズ」変数。inputタグのmaxlength 属性などに反映される。

$array[dcf], [dvf]

データチェックフラグ、データ表示フラグ

$data

実際にDBから取り出された対象フィールドの値が渡されます。

&$html, &$form

次の場合にカスタマイズしたHTML(入力用フォーム タグを含む)を格納する参照変数です。

(通常のトラッピングハンドラでは、返値は「カスタマイズしたHTML文字列」 または false です。しかし状況によっては true or false しか返せないものも あります。そうしたとき、カスタマイズしたHTML出力を呼出側に渡すために、 参照渡しが使われます。)

$mode

モードを指示する文字列が渡されて来ます。トラッピングハンドラ は大抵、幾つかの動作モード(list, detail, insert, ...)で使いまわされます。 そのとき、今どの動作モードでトラッピングハンドラに入って来たのかを識別 するための変数が$modeとなります。

&$result

これは少々特殊です。このタイプは参照型で、trueやfalse ばかりでなく文字列や数値、さらには配列までを返すことが有ります。この 使い途を理解するにはpoko2のソースコードを参照する必要があるでしょう。

一般に、ユーザーがカスタマイズすべきトラッピングハンドラは次の二つです。

function GetFieldHtmlOutput($array, $data, $mode, &$html)
function DataInputFormTrap($array, $data, $mode, &$form)

GetFieldHtmlOutput

GetList(), GetDetail()でDBから取得した フィールドの値を表示する際に使われます。$mode変数には以下の文字列が 渡されて来ます。list, detail, insert_confirm, update_confirm 実際にはこれらを区別することはほとんど無いでしょう。経験上、全て同じ処理で 望みの表示形式を実現できます。具体的な実装では$array[field]switch し、適切な表示形式に$dataを整形し、&$htmlに格納し、true を返します。デフォルトで良いならfalseを返します。

DataInputFormTrap

InsertTable(), UpdateTable()等で入力フォーム の出力に使います。$mode変数には"insert"又は "update"が入ります。INSERT時は新規入力なので$data は空です。UPDATE時は現在のDB値が入って来ます。$mode変数で場合分け し、適切な入力フォーム用HTML出力を&$formに格納します。カスタマイズ した場合はtrueを返し、組み込み済みのデフォルトフォーム出力処理に 任せて良いならfalseを返します。

では実際にカスタマイズしてみましょう。
最初にレコード一覧表示で「グループ」をsample_subから引っ張り、 グループ名を表示するようにしてみます。


...
class MyDbManager extends CDbManager {
...
        // GetFieldHtmlOutput()をオーバーライドします。
        function GetFieldHtmlOutput($array, $data, $mode, &$html)
        {
                switch($array[field]) {
                        case "gid":

                                // sample_sub テーブルから、gidが$dataと一致するレコード
                                // のgnameフィールドを取得します。
                                $sql = "select gname from sample_sub where gid = '$data'";

                                // SQL文を実行します。「SqlExec」ではなく「_SqlExec」を
                                // 使います。
                                $result = $this->SqlDriver->_SqlExec($sql);

                                // この分岐は 一致レコードが無かった場合の考慮です。
                                if($result)
                                        $gname = $this->SqlDriver->SqlResult(0, 0, $result);
                                else $gname = "unknown";

                                // SQL結果IDを解放します。
                                $this->SqlDriver->SqlFreeResult($result);

                                // カスタムHTML出力はグループ名になります。
                                $html = $gname;
                                return true;
                        default:
                                return false;
                }
        }
...
};
...

実際にレコード一覧表示でグループ名が表示されるか確認します。
うまく行けば、同様にレコード詳細表示でもグループ名が表示されます。

では続いてINSERT | UPDATE時の入力フォームのカスタマイズを行います。 DataInputFormTrapをオーバーライドします。


...
class MyDbManager extends CDbManager {
...
        function DataInputFormTrap($array, $data, $mode, &$form)
        {
                switch($array[field]) {

                        case "id":
                                // INSERT時、プライマリキーは「レコード数」+1を自動的に出力。
                                if($mode == "insert") $data = $this->SqlMaxId() + 1;

                                $form = "$data".$this->ObjForm->_HiddenForm(
                                                        array($array[field] => $data));
                                return true;

                        case "comment":
                                // コメント入力欄はtextareaフォームにします。
                                if($mode == "insert") $data = "";
                                $form = $this->ObjForm->_TextAreaForm(
                                                        array("rows" => 3, "cols" => 20,
                                                                "name" => $array[field],
                                                                "class" => "pdbm"),
                                                        $data);
                                return true;

                        // グループはプルダウンメニューにします。
                        case "gid":
                                // INSERT時は先頭のアイテムを選択しておきます。
                                if($mode == "insert") $data = 0;

                                $options = array();
                                $default = 0;
                                $sql = "select gid, gname from sample_sub";
                                $result = $this->SqlDriver->_SqlExec($sql);
                                for($i = 0;
                                        $i < $this->SqlDriver->SqlNumRows($result);
                                        $i++)
                                {
                                        $gid = $this->SqlDriver->SqlResult($i, 0, $result);
                                        $gname = $this->SqlDriver->SqlResult($i, 1, $result);
                                        if($gid == $data) $default = $i;
                                        $options[$gname] = $gid;
                                }
                                $this->SqlDriver->SqlFreeResult($result);

                                // <select><options>タグを出力します。
                                $form = $this->ObjForm->_SelectForm(
                                        array("name" => $array[field], "class" => "pdbm"),
                                        $options,
                                        $default);
                                return true;

                        default:
                                return false;
                }
        }
...
};
...

ここまでくればだいぶ形が整って来ました。

トラッピングハンドラを実装する上で重要な注意点が有ります。それは、 SQL文を実行するためのCSqlDriverクラスメソッドです。
必ず「_SqlExec」を使い、結果IDを取得。SqlFreeResult の引数に渡して解放 してください。これをせずに普通の「SqlExec」を使ってしまうと、レコードを 取得している呼び出し元のSQL結果IDを上書きしてしまい、ぐちゃぐちゃになります。 (例えばGetListTrap()中で試してみると良いでしょう。)

poko2のソースコード自体はそれほど大規模ではないので、気になる人はSQL ドライバファイルを読んでみると良いでしょう。デフォルト引数を併用した、関数 設計の一例が実装されています。
今回取り上げた外部テーブルから一覧を取得し、プルダウンメニューとして出力 する処理は、poko2を用いたWebアプリケーションを作る上で非常に使用頻度 が高い処理です。ぜひ中で何をしているのかを理解し、マスターして下さい。
また、poko2の提供する高水準クラス(CPoko, CPoko2)でもこれら出力 形式の整形用トラッピングハンドラだけは必ず実装する必要が有ります。慣れれ ば定型的な、しかしアプリケーション毎に毎回異なる作業です。

以上が基本的な入力フォームなどの機能的な外見のカスタマイズです。続いて、 色やフォントなど非機能的なデザインのカスタマイズを見て行きましょう。

3.2 CSSによるカスタマイズ

純粋に文字色や背景色、入力フォームの見栄えのカスタマイズを行うには、 poko2の場合、CSSを使います。
特にCDbManagerの場合、以下のタグで必ずclassを指定しています。

指定しているクラスは"pdbm"(poko2 db manager)です。
IDではなくクラスで指定するので、CSSを使ってカスタマイズする場合

tag.class {
        ...;
        ...;
        }

形式で指示します。

また、フィールドの「エイリアス」と「値」を表示するためのtdタグでは クラス名をそれぞれ"pdbm_f""pdbm_v" に分けています。
下に今回のサンプルで使用した一例を示します。


...
<head><title>CDbManager Test Tool</title>
<style type="text/css">
<!--

a.pdbm { text-decoration: none }
a.pdbm:link { color: black; background-color: white }
a.pdbm:visited { color: gray; background-color: white }
a.pdbm:active { color: black; background-color: gray }
a.pdbm:hover { color: white; background-color: gray }

table.pdbm {
        border-width: 1px;
        border-style: solid;
        border-color: black;
        background: #ccc;
        }

td.pdbm_f {
        padding: 2px;
        background: #ddd;
        text-align: right;
        }

td.pdbm_v {
        padding: 2px;
        background: white;
        }

input.pdbm {
        margin: 2px;
        padding: 2px;
        color: black; background: white;
        border-left:1px;
        border-right:1px;
        border-top:1px;
        border-bottom:1px solid black;
        }

input.pdbmbtn {
        margin: 1px;
        padding: 2px;
        color: black; background: #e8e8e8;
        border-width: 1px;
        border-style: solid;
        border-color: black;
        }

textarea.pdbm  {
        margin: 2px;
        padding: 2px;
        color: black; background: white;
        border-width: 1px;
        border-style: solid;
        border-color: black;
        }

select.pdbm {
        margin: 2px;
        padding: 2px;
        color: black; background: white;
        border-left:1px;
        border-right:1px;
        border-top:1px;
        border-bottom:1px solid black;
        }

span.pdbm_error {
        margin: 2px;
        color: red;
        border-bottom: 1px dashed red;
        }

strong.pdbm_error {
        color: blue;
        background-color: #dddde7;
        border: 1px solid blue
        }

-->
</style>
</head>
...

アプリケーションによっては、CSSではサポート仕切れないようなカスタマイズ を要求されるかも知れません。
その場合、CDbManagerから派生させるのではなく、CDbManagerに類する クラスをドライバクラスから直接派生させてしまった方が速いかも知れません。
またテンプレートファイルと結びつける場合も、CDbManagerから始める のではなく、ドライバクラスに直接アクセスしてテンプレートと結合させる テストツールを作成し、そこからCDbManagerpoko2フレームワークへ 集約できるクラスにまとめあげた方が速いでしょう。

経験的に、そうした極限までカスタマイズするツールの場合、CDbManager のようにINSERT, UPDATE, DETAILの全ての実装が要求されることはまれです。 例えばレコード一覧表示のみ、という場合もあります。そうした場合CDbManager から始めるのではなく、ドライバクラスであるCSqlDriverから始めた方が 良いです。現実にそのような実装で作成したアプリケーションも存在します。
逆に言えばそもそもCDbManagerから始まるpoko2ユーティリティは、 そうした極端なカスタマイズをしなくても良いような、管理系裏方ツール の作成に適しているということです。

3.3 入力値チェックのカスタマイズ

最後にWebDBアプリケーションを作成する上で重要な「値のチェック」のpoko2 における実装と、カスタマイズ方法のサンプルを示します。
また、「フレームワークの準備」で説明をしなかったCDbManager:: IsDCEMessageBufferingメンバについても本セクションの最後で解説します。

CDbManagerで値チェックが行われるタイミングは

の二箇所です。
チェックの際は、各フィールド情報のdcf要素を1ビットずつ調べ、 _DCF_ビットとヒットしたら、それに対応するデータチェックハンドラを 呼び出します。
データチェックハンドラはフィールド情報と実際の値を受け取り、調べます。 エラーが無ければfalseを返し、エラーがあればメッセージを表示するなりした 後、true を返します。

_DCF_ビットとチェックハンドラの対応関係は、CDbManager::DataCheckMap という配列中に、

CDbManager::DataCheckMap[_DCF_***] = "_DCH_function";
という形式でチェックハンドラのメソッド名として設定されます。
この配列はCDbManagerのコンストラクタで最後に呼び出される InitDataCheckMap()中で設定されます。設定される対応関係は基本的なもの だけなので、もし自分で追加したエラーチェックビットとハンドラを登録したい 場合は、InitDataCheckMap()中の最後に呼び出される InitDataCheckMapTrap() というトラッピングハンドラをオーバーライド し、その中で手動で設定します。

今回はサンプルと言うことで、

という順番にサンプル用データチェックハンドラを実装して行きます。

というわけで、最初に新しいデータチェックビットである_DCF_SAMPLE を追加します。


...
require($include_dir."/kernel/cls_dbmanager.php");

define("_DCF_SAMPLE", _DCF_URL << 1);

$SqlConfig = array(
...

cls_dbmanager.phprequireした直後あたりで早々とdefine します。_DCF_URLは、デフォルトで最も値が大きいビットフラグ (0x1 << 10) です。_DCF_SAMPLEでは_DCF_URLを更にもう 1ビットシフトさせた値にします。

続いて"comment"フィールドの[dcf]に対してこのチェック ビットを設定します。


...
        array("type" => "text",
                "size" => "",
                "field" => "comment",
                "alias" => "コメント",
                "dcf" => _DCF_SAMPLE,
                "dvf" => _DVF_LIST_HIDDEN),
...

何も設定しなかった初期状態では、"dcf" => 0 となっていました。 今回は新しく追加したチェックタイプである_DCF_SAMPLEを設定します。

ではいよいよ_DCF_SAMPLEに対応するチェックハンドラを作成します。
クラスメソッドとして派生先のクラスに実装します。メソッド名は" _DCH_Sample"としましょう。


...
class MyDbManager extends CDbManager {
...
        function _DCH_Sample($structure, $data)
        {
                $this->DataCheckError("サンプルエラー(スルー版)", $structure[alias]);
                return false;
        }
...

今回は「普通でない」処理をさせてみました。
CDbManager::DataCheckError()は本来、第一引数に渡されたエラーメッセージ を、赤文字センタリングで第二引数のフィールドエイリアスと一緒に表示します。
表示フォーマットは次の二つのクラスメンバで二段階で指定します。
CDbManager::dceTemplateField
CDbManager::dceTemplateBody
最初にdceTemplateFieldでフィールドエイリアスの表示
dceTemplateBodyでアライメントやフォントを含めた最終フォーマット
をそれぞれ指定しています。詳しくはcls_dbmanager.phpを参照して下さい。

話を戻しますが、通常のエラーチェックハンドラでCDbManager:: DataCheckError()を使用する場合、大概は「エラーがあったのでメッセージを 表示し、trueを返す」処理が殆どです。
cls_dbmanager.phpで既に組み込まれているエラーチェックハンドラは おおよそ次のようなパターンになっています。


function _DCH_NotNull($structure, $data)
{
        // おもに組み込み is_*** 系や、正規表現でチェックします。
        if($data=="" || ereg("^( | )*$",$data)) {

                // エラーが発生した場合、表示して...
                $this->DataCheckError(
                                "$structure[alias]が書き込まれていません",
                                $structure[alias]
                                );
                // true を返します。
                return true;

        // エラー無しなら false を返します。
        } else return false;
}

つまり、DataCheckError()を使う流れでは大概trueを返すわけです。
今回のサンプルではfalseを返すため、「エラー無し」として扱われます。
逆に言えばこれを一種のフィルタリングとして使うこともできます。
今回は特に入力値の不正チャックを実装するわけではないので、false を返す事によりそのままスルーさせた形式にしてみました。

では最後に、この新しいチェックハンドラを登録してみます。
つまりInsertConfirmUpdateConfir時に呼び出せるようにします。
そのためには、次の二つのクラスメソッドを派生先でオーバーライドする必要が あります。

まずGetDcfEndBitについて説明します。これはデータチェックフラグの検査終了 ビットを返します。(検査開始ビットを返すメソッドとしてGetDcfStartBit() もありますが、こちらをカスタマイズする場面はまず無いでしょう)
フィールド情報の"dcf"は、GetDcfStartBit()の返すビット から、GetDcfEndBit()の返すビットまでの範囲で走査されます。
デフォルトではGetDcfEndBit()_DCF_URLを返すようになっています ので、_DCF_URLを1ビットシフトさせた_DCF_SAMPLEはデフォルトでは フラグ走査範囲外になってしまいます。

というわけで、GetDcfEndBit()を派生先でオーバーライドする必要がある わけです。そしてCDbManager::DataCheckMap配列に チェックハンドラの メソッド名を代入します。

...
class MyDbManager extends CDbManager {
...
        function GetDcfEndBit()
        { 
                return _DCF_SAMPLE;
        }

        function InitDataCheckMapTrap()
        {
                $this->DataCheckMap[_DCF_SAMPLE] = "_DCH_Sample";
        }
...

InitDataCheckMapTrap()に関しては見たままになります。

実行してみると、「コメント」欄にどういうデータが入力されようとかならず センタリングでメッセージが表示されるはずです。
これだけではありませんが、CDbManagerが提供しているトラッピング ハンドラでは意図的に通常とは逆の結果を返すことにより、興味深い動作を させることができてしまったりするものもあります。 いろいろ試してみて下さい。

最後にCDbManager::IsDCEMessageBufferingメンバについて解説します。
このメンバがtrue(1)の場合、DataCheckError()で生成されるエラー メッセージは画面には出力されず、グローバル変数である$DCEMessageBuffer に蓄積されていきます。
false(0)の場合、DataCheckError()が呼び出された時点で直ちに エラーメッセージを画面に出力します。
CDbManagerのデフォルトはtrue(1)になっているので、エラーメッセージ を出力するには明示的にDCEMessageBufferprintするなりする必要が あります。
今回のサンプルでは、明示的な print を省略するためfalse(0)を設定しま した。

実際にデフォルトのtrue(1)が役に立つのはテンプレートと緻密に結合させる 場面です。データチェックエラーでエラーが発生すると、InsertConfirm()UpdateConfirm()falseを返すようになっています。その場合にのみ、 エラー用のテンプレートセクションを$DCEMessageBufferでパースして出力 する、といった用途を想像しています。


global $DCEMessageBuffer;
if(!$ObjDbManager->InsertConfirm())
        print $ObjTemplate->GetBlockSectionParsed(array(
                                "error_body" => $DCEMessageBuffer
                                ));

逆にエラーメッセージの表示タイミングの制御がそれほどシビアに要求されない 場合は、派生クラスで今回のサンプルのように、falseで上書きすることにより エラーメッセージの出力コードを記述する必要がなくなります。

「CSSによるカスタマイズ」ではspanタグとstrongタグでそれぞれ pdbm_errorクラスでスタイルシートを定義しています。 先程は触れませんでしたが、エラーメッセージの見栄えをカスタマイズするために は上記二つのタグで、pdbm_errorクラスのスタイルシートを指定します。
spanタグとstrongタグは、CDbManager::dceTemplateBodyメンバで 次のように使われています。


var $dceTemplateBody = 
"<span class=\"pdbm_error\">Input Error %s : <strong class=\"pdbm_error\">
%s
</strong></span><br>\n";

したがって、もしエラーメッセージの見栄えをカスタマイズしたい場合はサンプル のように

span.pdbm_error {
        ...;
        }

strong.pdbm_error {
        ...;
        }

というように指定します。


Next Previous Contents