フォームは、隠し入力、テキスト入力、セレクトボックスおよびチェックボックスなどのフィールドから構成されます。 この章では symfony のフォームフレームワークを利用してフォームを作成し、フォームフィールドを管理する方法を説明します。
この章を読むにあたり、symfony 1.3/1.4 が必要です。また、読み進めるにはプロジェクトおよび frontend
アプリケーションを作っておく必要があります。
symfony プロジェクトの作り方に関して、入門の手引きをご参照ください。
はじめる前に
問い合わせフォームを symfony アプリケーションに追加することからはじめます。
図1-1は、ユーザーがメッセージを送りたい場合に表示される問い合わせフォームです。
図1-1 - 問い合わせフォーム
このフォームに3つのフィールドを作成します。ユーザーの名前、ユーザーのメールアドレスおよび送信するメッセージです。この練習課題では、図1-2のようにフォームに投稿された情報を単に表示することが目的です。
図1-2 - サンキューページ
図1-3 - アプリケーションとユーザーのあいだのやりとり
ウィジェット
sfForm クラスと sfWidget クラス
ユーザーはフォームを構成しているフィールドに情報を入力します。symfony では、フォームは sfForm
クラスを継承するオブジェクトです。この例では、sfForm
クラスを継承する ContactForm
クラスを作ります。
note
sfForm
はすべてのフォームの基底クラスで、フォームの設定とライフサイクルの管理を簡略化します。
configure()
メソッドにウィジェットを追加するコードを記述することからフォームの設定をはじめましょう。
ウィジェットはフォームフィールドをあらわします。この例のフォームでは、3つのフィールド: name
、email
および message
をあらわす3つのウィジェットを追加する必要があります。
リスト1-1は ContactForm
クラスの最初の実装を示します。
リスト1-1 - 3つのフィールドが用意されているフォームの ContactForm
クラス
// lib/form/ContactForm.class.php class ContactForm extends BaseForm { public function configure() { $this->setWidgets(array( 'name' => new sfWidgetFormInputText(), 'email' => new sfWidgetFormInputText(), 'message' => new sfWidgetFormTextarea(), )); } }
note
この本では、スペースの節約のために純粋な PHP コードの例では <?php
開きステートメントは省略します。新しい PHP ファイルを作るときには開きタグを追加してください。
ウィジェットは configure()
メソッドのなかで定義します。
このメソッドは sfForm
クラスのコンストラクタによって自動的に呼び出されます。
setWidgets()
メソッドは、フォームに使われるウィジェットを定義するために使われます。
setWidgets()
メソッドは、キーがフィールド名で値がウィジェットのオブジェクトである連想配列を受け入れます。
それぞれのウィジェットは sfWidget
クラスを継承するオブジェクトです。この例では2種類のウィジェットを使いました。
sfWidgetFormInputText
: このウィジェットはinput
フィールドをあらわしますsfWidgetFormTextarea
: このウィジェットはtextarea
フィールドをあらわします
note
慣習として、フォームクラスを lib/form/
ディレクトリに保存します。
これらのフォームクラスは、symfony のオートローディングメカニズムで管理される任意のディレクトリに保存できます。
後の章で説明するように、モデルオブジェクトからフォームを生成するために symfony は lib/form/
ディレクトリを利用します。
フォームを表示する
フォームを使う準備ができました。 フォームを表示するための symfony のモジュールを作りましょう。
$ cd ~/PATH/TO/THE/PROJECT $ php symfony generate:module frontend contact
contact
モジュールで、フォームのインスタンスをテンプレートに渡すよう、リスト1-2のように index
アクションを修正しましょう。
リスト1-2 - contact
モジュールのアクションクラス
// apps/frontend/modules/contact/actions/actions.class.php class contactActions extends sfActions { public function executeIndex() { $this->form = new ContactForm(); } }
フォームがインスタンス化される際に、リスト1-1で定義した configure()
メソッドは自動的に呼び出されます。
アクションの修正に合わせてフォームを表示するために、リスト1-3のようなテンプレートを作る必要があります。
リスト1-3 - フォームを表示するテンプレート
// apps/frontend/modules/contact/templates/indexSuccess.php <form action="<?php echo url_for('contact/submit') ?>" method="POST"> <table> <?php echo $form ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
symfony のフォームはユーザーに情報を表示するウィジェットのみを扱います。
indexSuccess
テンプレートにおいて、<?php echo $form ?>
の行は3つのフィールドを表示するのみです。
form
タグや投稿ボタンなどの別の要素は、開発者が追加する必要があります。
最初これは自明ではないかもしれませんが、フォームの埋め込みを行う場合に便利でかんたん方法であることを後に説明します。
プロトタイプを作ったり、フォームを定義する場合に、<?php echo $form ?>
構文はとても便利です。
これによって、開発者はビジネスロジックに集中できるようになり、視覚上の問題に悩まなくてすみます。
第3章では、テンプレートとフォームレイアウトをパーソナライズする方法を説明します。
note
<?php echo $form ?>
を利用してオブジェクトを表示するとき、実際には PHP エンジンは $form
オブジェクトのテキスト表現を表示します。オブジェクトを文字列に変換するために、PHP は __toString()
マジックメソッドを実行しようとします。オブジェクトを HTML コードに変換するためにそれぞれのウィジェットにこのマジックメソッドが実装されています。<?php echo $form ?>
を呼び出すことは、<?php echo $form->__toString() ?>
を呼び出すことと同等です。
ブラウザでフォームを表示してみましょう (図1-4)。
contact/index
アクションのアドレス (/frontend_dev.php/contact
) を入力して、結果をチェックしましょう。
図1-4 - 生成された問い合わせフォーム
リスト1-4はテンプレートによって生成されたコードです。
<form action="/frontend_dev.php/contact/submit" method="POST"> <table> <!-- Beginning of generated code by <?php echo $form ?> --> <tr> <th><label for="name">Name</label></th> <td><input type="text" name="name" id="name" /></td> </tr> <tr> <th><label for="email">Email</label></th> <td><input type="text" name="email" id="email" /></td> </tr> <tr> <th><label for="message">Message</label></th> <td><textarea rows="4" cols="30" name="message" id="message"></textarea></td> </tr> <!-- End of generated code by <?php echo $form ?> --> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
フォームオブジェクトが HTML テーブルの3つの <tr>
ブロックとして表示されていることがわかります。このため、フォームを <table>
タグで囲んでおく必要があります。それぞれの行には、<label>
タグおよび <input>
や <textarea>
といったフォームタグが含まれます。
ラベル
それぞれのフィールドのラベルは自動生成されます。デフォルトでは、ラベルは次の2つのルールにしたがって、フィールド名から変換されます。先頭は大文字に、アンダースコアはスペースに置き換えられます。フィールドの名前が「_id」で終わる場合、サフィックスはラベルから削除されます。例:
$this->setWidgets(array( 'first_name' => new sfWidgetFormInputText(), // 生成されるラベル: "First name" 'last_name' => new sfWidgetFormInputText(), // 生成されるラベル: "Last name" 'author_id' => new sfWidgetFormInputText(), // 生成されるラベル: "Author" ));
自動的なラベルの生成はとても便利ですが、フレームワークでは、setLabels()
メソッドを使ってパーソナライズされたラベルを定義することもできます。
$this->widgetSchema->setLabels(array( 'name' => 'Your name', 'email' => 'Your email address', 'message' => 'Your message', ));
setLabel()
メソッドを使って単独のラベルのみを修正することもできます。
$this->widgetSchema->setLabel('email', 'Your email address');
最後に、3章ではフォームをより細かくカスタマイズするために、テンプレートからラベルを拡張する方法を説明します。
生成されたテーブルを越えて
フォームの表示はデフォルトで HTML テーブルですが、レイアウトのフォーマットを変更できます。異なるタイプのレイアウトフォーマットが定義されています。これらのクラスは sfWidgetFormSchemaFormatter
を継承します。デフォルトでは、sfWidgetFormSchemaFormatterTable
クラスで定義されている table
フォーマットがフォームに適用されます。list
フォーマットに切り替えることもできます。
class ContactForm extends BaseForm { public function configure() { $this->setWidgets(array( 'name' => new sfWidgetFormInputText(), 'email' => new sfWidgetFormInputText(), 'message' => new sfWidgetFormTextarea(), )); $this->widgetSchema->setFormFormatterName('list'); } }
これら2つのフォーマットはデフォルトで組み込まれています。独自のフォーマットクラスを作る方法は、5章で説明します。フォームを表示できたので、投稿を管理する方法へ進みましょう。
フォームを投稿する
リスト1-3のフォームを表示するテンプレートでは、form
タグブロックのなかでフォームを投稿するための内部 URL として contact/submit
を使いました。したがって、submit
アクションを contact
モジュールに追加する必要があります。
リスト1-5では、ユーザーが入力した情報をアクションで取得し、サンキューページにリダイレクトして単にこの情報をユーザーに表示する方法を示します。
リスト1-5 - contact
モジュールの submit
アクションの使い方
public function executeSubmit(sfWebRequest $request) { $this->forward404Unless($request->isMethod('post')); $params = array( 'name' => $request->getParameter('name'), 'email' => $request->getParameter('email'), 'message' => $request->getParameter('message'), ); $this->redirect('contact/thankyou?'.http_build_query($params)); } public function executeThankyou() { } // apps/frontend/modules/contact/templates/thankyouSuccess.php <ul> <li>Name: <?php echo $sf_params->get('name') ?></li> <li>Email: <?php echo $sf_params->get('email') ?></li> <li>Message: <?php echo $sf_params->get('message') ?></li> </ul>
note
http_build_query
は PHP の組み込み関数で、パラメータの配列から URL エンコードされたクエリ文字列を生成します。
executeSubmit()
メソッドは3つのアクションを実行します。
セキュリティに配慮して、HTTP
POST
メソッドを利用してページが投稿されたことをチェックします。POST
メソッドを利用して送信されていない場合、ユーザーは404ページにリダイレクトされます。indexSuccess
テンプレートにおいて、投稿メソッドをPOST
として宣言しました (<form ... method="POST">
)。$this->forward404Unless($request->isMethod('post'));
次に、ユーザーの入力値を取得して
params
テーブルに保存します。$params = array( 'name' => $request->getParameter('name'), 'email' => $request->getParameter('email'), 'message' => $request->getParameter('message'), );
最後に、ユーザーをサンキューページ (
contact/thankyou
) にリダイレクトして、入力された情報を表示します。$this->redirect('contact/thankyou?'.http_build_query($params));
ユーザーを別のページにリダイレクトする代わりに submitSuccess.php
テンプレートを作る選択肢もありますが、POST
メソッドによるリクエストの後でユーザーをつねにリダイレクトするほうがよい習慣です。
ユーザーがサンキューページをリロードする場合に再投稿を避けることができます。
ユーザーが戻るボタンをクリックしても、フォームを再投稿するポップアップが表示されません。
tip
executeSubmit()
メソッドが executeIndex()
メソッドと異なることにお気づきになられたでしょうか。これらのメソッドを呼び出すとき、symfony は現在の sfRequest
オブジェクトを executeXXX()
メソッドの第1引数に渡します。PHP ではすべてのパラメータを定義する必要はなく、また request
変数は executeIndex()
メソッドに必要なかったため、定義しませんでした。
図1-5はユーザーとやりとりするときのメソッドのワークフローを示します。
図1-5 - メソッドのワークフロー
note
テンプレート内でユーザーの入力を再表示するとき、XSS (クロスサイトスクリプティング) 攻撃のリスクにさらされます。XSS のリスクを回避するためのエスケープ処理の実装に関する詳細な情報は、「A Gentle Introduction to symfony」 のビューレイヤーの内側の章をご参照ください。
フォームを投稿すると、図1-6のようなページが表示されます。
図1-6 - フォームを投稿した後に表示されるページ
params
配列を作る代わりに、ユーザーの入力情報を配列形式で直接取得できるると便利です。リスト1-6のように、フィールドの値を contact
配列に保存するように、ウィジェットが出力する name
属性を修正できます。
リスト1-6 - ウィジェットが出力する name
属性の修正
class ContactForm extends BaseForm { public function configure() { $this->setWidgets(array( 'name' => new sfWidgetFormInputText(), 'email' => new sfWidgetFormInputText(), 'message' => new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contact[%s]'); } }
setNameFormat()
メソッドを呼び出すことで、すべてのウィジェットが出力する HTML の name
属性を修正できます。フォームを生成するとき、%s
は自動的にフィールドの名前に置き換えられます。
たとえば email
フィールドでは、name
属性は contact[email]
になります。PHP では、contact[email]
のようなフォーマットを含むリクエストの値に対して、配列が自動生成されます。このようにすると、フィールドの値を contact
配列として利用できます。
リスト1-7で示すように、request
オブジェクトから直接 contact
配列を取得できます。
リスト1-7 - アクションウィジェット内の name
属性の新しいフォーマット
public function executeSubmit(sfWebRequest $request) { $this->forward404Unless($request->isMethod('post')); $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact'))); }
フォームの HTML ソースを表示すると、name
属性だけでなく、フィールド名とフォーマットに依存した id
属性も symfony によって生成されていることがわかります。
id
属性は、name
属性から禁止されている文字をアンダースコア (_
) に置き換えることで自動的に作成されます。
名前 | name 属性 |
id 属性 |
---|---|---|
name | contact[name] | contact_name |
contact[email] | contact_email | |
message | contact[message] | contact_message |
別の解決方法
この例では、フォームを管理するために2つのアクション: 表示に対して index
、投稿に対して submit
を使いました。
フォームは GET
メソッドで表示され POST
メソッドで投稿されることを利用して、リスト1-8で示されるように2つのメソッドを index
メソッドにマージすることもできます。
リスト1-8 - フォームで使われる2つのアクションをマージする
class contactActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->form = new ContactForm(); if ($request->isMethod('post')) { $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact'))); } } }
indexSuccess.php
テンプレート内のフォームの action
属性も変更します。
<form action="<?php echo url_for('contact/index') ?>" method="POST">
後で説明するように、短くて首尾一貫しているので、この構文を使うことが望ましいです。
ウィジェットを設定する
ウィジェットのオプション
Web サイトに複数の管理者がいる場合、質問内容に応じてメッセージをリダイレクトできるように、分類のドロップダウンリストを追加したいことでしょう (図1-7)。
リスト1-9では、sfWidgetFormSelect
ウィジェットを利用して、ドロップダウンリストの subject
を追加します。
図1-7 - subject
フィールドをフォームに追加する
リスト1-9 - フォームに subject
フィールドを追加する
class ContactForm extends BaseForm { protected static $subjects = array('Subject A', 'Subject B', 'Subject C'); public function configure() { $this->setWidgets(array( 'name' => new sfWidgetFormInputText(), 'email' => new sfWidgetFormInputText(), 'subject' => new sfWidgetFormSelect(array('choices' => self::$subjects)), 'message' => new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contact[%s]'); } }
sfWidgetFormSelect
ウィジェットは、すべてのウィジェットと同じように、オプションのリストを第1引数として受け入れます。オプションは必須もしくはオプションです。sfWidgetFormSelect
ウィジェットでは、choices
オプションは必須です。これまでに見てきたウィジェットで利用可能なオプションは下記のとおりです。
ウィジェット | 必須のオプション | 追加のオプション |
---|---|---|
sfWidgetFormInputText |
- | type (デフォルトは text ) |
is_hidden (デフォルトは false ) |
||
sfWidgetFormSelect |
choices |
multiple (デフォルトは false ) |
sfWidgetFormTextarea |
- | - |
tip
ウィジェットのすべてのオプションを知りたければ、オンラインの API ドキュメントで参照できます。追加のオプションやデフォルト値も合わせて、すべてのオプションが説明されています。たとえば sfWidgetFormSelect
のすべてのオプションは、次のURLで調べることができます: (/api/1_4/sfWidgetFormSelect)
ウィジェットの HTML 属性
それぞれのウィジェットは HTML 属性のリストをオプションの第2引数にとります。これは生成されるタグのデフォルトの HTML 属性を定義するためにとても役立ちます。リスト1-10では、 email
フィールドに class
属性を追加する方法を示しています。
リスト1-10 - ウィジェットに対して属性を定義する
$emailWidget = new sfWidgetFormInputText(array(), array('class' => 'email')); // 生成される HTML <input type="text" name="contact[email]" class="email" id="contact_email" />
リスト1-11で示されるように、HTML 属性を指定して、自動生成される id
属性をオーバーライドできます。
リスト1-11 - id
属性をオーバーライドする
$emailWidget = new sfWidgetFormInputText(array(), array('class' => 'email', 'id' => 'email')); // 生成される HTML <input type="text" name="contact[email]" class="email" id="email" />
リスト1-12で示されるように、value
属性を指定して、フィールドのデフォルト値を設定することも可能です。
リスト1-12 - HTML 属性のデフォルト値を設定する
$emailWidget = new sfWidgetFormInputText(array(), array('value' => 'Your Email Here')); // 生成される HTML <input type="text" name="contact[email]" value="Your Email Here" id="contact_email" />
このオプションは input
ウィジェットに対して利用できますが、checkbox
や radio
ウィジェットでは正しく機能せず、textarea
ウィジェットでは利用できません。sfForm
クラスには、任意のタイプのウィジェットに対して統一された方法で、それぞれのフィールドのデフォルト値を定義する特別なメソッドが用意されています。
note
第3章で説明するレイヤーの分離を保つために、(可能であっても) HTML 属性をフォームクラスで定義するのではなく、テンプレートで定義することをおすすめします。
フィールドのデフォルト値を定義する
それぞれのフィールドごとにデフォルト値を定義しておくと便利です。たとえば、ユーザーがフィールドにフォーカスを移すと消えるヘルプメッセージを、フィールド内に表示するときです。
リスト1-13では、setDefault()
と setDefaults()
メソッドを利用してデフォルト値を定義する方法を示しています。
リスト1-13 - setDefault()
と setDefaults()
メソッドによるウィジェットのデフォルト値
class ContactForm extends BaseForm { public function configure() { // ... $this->setDefault('email', 'Your Email Here'); $this->setDefaults(array('email' => 'Your Email Here', 'name' => 'Your Name Here')); } }
setDefault()
と setDefaults()
メソッドは同じフォームクラスのすべてのインスタンスに対して理想的なデフォルト値を定義するために役立ちます。フォームを使用する既存のオブジェクトを修正したい場合、デフォルトの値はインスタンスに依存するので、それゆえこれらを動的に設定しなければなりません。リスト1-14は sfForm
のコンストラクタがデフォルトの値を動的に設定するために第1引数をとることを示します。
リスト1-14 - sfForm
のコンストラクタでウィジェットのデフォルト値を設定する
public function executeIndex(sfWebRequest $request) { $this->form = new ContactForm(array('email' => 'Your Email Here', 'name' => 'Your Name Here')); // ... }
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.