Zend_Form の国際化

Zend_Form の高度な使用法

Zend_Form にはさまざまな機能があり、 その多くは熟練者向けに用意されています。本章では、 それらの機能について例を交えて説明します。

配列記法

関連するフォーム要素について、要素名を配列形式にしてグループ化したいこともあるでしょう。 たとえば、配送先と請求先のふたつの住所を受け取りたい場合、 それぞれに同じ要素を使った上で配列でグループ化すれば、 結果を別々に受け取ることができます。 たとえば次のようなフォームを例に考えてみましょう。

  1. <form>
  2.     <fieldset>
  3.         <legend>配送先</legend>
  4.         <dl>
  5.             <dt><label for="recipient">氏名:</label></dt>
  6.             <dd><input name="recipient" type="text" value="" /></dd>
  7.  
  8.             <dt><label for="address">住所:</label></dt>
  9.             <dd><input name="address" type="text" value="" /></dd>
  10.  
  11.             <dt><label for="municipality">市:</label></dt>
  12.             <dd><input name="municipality" type="text" value="" /></dd>
  13.  
  14.             <dt><label for="province">州:</label></dt>
  15.             <dd><input name="province" type="text" value="" /></dd>
  16.  
  17.             <dt><label for="postal">郵便番号:</label></dt>
  18.             <dd><input name="postal" type="text" value="" /></dd>
  19.         </dl>
  20.     </fieldset>
  21.  
  22.     <fieldset>
  23.         <legend>請求先</legend>
  24.         <dl>
  25.             <dt><label for="payer">氏名:</label></dt>
  26.             <dd><input name="payer" type="text" value="" /></dd>
  27.  
  28.             <dt><label for="address">住所:</label></dt>
  29.             <dd><input name="address" type="text" value="" /></dd>
  30.  
  31.             <dt><label for="municipality">市:</label></dt>
  32.             <dd><input name="municipality" type="text" value="" /></dd>
  33.  
  34.             <dt><label for="province">州:</label></dt>
  35.             <dd><input name="province" type="text" value="" /></dd>
  36.  
  37.             <dt><label for="postal">郵便番号:</label></dt>
  38.             <dd><input name="postal" type="text" value="" /></dd>
  39.         </dl>
  40.     </fieldset>
  41.  
  42.     <dl>
  43.         <dt><label for="terms">I agree to the Terms of Service</label></dt>
  44.         <dd><input name="terms" type="checkbox" value="" /></dd>
  45.  
  46.         <dt></dt>
  47.         <dd><input name="save" type="submit" value="Save" /></dd>
  48.     </dl>
  49. </form>

この例では、請求先住所と配送先住所に同じフィールドを使用しているため、 一方が他方を上書きしてしまいます。 これを解決するには、配列記法を使用します。

  1. <form>
  2.     <fieldset>
  3.         <legend>配送先</legend>
  4.         <dl>
  5.             <dt><label for="shipping-recipient">氏名:</label></dt>
  6.             <dd><input name="shipping[recipient]" id="shipping-recipient"
  7.                 type="text" value="" /></dd>
  8.  
  9.             <dt><label for="shipping-address">住所:</label></dt>
  10.             <dd><input name="shipping[address]" id="shipping-address"
  11.                 type="text" value="" /></dd>
  12.  
  13.             <dt><label for="shipping-municipality">市:</label></dt>
  14.             <dd><input name="shipping[municipality]" id="shipping-municipality"
  15.                 type="text" value="" /></dd>
  16.  
  17.             <dt><label for="shipping-province">州:</label></dt>
  18.             <dd><input name="shipping[province]" id="shipping-province"
  19.                 type="text" value="" /></dd>
  20.  
  21.             <dt><label for="shipping-postal">郵便番号:</label></dt>
  22.             <dd><input name="shipping[postal]" id="shipping-postal"
  23.                 type="text" value="" /></dd>
  24.         </dl>
  25.     </fieldset>
  26.  
  27.     <fieldset>
  28.         <legend>請求先</legend>
  29.         <dl>
  30.             <dt><label for="billing-payer">氏名:</label></dt>
  31.             <dd><input name="billing[payer]" id="billing-payer"
  32.                 type="text" value="" /></dd>
  33.  
  34.             <dt><label for="billing-address">住所:</label></dt>
  35.             <dd><input name="billing[address]" id="billing-address"
  36.                 type="text" value="" /></dd>
  37.  
  38.             <dt><label for="billing-municipality">市:</label></dt>
  39.             <dd><input name="billing[municipality]" id="billing-municipality"
  40.                 type="text" value="" /></dd>
  41.  
  42.             <dt><label for="billing-province">州:</label></dt>
  43.             <dd><input name="billing[province]" id="billing-province"
  44.                 type="text" value="" /></dd>
  45.  
  46.             <dt><label for="billing-postal">郵便番号:</label></dt>
  47.             <dd><input name="billing[postal]" id="billing-postal"
  48.                 type="text" value="" /></dd>
  49.         </dl>
  50.     </fieldset>
  51.  
  52.     <dl>
  53.         <dt><label for="terms">I agree to the Terms of Service</label></dt>
  54.         <dd><input name="terms" type="checkbox" value="" /></dd>
  55.  
  56.         <dt></dt>
  57.         <dd><input name="save" type="submit" value="Save" /></dd>
  58.     </dl>
  59. </form>

