Zend Framework - Zend_Form編

HOME | TOP

■ Zend_Form概要

Zend_Formは、直接フォームをHTMLタグ打ちすることなく、パラメータを設定することでフォーム作成や入力値チェック(バリデータ Validator)を行えるようにするコンポーネント(クラス)です。

つまり、設定するパラメータにはフォーム作成に必要な値と入力値チェックに必要な値があることになります。



設定できるパラメータはこれら以外にも、フォーム作成と関わりの深い見た目を変えるためのデコレータ(Decorator)や入力値チェックを通過した値を加工できるようにするためのフィルター(Filter)があります。



フォームを作成するためにはまず、フォームオブジェクトを作成します。

作成したフォームオブジェクトに各種パラメータ(フォーム作成値・バリデータ値・フィルター値・デコレータ値)を与えることで実際にフォームを作成したり、送られてきた入力値のチェックに利用したりすることができるようになります。

つまりフォーム作成だけにZend_Formを使うくらいなら直接フォームタグを書いた方がいいと思います。フォーム作成以外に、送られてきた入力値のチェックでも使ってこそZend_Formを使う意味が出てくると思います。



▽ フォームの外形

ここで少しだけフォームの外形に触れておきます。



まず『<form> 〜 </form>』の一組のフォーム全体を単にフォームと呼びます。

このフォーム全体の持つパラメータ値は、ちょうど<form>タグの持つ属性("action"や"method"など)と一致します。
そしてフォームの内部にはフォーム要素(formElements)と呼ばれるさまざまな部品(テキストボックスやsubmitボタンなど)が属してることが分かります。



▽ フォーム作成の流れ

[1] フォームを作成するためにはまずフォームオブジェクトを作成します。
これは単にZend_Formクラスからオブジェクト作成する(つまりnew演算子を使う)だけで作ることができます。

$form = new Zend_Form();

これで空っぽの『<form> 〜 </form>』が一組作成されたイメージになります。



[2] フォームオブジェクトが作成できたらこのオブジェクトにフォーム作成に必要なパラメータ(主にフォーム要素作成のためのパラメータで、他にも入力値チェック用のパラメータなど)を与えていくことになります。

各種パラメータを与えたらフォームは完成形です。あとはフォーム作成でも入力値チェックでもできる状態です。

パラメータを与えてフォームを完成させていくやり方はいろんな方法が用意されているので別記(次項: フォーム要素の作成と登録)します。



[3] render()メソッドを使ってレンダリング(フォームの描画)を行います。

echo $form->render();

レンダリング結果を変数で受け取ったりechoで表示させたりできます。



▽ フォーム入力値チェックの流れ

フォーム作成の流れで説明した[2]まででフォームが完成しています。
あとはこれを使って、送られてきたフォーム入力値をチェックすることになります。

チェックしてほしい値をisValid()メソッドに渡すと、設定した期待どおりの値かどうかを真偽値で返してくれます。

ここのチェックを通過すれば次に行いたい処理へ進めることになります。




■ フォーム要素の作成と登録

フォームオブジェクトを作成(new演算子を使う)できたら次はそこにフォーム要素を加えていく(各種パラメータを設定する)作業になります。

それに先駆けて、まず手始めにシンプルなもの(フォーム要素を持たない空っぽのフォーム)を表示させてみることにします。



空っぽのフォームオブジェクトのままフォーム作成してみるスクリプト

実際にレンダリングされた結果のHTMLソース

まだこれだけのタグなので当然画面上は何も無い真っ白けです。

これを見ておいおいおいと、なんだこれはと思われた方もいらっしゃると思いますが、役割り分担がはっきりしている(きっちり分担することでバグの入り込む余地を少なくしている)Zend Framework(のみならずオブジェクト指向)では当然のことながら描画はZend_Viewが担当することになります。

なのでZend_Formを単独で使う場合にはsetView()メソッドに個別にビューオブジェクトを登録する必要がありますが、MVCアプリケーションではビューオブジェクトの登録は自動的に行われるのでこの登録作業は不要です。



▽ 各種パラメータ

フォームを完成させていくためのパラメータは大きく2種類に分けられます。

1つはフォーム全体として持ってる値("action"や"method"など)で、もう1つは個々のフォーム要素の持つ値です。



フォーム全体として持ってる値を登録するのは簡単です。
作成済みのフォームオブジェクトに専用メソッドで渡すか、フォームオブジェクト作成時に渡すことができます。

[1] 専用メソッドを使う方法

$form->setAction('/foo/bar')->getMethod('post');のようにメソッドチェーンでも渡せます。



