ありそうで見つからないのでここしばらく久しぶりに仕事でC#を使っている。勘も戻ってきて、「やっぱ慣れてる言語はよいなー」とか思っていたのだが、ちょっと欲しい機能が見つからなかった。いま実装中の機能で、DataSetにキャッシュしたデータをもとにUPDATE用のクエリを生成するのだが、状況によってすこしだけ異なるクエリを生成し分ける必要にせまられ、簡単なテンプレートエンジンが欲しくなったのだ。するとすでに優秀なライブラリがあるかもしれない(実はあとでNVelocityなんてのがあることに気づいた)が、探して評価している時間もないので割り切った機能で自作をしてみた。仕事で作ったやつなのでネタにするのはちょっと抵抗あるが、仕様検討も含めて1時間かかるかかからないかくらいの規模だし、どうせ誰かが似たようなやつ作ってるだろうしでソースを公開してみることにした。需要あるかはわからんけど。仕様ってほど機能があるわけではないが、標準のString.Formatのインデックスベースのプレースホルダの代わりにIDictionaryのキーやDataColumnのカラム名を使うように考えた。String.prototype.supplant」みたいな記法だ。"Value"というキーを持つIDictionary(HashtableとかNameValueCollectionとか)を与えるって使い方。その他はなんの付加価値もない。条件分岐も繰り返し処理もサブテンプレートの差込もできない。パラメータ名の表記は後ろにコロン+フォーマット指定ができるように展開するため、String.Formatと同様にフォーマット指定できるようにしてみた。仕組みソースみればだいたいわかると思うが、以下のようなプロセスで展開する。{}で囲まれたパラメータ名を抽出順にリストにし、そのインデックス番号でもってテンプレート内のパラメータ名を置換する展開時はパラメータ名のリストを順次走査して与えられたDataRowやIDictionaryから値を抽出してobject[]を作成、先に変換したテンプレートを用いてString.Formatするこんな感じでかなりシンプル。ソースこのくらいの感じ。例によってNYSLってことで。}}で、使い方はこんな感じ。}}改造などいまのとこIDictionaryとDataRowの受け取りをオーバーロードで実装しているため、メソッドシグニチャを除けばまったく同じコードでかなりみっともないが、TypeをキーにしたHashtableに、テンプレート変数名から値を取り出すメソッドをくるんだdelegateを登録するように改造したりすれば他の型(ってちょっと思いつかないが)への対応もむずかしくないかと。NVelocityを先に見つけてりゃこんなんつくらんかったな。06時02分12秒お詫びというか訂正というか訂正しないというか一週間もほったらかした上にいきなりお詫びからです。前々回、前回に扱った「zf2」のIndexController::dumpActionですが、前々回では「$this->getRequest()->getParams()」としていたところを、前回掲載のソースでは「$this->getRequest()->getPost()」とメソッドが変わっていました。どこかで変更した検証コードをそのまま確かめもせずに掲載してしまったようです。フォームのメソッドがPOSTなので機能上はほとんど違いがないのですが、なんの断りもなく違うメソッドになっていたので混乱された方もいらっしゃるかもしれませんのでここでお詫びいたします。申し訳ありませんでした。今回も引き続き「zf2」を流用しますが、あえて「getParams()」に戻す理由もないので、今回も「getPost()」のままにしますのでご了承ください。入力内容を検査し、動作を変えるさて、今回の内容です。前回まででフォームの内容をサーバに送信し、その内容を確認するところまで作りました。表示するといった感じにしてみましょう。この仕様変更に伴い、以下の処理を追加する必要があります。入力値をフォームへ反映させるこれらを順を追って実装していきましょう。入力内容をフォームへ反映させる準備まず、未入力項目があった場合への対応で、入力フォーム(=indexAction+index.phtml)へ、POSTされた内容を反映させる準備をします。いっても特別なことではなく、前回までのdumpActionで行っていたように、getPost()で取得した連想配列をviewへassignするだけです。$this->getRequest()->getPost()」をそのままindexActionの先頭に記述してもいいのですが、どのみち今回の仕様では未入力の有無にかかわらずビューへ入力データを渡す必要があるので、共通の処理としましょう。init()内で行ってしまうのです。実際のフォームフィールドへの反映ですが、これまた単純で、HTMLフォームの値として、ビュースクリプト内でechoするだけになります。ここまでのコードを掲載します。IndexController。}}IndexController.php基本的にdumpActionで行っていた'postData'への割り当てをinitに移しただけですが、'baseUrl'への割り当てでもZend_Requestを使用するため、先にローカル変数「$request」にZend_Requestを割り当てるようにしています。assignでメソッドチェーンを適用しています。postDataへ直接getPost()の値を割り当てているのではなく、array_merge()を使用しています。これは、フォーム送信ではなく、直接index/indexを呼び出した場合への対応で、「name」「mail」「sex」の3つのキーが常にpostDataに割り当てられた状態にするための下処理として行っています。index.phtmlは以下のようになります。</body></html>index.phtmlちょっと見にくい(醜い?)コードになっていますが、変更点は以下のとおりです。フォーム項目「mail」を追加しているPOST先をdumpActionからindexActionに変更postDataから値を取得し、value属性に設定しているまずPOST先の変更ですが、これはここまでの修正の動作確認のための一時変更です。dumpActionに飛ばしてしまうと、せっかくのフォーム項目に値を反映させるコードの動作確認が取れないからです。実際のフォーム項目への値の反映ですが、これは基本的に連想配列の対応する値をvalue属性に出力するだけですが、$this->escape()で出力をエスケープしています。関数がその正体となります。Zend_View::escapeメソッドはZend_View::setEscape()メソッドでのカスタマイズも可能になっていますが、ここでは扱いません。出力のエスケープ」を参照してください。入力値をチェックするここまでのコードで動作させてみてください。「送信」しても画面が一切変わり映えしない、つまらない結果になるでしょう。当初の予定どおり、入力値をチェックしてその結果による振り分け処理を実装してみましょう。チェック処理は以下のような考え方になります。indexActionからのPOST先はチェック処理を行うアクションになるチェックアクション内で、必須項目に対してempty()で空かどうかの判断を行う2つの必須項目のうち、1つでも空であればもう一度indexActionへ飛ばす必須項目に問題がなければ結果表示を行うこれらを行うため、IndexControllerに入力値チェック用のアクションメソッドを追加します。名前はpostActionとでもしましょう。postActionを追加したIndexControllerは以下のようになります。}}postActionを追加したIndexController.php処理の振り分けに前回登場した、Zend_Controller_Action::_forwardを使っている程度で、特に解説の必要はないと思います。postAction内で'errorMessage'にエラーメッセージを割り当てていますので、これを出力するコードをindex.phtmlに実装する必要があります。</body></html>エラーメッセージ出力に対応したindex.phtmlisset()でerrorMessageが割り当てられているかを判断している程度で、これまた特に解説の必要はないかと思います。Zend_Controller_Action::_redirectさて、ここまでで今回の予定の機能はすべて実装できました。前回の予告で「_redirectについても解説」といっていたのですが、うまい使い道が思いつかなかったので、簡単な解説だけにとどめさせていただきます。Zend_Controller_Action::_redirectは、_forwardと同様にZend_Controller_Actionのプロテクトメソッドで、目的のURLへリダイレクトするためのメソッドです。メソッドシグニチャは以下のようになっています。array()])使い方は_forwardに近いのですが、以下のような違いがあります。引数はアクションではなくURLを指定する_forwardと違い、実際に指定URLへのリダイレクトが発生する(ブラウザの表示URLが実際に変更する)_forwardとの違いを手っ取り早く見てみるには、今回のコードのpostActionで、indexActionに_forwardしているところを「_redirect('index/index')」に変更してみてください。init()内でpostDataを割り当て、それをビューに反映させているのに、入力内容が実際には反映されないと思います。これは、_forwardはリダイレクトが発生するわけではない(=同じリクエストを処理している)が、_redirectは実際にリダイレクトが発生するためです。_forwardは同じリクエストを処理しているため、今回のようにPOSTされたデータをinit内でビューに割り当てている限り、_forward前のアクションでも_forward後のアクションでも同じようにデータを扱えますが、_redirectはリダイレクトすることによってリクエストの内容が変化する、ということです。あとがき&次回予告さて、1週間も間があいた割には、大して新しい内容が出現しませんでした(^^;が、これで一通りページを作成してユーザの入力を受け取り処理をするための基礎に触れられたと思います。次回からは、いったんコントローラ関連の処理から離れ、これまたWebアプリケーションでは不可欠ともいえる、データベース関連の機能に移りたいと思います。DBMSはPHP5で標準的に扱えるため環境にあまり左右されない、SQLiteを使う予定です。Zend_Db関連をやるのではなく、1回くらいはSQLiteの使い方を解説するかもしれません(しないかもしれません^^;)。それでは次回を、気長にお待ちください。吊ブックマークレットtumblrのキーボードショートカットが便利になっとる。Chrome編dataスキームの画像をブロックしてみるローカルストレージを確認したり消したりするブックマークレットそうそう、いまさらTwitter始めました。(9)このブログについてJavaScriptとかC#とかPHPとか。