上の例では、住所をそれぞれ個別に受け取ることができます。 このフォームを送信すると、受け取り側では 3 つの要素を取得できます。 'save' が送信ボタン、そしてふたつの配列 'shipping' と 'billing' の中にはさまざまなキーとそれに対応する要素が含まれています。

Zend_Form は、この処理を サブフォーム で自動化します。 デフォルトで、サブフォームは、リストしている前の HTML フォームで示されたように、配列を用いて表記法をレンダリングします、 そして ID で完了します。 配列の名前はサブフォーム名からとられ、 配列のキーはサブフォーム内に含まれる要素となります。 サブフォームは、何段階でもネストさせることができます。 その場合も、ネストした配列形式でその構造を表します。 さらに、Zend_Form のさまざまなバリデーション機能は、この配列構造をきちんと処理するようにできています。 サブフォームをどれだけ深くネストさせたとしても、 フォームの検証は正しく行ってくれます。 この機能を使うために特に何かしなければならないということはありません。 この機能はデフォルトで有効になっています。

さらに、条件付きで配列記法を有効にしたり 特定の配列を指定してそこに要素やコレクションを所属させたりといった機能もあります。

  • Zend_Form::setIsArray($flag): このフラグを TRUE にすると、フォーム全体を配列として扱うことができます。 デフォルトでは、 setElementsBelongTo() がコールされていない限りはフォーム名を配列の名前とします。 フォームに名前が設定されていない場合や setElementsBelongTo() が設定されていない場合は、 このフラグは無視されます (要素が属する配列の名前がないからです)。

    フォームが配列として扱われているかどうかを知りたい場合には isArray() アクセサを使用します。

  • Zend_Form::setElementsBelongTo($array): このメソッドを使用すると、フォームの全要素が属する 配列の名前を指定できます。現在設定されている値を調べるには getElementsBelongTo() アクセサを使用します。

さらに、要素レベルでは、特定の要素を特定の配列に属させるために Zend_Form_Element::setBelongsTo() メソッドを使うこともできます。 この値が何者なのか (明示的に設定されたものなのか フォームを経由して暗黙的に設定されたものなのか) を知るには getBelongsTo() アクセサを使用します。

複数ページのフォーム

現在、複数ページのフォームは Zend_Form では公式にはサポートしていません。 しかし、それを実装するための機能の大半はサポートしており、 ほんの少し手を加えるだけでこの機能を実現できます。

複数ページのフォームを作成する鍵となるのが、 サブフォームの活用です。各ページに、ひとつのサブフォームだけを表示させるわけです。 こうすれば、それぞれのサブフォームの内容を各ページで検証し、 かつすべてのサブフォームの入力を終えるまでフォームの処理を行わないということができます。

Example #1 登録フォームの例

例として、登録フォームを考えてみましょう。 まず最初のページでユーザ名とパスワードを入力してもらい、 次のページではユーザのメタデータ (姓、名、住所など)、そして最後のページでは 参加したいメーリングリストを選択するといったものです。