[2] フォームオブジェクト作成時に渡す方法

フォームオブジェクト作成時にはフォーム要素関連の値も渡すことができるので、この方法を使うとフォームオブジェクト作成と同時に一気にフォームを完成させることができます。

また、階層構造の配列を渡しているだけなのでiniファイルに階層構造を記述してZend_Config_Iniで配列化したものを渡して一気に作成することもできます。

フォーム全体として持ってる値の登録はこれだけです。あとは個々のフォーム要素に持たせる各種パラメータを登録していくことになります。





フォーム要素に持たせるパラメータは、フォーム要素のオブジェクトを作成して登録する方法と、フォームオブジェクト作成時に配列を渡して作成と登録を一度に行う方法とに分けることができます。

[1] 作成して登録する方法

この段階ではフォーム要素<input type="text" name="username">のようなものが作成できる程度のパラメータが渡せたイメージになります。



上記createElement()メソッドを使った作成と似たような方法として、専用クラスを使ってインスタンス化する方法もあります。

こちらの方法だとクラス呼び出しの記述まで増えてしまうのであまり使われない方法でしょう。



[2] 作成と登録を一度に行う方法

この方法では"action"や"method"はもちろんのこと、フォームの持つフォーム要素に関するパラメータをすべて指定した配列を渡します。

このようにインデントして階層構造を分かりやすくした方がよりフォームが明確な感じで見通しがよくなります。



これと似たような手法(フォームオブジェクト作成時に一気にパラメータ渡し)として、iniファイルにパラメータ設定を書いておき、これを配列にセットするやり方があります。



このiniファイル設定値は以下のようにして配列化できるのでそのまま渡せます。

この手法は他でも用いるので覚えておいた方がいい手法です。

直書きで配列を直接渡す方法は、フォーム数が割と少ない小規模アプリケーションでは有効な感じですが、フォーム数が多くなってくると徐々に見通しが悪くなってきます。
そういう意味でもこのiniファイルの手法は覚えておきたいものです。



フォーム要素に設定できるパラメータにはいくつか種類があります。

設定できるパラメータの種類と数が多いので、実際に作成していく中で必要なものだけを覚えるしかないでしょう。

ということでフォーム要素のパラメータ指定はいろいろあるので、有効だと思えるものに絞って書いていきます。



具体例として、よくあるログインフォームで考えていきます。

ログインフォームでは、入力できるフォーム要素として「テキストボックス」と「パスワード」の2つがあります。
あとは送信ボタンがあるくらいでしょう。

ログインフォームのイメージ
    なまえ[             ]
パスワード[             ]
[送信]


まず「なまえ」に使用できる文字種類が決まっているはずです。
加えて少なくとも6文字で最大で20文字長などの仕様もあるはずです。
「パスワード」にも同様のことが言えます。
これら「なまえ」と「パスワード」は共に必須入力になります。
「送信」ボタンは送信の文字を設定するだけで済みそうです。



ではログインフォームから入力されるべき値、期待する値をパラメータ指定にしていきます。
見てくれを変えるデコレータまで書くと記述が増えて混乱するので、まずはデコレータはいじらずにデフォルトを使うとして、あとのパラメータをiniファイルに具現化していきます。

iniファイルでは、[foobarfuga]の形式で記述するとfoobarfugaセクションが出来上がります。
iniファイルを取り込む時にセクション指定できるので複数フォームがあるならセクション分けしていくと分かりやすくなるでしょう。

先頭を"form"としていますが、これは取り込んで配列渡しするときに$config->formで指定できるようにするためのものです。なくても問題ないものですが、内容が分かりやすくなるフレーズがいいでしょう。



では設定したパラメータを見ていきましょう。
これだけ整列していると見ればなんとなく分かると思います。

まず任意で付けた頭の"form"をはずして考えると

elements.{name属性値}.type = フォーム要素のタイプ指定
elements.{name属性値}.options.label = ラベル指定

ということが分かります。
ラベルというのは普通はフォーム要素のすぐ左にある「なまえ」や「パスワード」といった表示文字になります。submitボタンの場合はボタン上の文字はvalueではなくlabelで設定するようです。

デコレータはあとで説明するので、ここまででフォーム作成(タグ描画)に関わるパラメータは設定完了ということになります。次は、

elements.{name属性値}.options.required = true

という行がありますが、これは必須入力であることを意味します。なので空っぽだと入力エラーの判断をすることになります。

あとは、バリデータの指定があります。(前述のrequiredも一応はバリデータです)

