1章で、基本的な問い合わせフォームを作り、表示する方法を学びました。この章ではフォームのバリデーションを管理する方法を学びます。
始める前に
1章で作った問い合わせフォームにはまだじゅうぶんな機能が備わっていません。ユーザーが無効なメールアドレスを投稿するもしくはユーザーが投稿するメッセージが空の場合何が起きるでしょうか?これらのケースでは、図2-1で示されるように、ユーザーに入力訂正を求めるためにエラーメッセージを表示するとよいでしょう。
図2-1 - エラーメッセージを表示する

問い合わせフォームに実装するバリデーションルールは次のとおりです。
name: オプションemail: 必須、値は有効なメールアドレスでなければなりませんsubject: 必須、選んだ値は値のリストに対して有効でなければなりませんmessage: 必須、メッセージの長さは少なくとも4文字でなければなりません
note
なぜ subject フィールドのバリデーションを行う必要があるのでしょうか?<select> タグでは、ユーザーはあらかじめ定義された値しか選ぶことができません。通常のユーザーは示された選択肢の1つだけを選ぶことができます。しかし、Firefox Developer Toolbar のようなツールを利用するか、curl や wget でリクエストをシミュレートすることで、定義された値とは異なる値を投稿できます。
リスト2-1は1章で使われたテンプレートです。
リスト2-1 - 問い合わせフォームのテンプレート
// apps/frontend/modules/contact/templates/indexSucces.php <form action="<?php echo url_for('contact/index') ?>" method="POST"> <table> <?php echo $form ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
図2-2はアプリケーションとユーザーとのやりとりを示しています。まず最初に、フォームをユーザーに表示します。ユーザーがフォームを投稿すると、入力が有効な場合はサンキューページにリダイレクトされ、入力に無効な値が含まれる場合、エラーメッセージとともにフォームが再表示されます。
図2-2 - アプリケーションとユーザーとのやりとり