まずはフォームを作成し、 その中でサブフォームをいくつか定義します。

  1. span style="color: #808080; font-style: italic;">// ユーザサブフォーム (ユーザ名とパスワード) を作成します
  2. 'username''required''label'      => 'Username:',
  3.                 'filters''StringTrim', 'StringToLower'),
  4.                 'validators''Alnum''Regex''/^[a-z][a-z0-9]{2,}$/''password''required''label'      => 'Password:',
  5.                 'filters''StringTrim'),
  6.                 'validators''NotEmpty''StringLength'// 詳細サブフォーム (姓、名、住所) を作成します
  7. 'givenName''required''label'      => 'Given (First) Name:',
  8.                 'filters''StringTrim'),
  9.                 'validators''Regex''/^[a-z][a-z0-9., \'-]{2,}$/i''familyName''required''label'      => 'Family (Last) Name:',
  10.                 'filters''StringTrim'),
  11.                 'validators''Regex''/^[a-z][a-z0-9., \'-]{2,}$/i''location''required''label'      => 'Your Location:',
  12.                 'filters''StringTrim'),
  13.                 'validators''StringLength'// メーリングリストサブフォームを作成します
  14. 'none'        => 'No lists, please',
  15.             'fw-general'  => 'Zend Framework General List',
  16.             'fw-mvc'      => 'Zend Framework MVC List',
  17.             'fw-auth'     => 'Zend Framwork Authentication and ACL List',
  18.             'fw-services' => 'Zend Framework Web Services List''subscriptions''label'        =>
  19.                     'Which lists would you like to subscribe to?',
  20.                 'multiOptions''required''filters''StringTrim'),
  21.                 'validators''InArray'// サブフォームをメインフォームにアタッチします
  22. 'user'  => $user,
  23.             'demog' => $demog,
  24.             'lists'