elements.{name属性値}.options.validators.alnum.validator = "alnum"
elements.{name属性値}.options.validators.regex.validator = "regex"
elements.{name属性値}.options.validators.regex.options.pattern = "/^[a-z]/i"
elements.{name属性値}.options.validators.strlen.validator = "StringLength"
elements.{name属性値}.options.validators.strlen.options.min = "6"
elements.{name属性値}.options.validators.strlen.options.max = "20"

"alnum"というのは、「アルファベット+ナンバー」なので英数字のみを許可することになります。

それから"regex"がありますが、これは正規表現によるチェックを行います。その内容が"/^[a-z]/i"になっているので、先頭文字が英字であるかチェックすることになります。

あと文字列長が6〜20文字の範囲かどうかもチェックしていることになります。バリデータ指定は以上です。

そしてフィルターの指定があります。

elements.{name属性値}.options.filters.lower.filter = "StringToLower"

これは入力値のチェックを無事に通過したものをgetValue()メソッドで取得する時に施す加工です。「なまえ」として入力された英字は取得段階でフィルターによってすべて小文字変換する指定です。

この方法で取得すればもう英小文字であることが保証されていることになるので、個別にチェックや加工処理をすること無しに次の処理へ進むことができるということになります。




■ デコレータ

今度はデコレータに重点を置いて、フォームのいろいろな見せ方を考えていきます。
設定をより見やすくするために、前項で書いたバリデータ・フィルター指定の部分はそぎ落とします。



この項はほとんどわたしの勘で書いたものです。つまり間違ってる可能性が高いです。
というのも、わたしは技術書や解説本のたぐいは一切読まずにネット情報だけをかき集めて覚えたつもりになって我流の説明をしているからです。
そんな中でもZend_Formのデコレータの解説サイトがなかなか探しきれずにいます。
頼みの公式リファレンスでも見つけられません。
いくら検索しても判で押したような記述のサイトばかりでしかも表示が重いです。なんか泣けてきます。><;



まずはデコレータ指定の基本を押さえておきます。

デコレータは設定しないでもデフォルトで

といったタグが施されて構造化された表示になります。
つまりこの構造化している<dl>, <dt>, <dd>といったタグは、デフォルトのデコレータによって施されているということになります。



上記はフォーム要素が2つある場合ですが、このデフォルトのデコレータ指定はiniファイルに記述するとだいたい次のような感じになります。

; フォーム全体のデコレータ
decorators.elements.decorator = "FormElements"
decorators.dl.decorator = "HtmlTag"
decorators.dl.options.tag = "dl"
decorators.form.decorator = "Form"
; 各フォーム要素のデコレータをまとめて設定
elementDecorators.helper = "ViewHelper"
elementDecorators.dd.decorator = "HtmlTag"
elementDecorators.dd.options.tag = "dd"
elementDecorators.dt.decorator = "Label"
elementDecorators.dt.options.tag = "dt"


代表的なデコレータ
デコレータ名説明
FormElements各フォーム要素の描画結果を結合して返す
ViewHelperビューヘルパを利用してフォーム要素を描画
HtmlTagtagで指定したタグで囲む
Form<form>, </form>タグを描画する
Labelフォーム要素にラベルがあれば描画する。tagを指定できる
Errors入力エラーがあった場合にフォーム上にエラー表示させるためのもの

tag指定できるデコレータは"HtmlTag"と"Label"の2つです。(多分)



デコレート範囲先頭ワード任意の名前空間指定部分
フォーム全体decoratorselements,formなど
自由に付ける
decorator = "デコレータ名"
options.tag = "指定タグ名"
フォーム要素まとめてelementDecorators
フォーム要素を個別にelements.{name値}.options.decorators

先頭ワードでデコレート範囲が決まります。
先頭ワードの次にくっつくのは任意の名前空間で、最後に指定部分がくっついた形です。

なぜか"FormElements"と"ViewHelper"は、decorator = のdecorator部分を省略できる。(自動的に呼ばれてるから?)



では、実際にデコレータを使ってフォームを作ってみましょう。



▽ デコレータを指定しない状態(デフォルト)

[./form.ini]

フォーム作成用スクリプト

フォームのレンダリング結果
 

なんか物足りないけど、デフォルトのデコレータではこんな感じになりました。



レンダリングされたフォームのHTMLソース

やはりフォーム要素が増えるほど<dl>, <dt>, <dd>タグがまとわりつく感じになっております。







▽ デコレータによって付加されているタグをすべて取り除いてみる

先ほどは<dl>, <dt>, <dd>タグがまとわり付いた感じになりましたので、今度は全部取っ払ってみます。