バリデータ
symfony のフォームはフィールドで構成されます。1章で見てきたように、それぞれのフィールドは一意性のある名前で識別されます。フィールドをユーザーに表示するために、それぞれのフィールドにウィジェットをバインドしました。ここでは、それぞれのフィールドにバリデーションルールを適用する方法を見てみましょう。
sfValidatorBase クラス
それぞれのフィールドのバリデーションは sfValidatorBase クラスを継承するオブジェクトによって行われます。問い合わせフォームのバリデーションを行うために、4つのそれぞれのフィールド: name、email、subject、message に対してバリデータオブジェクトを定義します。
リスト2-2では、フォームクラスの setValidators() メソッドを使ってこれらのバリデータを実装する方法が示されています。
リスト2-2 - ContactForm クラスにバリデータを追加する
// lib/form/ContactForm.class.php 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]'); $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )); } }
3つの異なるバリデータを使います。
sfValidatorString: 文字列のバリデーションを行いますsfValidatorEmail: メールのバリデーションを行いますsfValidatorChoice: 入力値とあらかじめ定義された選択肢のリストに対してバリデーションを行います
それぞれのバリデータはオプションのリストを第1引数にとります。ウィジェットのように、これらのオプションの中には必須であるものと、必須ではないものがあります。たとえば、sfValidatorChoice バリデータは1つの必須オプションである choices を受け入れます。それぞれのバリデータは、sfValidatorBase クラスで定義されている required と trim オプションも受け入れます。
| オプション | デフォルト値 | 説明 |
|---|---|---|
| required | true |
フィールドが必須であるか指定します。 |
| trim | false |
バリデーションが行われる前に、文字列の最初と最後の空白を自動的に削除します。 |
リスト2-2で使われたバリデータの利用可能なオプションを見てみましょう。
| バリデータ | 必須のオプション | 任意のオプション |
|---|---|---|
| sfValidatorString | max_length |
|
min_length |
||
| sfValidatorEmail | pattern |
|
| sfValidatorChoice | choices |
この時点でフォームに無効な値を投稿しても、フォームの動作は以前と同じままです。リスト2-3で示されるように、投稿された値のバリデーションを行うように contact モジュールを更新する必要があります。
リスト2-3 - contact モジュール内でバリデーションを実装する
class contactActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->form = new ContactForm(); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('contact')); if ($this->form->isValid()) { $this->redirect('contact/thankyou?'.http_build_query($this->form->getValues())); } } } public function executeThankyou() { } }
リスト2-3にはさまざまな新しい概念が導入されています。
最初の
GETリクエストを処理するブロックにおいて、フォームは初期化されユーザーに表示するためにテンプレートに渡されます。フォームは初期状態にあります。$this->form = new ContactForm();
ユーザーが
POSTリクエストでフォームを投稿すると、bind()メソッドによりフォームとユーザーの入力データがバインドされ、バリデーション処理が実行されます。フォームはバインドされた状態に変化します。if ($request->isMethod('post')) { $this->form->bind($request->getParameter('contact'));
いったんフォームがバインドされると、
isValid()メソッドを利用してフォームの有効性をチェックできます。戻り値が
trueの場合、フォームは有効なので、ユーザーはサンキューページにリダイレクトされます。if ($this->form->isValid()) { $this->redirect('contact/thankyou?'.http_build_query($this->form->getValues())); }
戻り値が
trueではない場合、最初と同じようにindexSuccessテンプレートが表示されます。バリデーション処理によって、ユーザーに表示されるエラーメッセージがフォームに追加されます。
note
フォームが初期状態のとき、isValid() メソッドはつねに false を返し getValues() メソッドはつねに空の配列を返します。
図2-3は、アプリケーションとユーザーとのやりとりで実行されるコードを示しています。
図2-3 - アプリケーションとユーザーとのやりとりで実行されるコード

バリデータの目的
サンキューページにリダイレクトする箇所で、$request->getParameter('contact') ではなく $this->form->getValues() が使われていることにお気づきかもしれません。
実際、$request->getParameter('contact') はユーザーデータを返し、$this->form->getValues() はバリデーションが行われた値を返します。
フォームが有効な場合、なぜこれら2つの文は同一ではないのでしょうか?それぞれのバリデータには2つのタスク: バリデーションタスク、だけでなくクリーニングタスクがあります。実際、getValues() メソッドは、バリデーションとクリーニングが行われたデータを返しています。
クリーニングプロセスには、2つの主要なアクション: 入力データの標準化と変換があります。
すでに trim オプションによるデータの標準化のケースについて説明しました。しかし、たとえば日付フィールドでは、標準化のアクションはさらに重要です。sfValidatorDate は日付のバリデーションを行います。このバリデータでは、タイムスタンプ、正規表現にもとづくフォーマットなど、さまざまなフォーマットを利用できます。デフォルトでは、入力を単に返す代わりに Y-m-d H:i:s フォーマットに変換されます。したがって、開発者は入力フォーマットを気にすることなく、特定のフォーマットでデータを取得できることが保証されます。システムはユーザーに多くの柔軟性を提供し、開発者に一貫性を保証します。
では、ファイルのアップロードのような変換アクションを考えてみましょう。ファイルは sfValidatorFile を利用してバリデーションを行うことができます。いったんファイルがアップロードされると、バリデータはファイルの名前を返す代わりに sfValidatedFile オブジェクトを返します。sfValidatedFile オブジェクトを使うことで、ファイル情報の取り扱いがよりかんたんになります。この章の後半で、このバリデータを使う方法を説明します。
tip
getValues() メソッドはバリデーションが行われクリーンになったすべての値の配列を返します。しかし、何か1つの値だけを読み込む場合、getValue() メソッド: $email = $this->form->getValue('email') を利用できます。
無効なフォーム
フォームに無効な値があるときは、indexSuccess テンプレートが表示されます。図2-4は無効なデータでフォームを投稿したときの表示結果です。
図2-4 - 無効なフォーム

<?php echo $form ?> ステートメントの呼び出しでは、フィールドに関連したエラーメッセージが自動的に表示され、クリーンアップされたユーザーの入力データが投入されます。
bind メソッドを利用してフォームが外部データにバインドされると、フォームはバインドされた状態になり、次のアクションが実行されます。
バリデーション処理が実行されます。
テンプレートで利用できるように、エラーメッセージがフォームに保存されます。
フォームのデフォルト値が、クリーンアップされたユーザーの入力データに置き換えられます。
テンプレートのなかで form 変数を使うことで、エラーメッセージや入力データを表示するために必要な情報をかんたんに利用できます。
caution
1章で説明したように、デフォルト値をフォームクラスのコンストラクタに渡すことができます。無効なフォームを投稿した後に、これらのデフォルト値は投稿された値でオーバーライドされ、ユーザーは間違いを訂正できます。したがって、次の例のように入力データをデフォルト値として使わないでください: $this->form->setDefaults($request->getParameter('contact'))
バリデータのカスタマイズ
エラーメッセージをカスタマイズする
図2-4でお気づきかもしれませんが、このエラーメッセージは実際には役に立ちません。これらのメッセージをより分かりやすいものにカスタマイズする方法を見てみましょう。
それぞれのバリデータは、フォームにエラーを追加できます。エラーはエラーコードとエラーメッセージで構成されます。すべてのバリデータには、少なくとも sfValidatorBase で定義された required と invalid エラーがあります。
| コード | メッセージ | 説明 |
|---|---|---|
| required | Required. |
フィールドは必須で値は空です |
| invalid | Invalid. |
フィールドは無効です |
すでに利用したバリデータに関するエラーコードは次のとおりです。
| バリデータ | エラーコード |
|---|---|
| sfValidatorString | max_length |
min_length |
|
| sfValidatorEmail | |
| sfValidatorChoice |
エラーメッセージをカスタマイズするには、バリデーションオブジェクトを作るときに第2引数で指定します。 リスト2-4ではいくつかのエラーメッセージをカスタマイズし、図2-5ではカスタマイズされたエラーメッセージが実際に表示されています。
リスト2-4 - エラーメッセージをカスタマイズする
class ContactForm extends BaseForm { protected static $subjects = array('Subject A', 'Subject B', 'Subject C'); public function configure() { // ... $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(array(), array('invalid' => 'The email address is invalid.')), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4), array('required' => 'The message field is required.')), )); } }
図2-5 - カスタマイズされたエラーメッセージ

図2-6は、短すぎるメッセージを投稿しようとすると表示されるエラーメッセージを示しています (最小の長さを4文字に設定します)。
図2-6 - 短すぎるメッセージエラー

このエラーコード (min_length) に関連したデフォルトのエラーメッセージは、すでに説明したメッセージと異なります。
2つの動的な値: ユーザーの入力データ (foo) と、このフィールドで許可する最小の文字数 (4) を実装しています。リスト2-5では、これらの動的な値を利用してエラーメッセージをカスタマイズし、図2-7はカスタマイズした結果です。
リスト2-5 - エラーメッセージを動的な値でカスタマイズする
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(array(), array('invalid' => 'Email address is invalid.')), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4), array( 'required' => 'The message field is required', 'min_length' => 'The message "%value%" is too short. It must be of %min_length% characters at least.', )), )); } }
図2-7 - 動的な値でカスタマイズされたエラーメッセージ