submit ボタンがないこと、 またサブフォームのデコレータではなにもしていないことに注意しましょう。 そのままでは、これらのサブフォームはフィールドセットとして表示されることになります。 つまり、処理をオーバーライドしてそれらを個別のサブフォームになるようにし、 さらに submit ボタンを追加して処理を進められるようにする必要があります。 submit ボタンには action プロパティと method プロパティも必要です。 では、これらの機能のとっかかりを先ほどのクラスに追加してみましょう。

  1. span style="color: #808080; font-style: italic;">// ...
  2.  
  3.     /**
  4.      * 表示用のサブフォームを準備する
  5.      *
  6.      * @param  string|Zend_Form_SubForm $spec
  7.      * @return Zend_Form_SubForm
  8.      */'Invalid argument passed to ''()'/**
  9.      * Form デコレータを各サブフォームに追加する
  10.      *
  11.      * @param  Zend_Form_SubForm $subForm
  12.      * @return My_Form_Registration
  13.      */'FormElements''HtmlTag''tag' => 'dl',
  14.                                    'class' => 'zend_form')),
  15.             'Form'/**
  16.      * submit ボタンを各サブフォームに追加する
  17.      *
  18.      * @param  Zend_Form_SubForm $subForm
  19.      * @return My_Form_Registration
  20.      */'save''label'    => 'Save and continue',
  21.                 'required''ignore'/**
  22.      * action と method をサブフォームに追加する
  23.      *
  24.      * @param  Zend_Form_SubForm $subForm
  25.      * @return My_Form_Registration
  26.      */'/registration/process')
  27.                 ->setMethod('post'

次に、アクションコントローラ用の仕組みを追加する必要があります。 さらにいくつか考えなければならないこともあります。 まず、フォームの入力内容をリクエスト間で持続させなければなりません。 次に、フォームの情報のうちどの部分が入力済みなのか、 そしてその部分に対応するサブフォームがどれなのか といった情報を取得するロジックも必要です。今回は Zend_Session_Namespace を使用してデータを持続させることにします。 そうすれば、二番目の問題に対応するのも簡単になるでしょう。

それではコントローラを作成していきましょう。 そして、フォームのインスタンスを取得するためのメソッドを追加します。

  1.  

それでは、どのフォームを表示するのかを決める機能を追加していきましょう。 基本的に、フォーム全体の入力内容の検証を終えるまでは フォームの一部の表示を続けることになります。 さらに、普通はそれを決まった順序で表示することになるでしょう。 今回の場合は user、demog、そして最後に lists といった具合です。 どのデータが入力済みかを調べるには、セッションの名前空間を調べます。 各サブフォームに対応するキーが存在するかどうかを調べるというわけです。

  1. span style="color: #808080; font-style: italic;">// ...
  2. 'RegistrationController'/**
  3.      * 使用するセッション名前空間を取得する
  4.      *
  5.      * @return Zend_Session_Namespace
  6.      *//**
  7.      * すでにセッションに保存済みであるフォームの一覧を取得する
  8.      *
  9.      * @return array
  10.      *//**
  11.      * 使用できるすべてのサブフォームの一覧を取得する
  12.      *
  13.      * @return array
  14.      *//**
  15.      * 今どのサブフォームが送信されたのか?
  16.      *
  17.      * @return false|Zend_Form_SubForm
  18.      *//**
  19.      * 次に表示するサブフォームを取得する
  20.      *
  21.      * @return Zend_Form_SubForm|false
  22.      */

上のメソッドを使用すると、たとえば "$subForm = $this->getCurrentSubForm();" で現在のサブフォームを取得してそれを検証したり "$next = $this->getNextSubForm();" で次に表示するフォームを取得したりできます。

では、実際にサブフォームを処理したり表示したりする方法を考えてみましょう。 getCurrentSubForm() を使用すれば、 今送信されてきたデータがどのサブフォームのものなのかがわかります (FALSE が返された場合は、まだ何も表示あるいは送信されていないことを表します)。 また、 getNextSubForm() を使用すれば次に表示すべきフォームを取得できます。 そして、フォームの prepareSubForm() メソッドを使用すれば、フォームを表示するための準備を行えます。

フォームを送信したら、サブフォームのデータを検証し、 そしてフォーム全体の入力が完了したかどうかを調べることができます。 これらの作業を行うためには、さらにいくつかのメソッドを追加しなければなりません。 送信されたデータをセッションに追加するメソッドや、 フォーム全体の検証を行う際にセッションの全セグメントを検証するメソッドなどです。

  1. span style="color: #808080; font-style: italic;">// ...
  2.  
  3.     /**
  4.      * サブフォームの入力は妥当か?
  5.      *
  6.      * @param  Zend_Form_SubForm $subForm
  7.      * @param  array $data
  8.      * @return bool
  9.      *//**
  10.      * フォーム全体の入力は妥当か?
  11.      *
  12.      * @return bool
  13.      */

これで足場は固まりました。 ではこのコントローラのアクションを作っていきましょう。 まずこのフォームの最初のページ、 それからフォームを処理するための 'process' アクションが必要となります。

  1. span style="color: #808080; font-style: italic;">// ...
  2. // 現在のページを再表示するか、"次の" (最初の)
  3.         // サブフォームを取得します
  4. 'index''index''index');
  5.         }
  6.  
  7.         // フォームの入力が完了しました!
  8.         // 確認ページに情報を表示します
  9. 'verification');
  10.     }
  11. }

お気づきのとおり、実際にフォームを処理する部分のコードは比較的シンプルです。 注目すべき点は、どのサブフォームが送信されてきたのかを調べ、 何も送信されていない場合は先頭ページに飛ばしている部分です。 サブフォームが送信されてきた場合はそれを検証し、 問題がある場合は同じサブフォームを再表示します。 問題がない場合は、フォーム全体の入力が妥当か (つまりすべての入力が終わっているか) を調べ、 問題がある場合は次のサブフォームを表示します。 最後に、セッションの中身を確認ページに表示します。

ビュースクリプトは非常にシンプルなものになります。

  1. span style="color: #808080; font-style: italic;">// registration/index.phtml ?>
  2. // registration/verification.phtml ?>
  3. // 入力内容はセッション名前空間に格納されているので、
  4. // このようにしなければなりません

将来的に、Zend Framework には複数ページのフォームを より簡単に作成するためのコンポーネントが用意される予定です。 このコンポーネントは、 セッションや各フォームの順序などの管理を抽象化したものとなります。 現時点では、複数ページのフォームをあなたのサイトで使用するには 上の例のようにするのが最も無難でしょう。


Zend_Form の国際化