注目すべきはdisableLoadDefaultDecoratorsをtrue指定しているところです。



フォームのレンダリング結果


レンダリングされたフォームのHTMLソース

なんとか希望通りに取り除くことができました。







▽ 全フォーム要素をまとめてデコレートする(その1)

全フォーム要素をまとめて指定するにはelementDecoratorsを使います。



フォームのレンダリング結果

ラベル文字を太字にしてみました。なくはないですかね

ボタンのラベルがボタン上の他にも出現しました。全フォーム要素まとめてラベルを<b>で囲む指定にしたので当然ですね。



レンダリングされたフォームのHTMLソース





▽ 全フォーム要素をまとめてデコレートする(その2)

今度は難しそうなtableタグを施すデコレータに挑戦してみます。



フォームのレンダリング結果

これはイケたんじゃないでしょうか? これはアリですな。これのやり方が載ってるサイトがなかなか見つからなくて結構苦労しました。

ただ、人間の欲望は際限がありません。できることならボタンちゃんだけはtableタグの外に出したい!



レンダリングされたフォームのHTMLソース





▽ フォーム要素を個々にデコレートする

先ほどはボタンまでもがtableタグに含まれてしまいました。なんとか外に出すように頑張ってみます。
ということで、今度はフォーム要素を個別にデコレート設定する必要がありそうです。

とは言っても、tableタグの開始と終了を指定するやり方はどこを探しても見つけられませんでした。
自称めちゃくちゃ検索には自信があるんですが探しきれませんでした。

なので自力でなんとかしましょう。公式リファレンス等には、グループ表示というのができるというのはチラっと載っているのでそれいただきです。

tableタグに含ませたいものだけをグループ化すればよさそう。ということで、displayGroupsでグループ定義し、displayGroupDecoratorsでグループのデコレータとしてtableタグを設定しました。



フォームのレンダリング結果

これです。期待通りのものができました。かなり悩みましたけど。

最初はボタン位置がtableの上に来るようになってしまったので、order指定も加えることになりました。

にしても検索しても見つけられないってことはもしかしてZend_Formってそんなに使われていないのが実状なのかな? 実はZend Frameworkのことそんなに知らないんですよね。。
薄々は思っていたけど、フォーム設定だけで結構冗長というか、、、ね。



レンダリングされたフォームのHTMLソース





▽ 多少見栄えをよくする努力をしてみる

本来ならスタイルシートの指定値はタグ埋め込みではなく、一箇所にくくり出してタグではclassやid指定にしたいところですが、シンプル例ということで。。。



フォームのレンダリング結果

これはキテるね、ハッキリ言って。。バキバキきてる



レンダリングされたフォームのHTMLソース





▽ もっとコンパクトに書いてみる

前回までは、フォーム要素"username"と"password"別々に同様のデコレータ設定を施すやり方をしました。
この方法だと、フォーム要素が増えれば増えるほど記述がかさばるのは目に見えています。

そこで、おのおののフォーム要素をまとめて設定できるelementDecoratorsがあるわけですからこれを使ってコンパクト化してみます。
ただし、例によってsubmitボタンまで同様のデコレートが施されては意味がなくなりますので、一旦elementDecoratorsでデコレートした後でsubmitボタンだけデコレートを無効化する方法を考えます。

公式リファレンスを眺めていたら、removeDecoratorというそれっぽい名前のものがありました。しかし残念なことにiniファイルにどう記述したら有効になるのかわかりませんでした。

そこで考えた方法は、全フォーム要素デコレート後にsubmitボタンだけ"ViewHelper"デコレータを呼び出してそれまでのデコレートをリセットするやり方です。これは正統なやり方なのかはわかりませんが、これでできたので書いておきます。



フォームのレンダリング結果

これでできました。いいんじゃないでしょうか



レンダリングされたフォームのHTMLソース





▽ ついでにもう一つ考えてみました

今度はDescription設定を利用して、テーブルカラム数を1つ増やしてみることに挑戦してみました。
Descriptionにテーブルのカラムが増えるようにするためのタグを加えているので、レンダリング時のエスケープをfalse設定にしています。

強引なやり方ですね。



フォームのレンダリング結果

なまえを入力して

パスワードを入力して

思ったとおりうまくいきました。
これ以上複雑なテーブル構成にするにはdescriptionを配列で設定して、自作デコレータで強引にテーブルのカラム数を増やして当てはめていくとかしないといけないかも知れませんね。



レンダリングされたフォームのHTMLソース


■ Zend_Formのよりよい利用法を考える