それぞれのエラーメッセージには、動的な値を使うこともできます。値の名前をパーセント記号 (%) で囲みます。利用可能な値は、通常はユーザーの入力データ (value) とエラーに関連したバリデータの値です。
tip
バリデータのすべてのエラーコード、オプション、デフォルトメッセージを確認したい場合は、API のオンラインドキュメント (/api/1_4/)をご参照ください。デフォルト値に加えて、それぞれのコード、オプションとエラーメッセージが詳細に記述されています。たとえば、sfValidatorString バリデータの API は /api/1_4/sfValidatorString で確認できます。
バリデータのセキュリティ
デフォルトでは、ユーザーによって投稿されるすべてのフィールドにバリデータがある場合にのみ、フォームは有効になります。 これにより、それぞれのフィールドにバリデーションルールが設定されていること、およびフォームで定義されていないフィールドを使って値を不正に注入できないことが保証されます。
このセキュリティルールを理解するために、リスト2-6で示されるユーザーオブジェクトを考えてみましょう。
リスト2-6 - User クラス
class User { protected $name = '', $is_admin = false; public function setFields($fields) { if (isset($fields['name'])) { $this->name = $fields['name']; } if (isset($fields['is_admin'])) { $this->is_admin = $fields['is_admin']; } } // ... }
User オブジェクトは2つのプロパティ: ユーザー名 (name)、管理者のステータスを保存するブール値 (is_admin) から構成されます。
setFields() メソッドは両方のプロパティを更新します。
リスト2-7では User クラスに関連したフォームクラスで、ユーザーは name プロパティのみを修正することができます。
リスト2-7 - User フォーム
class UserForm extends BaseForm { public function configure() { $this->setWidgets(array('name' => new sfWidgetFormInputTextString())); $this->widgetSchema->setNameFormat('user[%s]'); $this->setValidators(array('name' => new sfValidatorString())); } }
リスト2-8では、リスト2-7で定義したフォームクラスを利用して、ユーザーが name フィールドを修正できる user モジュールの実装方法を示しています。
リスト2-8 - user モジュールの実装
class userActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->form = new UserForm(); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('user')); if ($this->form->isValid()) { $user = // 現在のユーザーを読み取る $user->setFields($this->form->getValues()); $this->redirect('...'); } } } }
保護がない場合、ユーザーが name フィールドと同時に is_admin フィールドの値をフォームから投稿すると、このコードには脆弱性があります。このような投稿は、Firebug のようなツールを使うことでかんたんに実現できます。実際、フォーム内に is_admin フィールドに関連づけられたバリデータがないため、is_admin の値はつねに有効です。
値が何であれ、setFields() メソッドは name プロパティだけでなく、is_admin プロパティも更新します。
しかし、このコードで name と is_admin フィールドの両方に対して値を渡すテストすると、図2-8で示されるように、グローバルエラーの「Extra field name.」が表示されます。投稿されたフィールドに、バリデータが関連づけられていないものがあるため、システムでエラーが発生しました。is_admin フィールドは UserForm フォームクラスで定義されていません。
図2-8 - バリデータが見つからないエラー

これまで説明したすべてのバリデータは、フィールドに関連するエラーを生成します。このグローバルエラーはどこで処理されているのでしょうか?setValidators() メソッドを使うと、 sfValidatorSchema オブジェクトが生成されます。sfValidatorSchema はバリデータのコレクションを定義します。setValidators() メソッド呼び出しは次のコードと同等です。
$this->setValidatorSchema(new sfValidatorSchema(array( 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )));
バリデータのコレクションを保護するために sfValidatorSchema にはデフォルトで有効な2つのバリデーションルールが用意されています。デフォルトのルールは allow_extra_fields と filter_extra_fields オプションで設定できます。
allow_extra_fields オプションのデフォルト値は false であり、ユーザーの入力データのすべてにバリデータがあるかをチェックします。バリデータがない場合、以前の例で示したように、グローバルエラーの「Extra field name.」が表示されます。開発時には、開発者がフィールドを明示的にバリデーションを行うことを忘れた場合の警告になります。
問い合わせフォームに戻ってみましょう。name フィールドが必須のフィールドとなるようバリデーションルールを変更してみましょう。required オプションのデフォルト値は true なので、name バリデータを次のように変更できます。
$nameValidator = new sfValidatorString();
min_length もしくは max_length オプションがセットされていないので、このバリデータは何も影響を与えません。この場合、これを空のバリデータに置き換えることもできます。
$nameValidator = new sfValidatorPass();
空のバリデータを定義する代わりに、バリデータを削除することもできますが、すでに説明したデフォルトの保護機能があるためうまくゆきません。リスト2-9では、allow_extra_fields オプションを利用して保護機能を無効にする方法を示しています。
リスト2-9 - allow_extra_fields 保護機能を無効にする
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )); $this->validatorSchema->setOption('allow_extra_fields', true); } }
図2-9に示すように、これでフォームのバリデーションを行うことができます。
図2-9 - allow_extra_fields に true がセットされているバリデーション

よく見ると、フォームが有効であるにも関わらず、どんな値を投稿しても、サンキューページ内で name フィールドの値が空になっています。実際、$this->form->getValues() で返される配列に値は設定されていません。allow_extra_fields オプションを無効にすることで、バリデータが存在していないことをエラーは発生しなくなりますが、デフォルトでは、filter_extra_fields オプションに true がセットされており、バリデータが存在しない値をフィルタリングして、バリデーションずみの値から削除します。リスト2-10で示すように、もちろんこのふるまいを変更することもできです。
リスト2-10 - filter_extra_fields による保護を無効にする
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )); $this->validatorSchema->setOption('allow_extra_fields', true); $this->validatorSchema->setOption('filter_extra_fields', false); } }
これでフォームのバリデーションを行い、サンキューページで入力された値を読み取ることができるようになります。
4章では、フォームの値から Propel オブジェクトを安全にシリアライズするために、これらの保護を利用できることを説明します。
ロジカルバリデータ
ロジカルバリデータ (論理バリデータ) を利用すると、いくつかのバリデータを単独のフィールドに対して定義できます。
sfValidatorAnd: フィールドがすべてのバリデーションをパスすると有効になりますsfValidatorOr: フィールドが少なくとも1つのバリデーションをパスすると有効になります
ロジカルバリデータのコンストラクタはバリデータのリストを第1引数にとります。リスト2-11では、同時に満たす必要がある2つのバリデータを name フィールドに関連づけるために、sfValidatorAndを使います。
リスト2-11 - sfValidatorAnd バリデータを使う
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( // ... 'name' => new sfValidatorAnd(array( new sfValidatorString(array('min_length' => 5)), new sfValidatorRegex(array('pattern' => '/[\w- ]+/')), )), )); } }
フォームを投稿するとき、name フィールドの入力データは少なくとも5文字で構成され、かつ、正規表現 ([\w- ]+) にマッチする必要があります。
ロジカルバリデータはそれ自身がバリデータなので、リスト2-12で示されるように、高度な論理式を定義するために組み合わせることができます。
リスト2-12 - 複数のロジカルバリデータを組み合わせる
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( // ... 'name' => new sfValidatorOr(array( new sfValidatorAnd(array( new sfValidatorString(array('min_length' => 5)), new sfValidatorRegex(array('pattern' => '/[\w- ]+/')), )), new sfValidatorEmail(), )), )); } }
グローバルバリデータ
これまで説明したそれぞれのバリデータは、特定のフィールドに結びつけられ一度に1つの値のバリデーションのみを行います。デフォルトでは、フィールドのバリデーションはユーザーによって投稿された別のフィールドのデータに関係しませんが、コンテキストに依存したり、多くの別のフィールドの値に依存してバリデーションを行う場合もあります。たとえば、2つのパスワードが同じでなければならないときや、開始日が終了日より前の日付でなければならないときに、グローバルバリデータが必要となります。
これら両方の場合において、それぞれのコンテキストでユーザー入力のバリデーションを行うためにグローバルバリデータを使わなければなりません。プレバリデータもしくはポストバリデータをそれぞれ使うことで、個別のフィールドバリデーションの前または後にグローバルバリデータが処理されるように設定できます。通常はポストバリデータを使うことをおすすめします。データはすでにバリデーションとクリーンアップが行われている、つまり標準化されたフォーマットになっているからです。リスト2-13では、sfValidatorSchemaCompare バリデータを利用して2つのパスワードの比較を実装する方法を示しています。
リスト2-13 - sfValidatorSchemaCompare バリデータを使う
$this->validatorSchema->setPostValidator(new sfValidatorSchemaCompare('password', sfValidatorSchemaCompare::EQUAL, 'password_again'));
symfony 1.3/1.4 に関して、sfValidatorSchemaCompare クラスの定数の代わりに「自然な」 PHP 演算子も使うことができます。上記のコードは下記のコードと同等です。
$this->validatorSchema->setPostValidator(new sfValidatorSchemaCompare('password', '==', 'password_again'));
tip
sfValidatorSchemaCompare クラスは sfValidatorSchema クラスを継承します。ほかのグローバルバリデータも同様です。sfValidatorSchema はそれ自身がグローバルバリデータで、別のバリデータにそれぞれのフィールドのバリデーションを処理させることで、ユーザーの入力データ全体のバリデーションを行います。
リスト2-14では、開始日が終了日より前の日付であることのバリデーションを行う1つのバリデータの使い方と、エラーメッセージのカスタマイズを示しています。
リスト2-14 - sfValidatorSchemaCompare バリデータを使う
$this->validatorSchema->setPostValidator( new sfValidatorSchemaCompare('start_date', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'end_date', array(), array('invalid' => 'The start date ("%left_field%") must be before the end date ("%right_field%")') ) );
ポストバリデータを利用することで、2つの日付の比較が正確になることが保証されます。入力に使われる日付フォーマットに関わらず、start_date と end_date フィールドのバリデーションは比較可能なフォーマットの値に変換されます (デフォルトでは Y-m-d H:i:s)。
デフォルトでは、プレバリデータとポストバリデータは、フォームに対してグローバルエラーを返します。とは言うものの、エラーを特定のフィールドに関連づけたい場合もあります。たとえば、sfValidatorSchemaCompare バリデータの throw_global_error オプションを使うと、グローバルエラー (図2-10) にするか、またはエラーを1番目のフィールドに関連づける (図2-11) かを選ぶことができます。リスト2-15では、throw_global_error オプションの使い方を示しています。
リスト2-15 - throw_global_error オプションを使う
$this->validatorSchema->setPostValidator( new sfValidatorSchemaCompare('start_date', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'end_date', array('throw_global_error' => true), array('invalid' => 'The start date ("%left_field%") must be before the end date ("%right_field%")') ) );
図2-10 - グローバルバリデータのグローバルエラー

図2-11 - グローバルバリデータのローカルエラー

最後に、ロジカルバリデータを使うことで、リスト2-16で示されるようにポストバリデータを結合することもできます。
リスト2-16 - ロジカルバリデータで複数のポストバリデータを統合する
$this->validatorSchema->setPostValidator(new sfValidatorAnd(array( new sfValidatorSchemaCompare('start_date', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'end_date'), new sfValidatorSchemaCompare('password', sfValidatorSchemaCompare::EQUAL, 'password_again'), )));
ファイルのアップロード
PHP でファイルを扱う作業には、すべての Web 指向のプログラミング言語のように、HTML コードとサーバーサイドのファイル読み込みの両方が含まれます。この節では開発者の生活を楽にするためにフォームフレームワークが提供するツールを見ます。共通の罠に陥るのを回避する方法も見ることになります。
メッセージにファイルを添付できるように問い合わせフォームを変更してみましょう。これを実現するには、リスト2-17ファイルで示されるように file フィールドを追加します。
リスト2-17 - ContactForm フォームに file フィールドを追加する
// lib/form/ContactForm.class.php 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(), 'file' => new sfWidgetFormInputTextFile(), )); $this->widgetSchema->setNameFormat('contact[%s]'); $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), 'file' => new sfValidatorFile(), )); } }
ファイルをアップロードすることを許可するフォーム内に sfWidgetFormInputTextFile ウィジェットが存在している場合、リスト2-18で示されるように form タグに enctype 属性を 追加する必要があります。
リスト2-18 - file フィールドを考慮に入れるためにテンプレートを修正する
<form action="<?php echo url_for('contact/index') ?>" method="POST" enctype="multipart/form-data"> <table> <?php echo $form ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
note
フォームに関連したテンプレートを動的に生成する際に enctype 属性が必要なフォームの場合、フォームオブジェクトの isMultipart() メソッドは true を返します。
PHP では、アップロードされたファイルに関する情報は投稿されたほかの値とは別に保存されます。リスト2-19で示されるように、この情報を第2引数として渡すように bind() メソッド呼び出しを修正する必要があります。
リスト2-19 - アップロードされたファイルを bind() メソッドに渡す
class contactActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->form = new ContactForm(); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('contact'), $request->getFiles('contact')); if ($this->form->isValid()) { $values = $this->form->getValues(); // この値で何かを行います。 // ... } } } public function executeThankyou() { } }
これでフォームはじゅうぶんに利用できますが、アップロードされたファイルをディスク上に保存するためにアクションを変更する必要もあります。この章の最初で説明したように、sfValidatorFile はアップロードされたファイルに関連する情報を sfValidatedFile オブジェクトに変換します。リスト2-20では、このオブジェクトを使用してファイルを web/uploads ディレクトリに保存する方法を示しています。
リスト2-20 - sfValidatedFile オブジェクトを使う
if ($this->form->isValid()) { $file = $this->form->getValue('file'); $filename = 'uploaded_'.sha1($file->getOriginalName()); $extension = $file->getExtension($file->getOriginalExtension()); $file->save(sfConfig::get('sf_upload_dir').'/'.$filename.$extension); // ... }
次の表は sfValidatedFile オブジェクトのすべてのメソッドの一覧です。
| メソッド | 説明 |
|---|---|
| save() | アップロードされたファイルを保存します |
| isSaved() | ファイルが保存された場合は true を返します |
| getSavedName() | 保存されたファイルの名前を返します |
| getExtension() | MIME Type にしたがってファイルの拡張子を返します |
| getOriginalName() | アップロードされたファイルの名前を返します |
| getOriginalExtension() | アップロードされたファイルの拡張子を返します |
| getTempName() | 一時ファイルのパスを返します |
| getType() | ファイルの MIME Type を返します |
| getSize() | ファイルのサイズを返します |
tip
ファイルのアップロードにおいて、ブラウザから提供された MIME Type は信頼できません。セキュリティを最大限に保証するために、ファイルのバリデーション処理において、finfo_open と mime_content_type 関数、file ツールが順番に使われます。最後の方法として、どの関数も MIME Type を推測できない場合、もしくはシステムがこれらを提供しない場合、ブラウザの MIME Type が考慮に入れられます。MIME Type を推測する関数を追加または変更したい場合、sfValidatorFile コンストラクタで mime_type_guessers オプションを指定します。
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.