フォーム作成だけのためにZend_Formを使うくらいならフォームタグを直打ちで埋め込んだページを用意した方がましだということを前に書きました。
処理速度や労力、見通しのよさを考えるとそう思います。
フォーム作成に加えて、入力された値をチェックできて初めてZend_Formを使う意味が出てくると思います。

ではこのZend_Form、どのような使い方をするのがよりよい利用法なのかを私なりに深くエグってみます。



▽ アプリケーション全体に統一性を持たせる



例えば1つのアプリケーションシステムで画面に統一性を持たせる場合に、上記のような抜け殻のページを1つだけ(または画面の種類数)用意しておき、そこにいろんなフォームを置き換えられるようにしておく。
これで文字コードやスタイルシートが統一されたいろんなフォームを持ったページが作成できる。



抜け殻ページから直接特定のフォームだけを呼んで置き換えるように書いてしまうと、抜け殻ページを用意する意味がなくなるので気をつける。



あとはやはりフォーム設定があちこちに散在してしまうと見通しが悪くなるので、これこそまさにiniファイルを利用するにふさわしいと思います。

例えばアプリケーションのカテゴリーごとにcategory01.ini〜category10.iniのようにファイル分けしておき、各iniファイル内でも[fugaform][hogeform][mogeform]のようにセクション分けして場面に応じて目的のセクションを読み出してフォームを作成するようにすれば、かなりの規模でも見通しよく作れると思います。

[category01.ini]

MVCアクションの例

フォーム作成・登録用のメソッド(ここでは_getformに当たる)を自作して呼び出すだけの形。
これでフォーム設定が一箇所にまとまった感じになりそうです。



ビュースクリプト

あまり大してエグれませんでしたが、まぁこんなところでしょう。

■ デコレータの自作

デコレータを自作するとしたら、これまでの冗長設定を打破するデコレータを自作したい思いはありますね。

と考えてはみたものの、そんなものがあれば始めから作られてたはず。
結局のところ自作したほうがよさそうなデコレータは思いつきません。つまり既存のもので事足りる気がしますね。



作り方は簡単、Zend_Form_Decorator_Abstractクラスを継承してrender()メソッドを定義するだけで作成できます。
この自作デコレータを使う時に、置き場所とデコレータ名を指定すれば使えるようになります。



[application/forms/decorators/Sample.php]

クラス名でデコレータと判るような接頭辞(Deco_)を付けた"Sample"というデコレータを自作してみました。

これを使うとフォームタグその他を無効化して台無しにすることができます。

これを見ればわかると思いますが、デコレータはフォーム作成時に施すフィルターだということが言えますね。



iniファイル設定は以下のような感じになると思います。

[application/forms/configs/foo.ini]

これを使うことはなさそうですね。


■ 入力エラーメッセージの表示

これまで使ってきませんでしたが、入力されてきた値をチェックしたときに

入力エラーがあった場合はまた入力画面を表示させて処理を繰り返すことになると思います。

その再入力画面のフォーム上に、エラーになった入力に対するメッセージを表示させる機能があります。

この機能を担っているのが"Errors"デコレータになります。

入力エラーがあって、フォームを再描画するときにこの"Errors"デコレータを指定してあるフォーム要素があれば、そこにメッセージを添える働きをします。



ただ困ったことにこのエラーメッセージはアメリカ語で表示されます。
そうです、実は日本語は世界共通語じゃなかったんです。



だからアメリカンな気分でアプリケーションを使ってもらうか、作り手が日本語メッセージにすり換えるしかないということになります。



で、すり換えるにはエラーメッセージの原文を持っているZend_Validateに属するバリデータを見ていく必要があります。

例えば"Alnum"バリデータはこんな感じになります。



表示結果
[alnumInvalid] => Invalid type given, value should be float, string, or integer
[notAlnum] => '%value%' contains characters which are non alphabetic and no digits
[alnumStringEmpty] => '%value%' is an empty string


こんな感じで、使用するバリデータの原文を全部取得したらあとはZend_Translateを使って置き換え指定するだけです。

Arrayアダプタを使った簡単な置き換え方法です。

お気づきのとおり、バリデータをたくさん使っていれば結構な作業になりそうです。



わたしは思います。アプリケーションを作る上で一つ大切なことは、エラー内容を詳細に教え過ぎないことだと。

あまり教えてもごちゃごちゃなるだけです。
もしもバリデーションに穴があれば、詳細に教えたことを突いて来られるかも知れません。



だから"Errors"デコレータは、使うとしたら原文のままでいいでしょう。空いた時間でパーティーの準備をしてはどうでしょう。

使わないって手もありますね。


2010(C)Mingw
>