symfony book 日本語ドキュメント

第10章 - フォーム

Caution この章ではsymfony 1.0でフォームを実装する方法を説明しています。互換性とadminジェネレーターの機能はまだこのシステムを利用するので、symfony 1.1でもこの章の情報も価値があります。しかしながら、symfony 1.1で新しいプロジェクトを始めるのあれば、新しいフォームフレームワークの詳細について"symfony Forms in Action"の本も読むべきです。

テンプレートを書くとき、開発者の多くの時間はフォーム(form)に費やされます。このことにもかかわらず、一般的にフォームは貧弱に設計されます。デフォルト値、整形、検証、再投入と一般的なフォームの扱いなど多くのことに注意を払うことが必要なので、開発者のなかにはプロセスにおけるいくつかの重要な詳細事項を大まかにしか見ない人がいます。というわけで、symfonyはこのトピックに対して特別な注意を払うことにします。この章ではフォームの開発速度を上げる一方で、これらの多くの要件を自動化するツールについて説明します。

フォームヘルパー

テンプレートによって、フォーム要素のHTMLタグがPHPコードに混ざることはよくあることです。symfonyのフォームヘルパーはこのタスクをシンプルにして<input>タグのあいだで<?php echoのタグを繰り返し展開することを避けることを目的としています。

メインのフォームタグ

以前の章で説明したように、フォームを作成するにはform_tag()ヘルパーを使わなければなりません。このヘルパーはパラメーターとして渡されたアクションをルーティングURLに変換するからです。2番目の引数は追加オプションをサポートします。たとえば、デフォルトのmethodを変更するには、デフォルトのenctypeを変更するか、もしくはほかの属性を指定します。リスト10-1は例を示しています。

リスト10-1 - form_tag()ヘルパー

[php]
<?php echo form_tag('test/save') ?>
 => <form method="post" action="/path/to/save">

<?php echo form_tag('test/save', 'method=get multipart=true class=simpleForm') ?>
 => <form method="get" enctype="multipart/form-data" class="simpleForm"action="/path/to/save">

フォームヘルパーを閉じる必要がないので、ソースコードのなかできれいに見えなくても、HTMLの</form>を使うべきです。

標準のフォーム要素

フォームヘルパーによって、フォームのなかのそれぞれの要素はデフォルトでname属性から推測されるid属性に渡されます。これは便利なだけの規約ではありません。標準のフォームヘルパーとそれらのオプションの全リストに関してはリスト10-2をご覧ください。

リスト10-2 - 標準のフォームヘルパーの構文

[php]
// テキストフィールド(入力)
<?php echo input_tag('name', 'default value') ?>
 => <input type="text" name="name" id="name" value="default value" />

// すべてのフォームヘルパーは追加オプションのパラメーターを受けとる
// これによってカスタム属性を生成タグに追加できる
<?php echo input_tag('name', 'default value', 'maxlength=20') ?>
 => <input type="text" name="name" id="name" value="default value" maxlength="20" />

// 長いテキストフィールド(テキストエリア)
<?php echo textarea_tag('name', 'default content', 'size=10x20') ?>
 => <textarea name="name" id="name" cols="10" rows="20">
      default content
    </textarea>

// チェックボックス
<?php echo checkbox_tag('single', 1, true) ?>
<?php echo checkbox_tag('driverslicense', 'B', false) ?>
 => <input type="checkbox" name="single" id="single" value="1" checked="checked" />
    <input type="checkbox" name="driverslicense" id="driverslicense" value="B" />

// ラジオボタン
<?php echo radiobutton_tag('status[]', 'value1', true) ?>
<?php echo radiobutton_tag('status[]', 'value2', false) ?>
 => <input type="radio" name="status[]" id="status_value1" value="value1" checked="checked" />
    <input type="radio" name="status[]" id="status_value2" value="value2" />

// ドロップダウンリスト(選択)
<?php echo select_tag('payment',
  '<option selected="selected">Visa</option>
   <option>Eurocard</option>
   <option>Mastercard</option>')
?>
 => <select name="payment" id="payment">
      <option selected="selected">Visa</option>
      <option>Eurocard</option>
      <option>Mastercard</option>
    </select>

// 選択タグのためのオプションのリスト
<?php echo options_for_select(array('Visa', 'Eurocard', 'Mastercard'), 0) ?>
 => <option value="0" selected="selected">Visa</option>
    <option value="1">Eurocard</option>
    <option value="2">Mastercard</option>

// オプションのリストと結びつけられたドロップダウンヘルパーオプションのリスト
<?php echo select_tag('payment', options_for_select(array(
  'Visa',
  'Eurocard',
  'Mastercard'
), 0)) ?>
 => <select name="payment" id="payment">
      <option value="0" selected="selected">Visa</option>
      <option value="1">Eurocard</option>
      <option value="2">Mastercard</option>
    </select>

// オプション名を指定するため、連想配列を使う
<?php echo select_tag('name', options_for_select(array(
  'Steve'  => 'Steve',
  'Bob'    => 'Bob',
  'Albert' => 'Albert',
  'Ian'    => 'Ian',
  'Buck'   => 'Buck'
), 'Ian')) ?>
 => <select name="name" id="name">
      <option value="Steve">Steve</option>
      <option value="Bob">Bob</option>
      <option value="Albert">Albert</option>
      <option value="Ian" selected="selected">Ian</option>
      <option value="Buck">Buck</option>
    </select>

// 複数の選択を持つドロップダウンリスト(選択された値は配列が可能)
<?php echo select_tag('payment', options_for_select(
  array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'),
  array('Visa', 'Mastercard'),
), array('multiple' => true))) ?>

 => <select name="payment[]" id="payment" multiple="multiple">
      <option value="Visa" selected="selected">Visa</option>
      <option value="Eurocard">Eurocard</option>
      <option value="Mastercard">Mastercard</option>
    </select>
// 複数の選択を持つドロップダウンリスト(選択された値は配列が可能)
<?php echo select_tag('payment', options_for_select(
  array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'),
  array('Visa', 'Mastercard')
), 'multiple=multiple') ?>
 => <select name="payment[]" id="payment" multiple="multiple">
      <option value="Visa" selected="selected">Visa</option>
      <option value="Eurocard">Eurocard</option>
      <option value="Mastercard" selected="selected">Mastercard</option>
    </select>

// アップロードファイルフィールド
<?php echo input_file_tag('name') ?>
 => <input type="file" name="name" id="name" value="" />

// パスワードフィールド
<?php echo input_password_tag('name', 'value') ?>
 => <input type="password" name="name" id="name" value="value" />

// 隠しフィールド
<?php echo input_hidden_tag('name', 'value') ?>
 => <input type="hidden" name="name" id="name" value="value" />

// 投稿ボタン (テキストとして)
<?php echo submit_tag('Save') ?>
 => <input type="submit" name="submit" value="Save" />

// 投稿ボタン (画像として)
<?php echo submit_image_tag('submit_img') ?>
 => <input type="image" name="submit" src="/images/submit_img.png" />

submit_image_tag()ヘルパーは同じ構文を使いimage_tag()と同じ利点を持ちます。

NOTE ラジオボタンに関して、デフォルトではid属性はname属性の値ではなく名前と値の組み合わせに設定されます。自動化された"別のものを選択したときに以前の選択を解除する"機能を手に入れるために同じ名前を持ついくつかのラジオボタンのタグを用意する必要があるからで、id=nameの慣習によればページのなかでid属性を持つHTMLタグが存在することを意味します。これは厳密には禁止されています。

-

SIDEBAR フォーム投稿の扱いかた

フォームを通してユーザーによって投稿されたデータをどのように読みとりますか?リクエストパラメーターで利用できるので、アクションは値を取得するために$request->getParameter($elementName)を呼び出すことが必要なだけです。

フォームを表示して処理するためのよい習慣は同じアクションを使うことです。リクエストメソッド(GETもしくはPOST)に従い、フォームのテンプレートが呼び出されるもしくはフォームが処理されるとリクエストは別のアクションにリダイレクトされます。

[php]
// mymodule/actions/actions.class.phpにおいて
public function executeEditAuthor($request)
{
  if (!$this->getRequest()->isMethod('post'))
  {
    // フォームを表示する
    return sfView::SUCCESS;
  }
  else
  {
    // フォーム投稿を扱う
    $name = $request->getParameter('name');
    ...
    $this->redirect('mymodule/anotheraction');
  }
}

これを機能させるには、フォームのターゲットは表示するアクションと同じアクションでなければなりません。

[php]
// mymodule/templates/editAuthorSuccess.phpにおいて
<?php echo form_tag('mymodule/editAuthor') ?>

...

symfonyは裏で非同期通信リクエストを行うために専用のフォームヘルパーを提供します。つぎの章では、Ajaxについて焦点を当てて、詳細な説明を行います。

日付入力ウィジェット

フォームは日付を読みとるためによく使われます。誤った書式の日付はフォーム投稿が失敗する主な原因です。図10-1で示されるように、richオプションをtrueに設定する場合、input_date_tag()ヘルパーはインタラクティブなJavaScriptカレンダーのなかで入力するユーザーを助けます。

図10-1 - リッチな日付入力タグ

リッチな日付入力タグ

richオプションが省略された場合、月、日にち、年の範囲で投入されたヘルパーは3つの<select>タグを出力します。これらのヘルパー(select_day_tag()select_month_tag()select_year_tag())を呼び出すことでこれらのドロップダウンを個別に表示できます。これらの要素のデフォルト値は現在の日にち、月、年です。リスト10-3は日付入力ヘルパーを示してます。

リスト10-3 - 日付入力ヘルパー

[php]
<?php echo input_date_tag('dateofbirth', '2005-05-03', 'rich=true') ?>
 => a text input tag together with a calendar widget

// つぎのヘルパーはDateFormヘルパーグループを必要とする
<?php use_helper('DateForm') ?>

<?php echo select_day_tag('day', 1, 'include_custom=日にちを選択する') ?>
=> <select name="day" id="day">
      <option value="">日にちを選択する</option>
      <option value="1" selected="selected">01</option>
      <option value="2">02</option>
      ...
      <option value="31">31</option>
    </select>

<?php echo select_month_tag('month', 1, 'include_custom=月を選択する use_short_month=true') ?>
=> <select name="month" id="month">
      <option value="">月を選択する</option>
      <option value="1" selected="selected">Jan</option>
      <option value="2">Feb</option>
      ...
      <option value="12">Dec</option>
    </select>

<?php echo select_year_tag('year', 2007, 'include_custom=年を選択する year_end=2010') ?>
 => <select name="year" id="year">
      <option value="">年を選択する</option>
      <option value="2006">2006</option>
      <option value="2007" selected="selected">2007</option>
      ...
    </select>

input_date_tag()ヘルパーが受け入れる日付の値はPHPのstrtotime()関数によって認識されるものです。リスト10-4は利用できる書式を示し、リスト10-5は避けなければならない書式を示しています。

リスト10-4 - 日付ヘルパーで受け入れられる日付書式

[php]
// 立派に動作する
<?php echo input_date_tag('test', '2006-04-01', 'rich=true') ?>
<?php echo input_date_tag('test', 1143884373, 'rich=true') ?>
<?php echo input_date_tag('test', 'now', 'rich=true') ?>
<?php echo input_date_tag('test', '23 October 2005', 'rich=true') ?>
<?php echo input_date_tag('test', 'next tuesday', 'rich=true') ?>
<?php echo input_date_tag('test', '1 week 2 days 4 hours 2 seconds', 'rich=true') ?>

// nullを返す
<?php echo input_date_tag('test', null, 'rich=true') ?>
<?php echo input_date_tag('test', '', 'rich=true') ?>

リスト10-5 - 日付ヘルパーで無効な日付書式

[php]
// 日付 zero = 01/01/1970
<?php echo input_date_tag('test', 0, 'rich=true') ?>

// 英語ではない日付書式は機能しない
<?php echo input_date_tag('test', '01/04/2006', 'rich=true') ?>

リッチなテキスト編集機能

TinyMCEとFCKEditorウィジェットの統合機能のおかげで、<textarea>タグでもリッチなテキスト編集機能を利用できます。図10-2で示されるように、これらはテキストを太文字、イタリック体、そのほかのスタイルとして整形するためのボタンつきのワードプロセッサのようなインターフェイスを提供します。

図10-2 - リッチなテキスト編集機能

リッチなテキスト編集機能

両方のウィジェットとも手動でインストールする作業が必要です。2つのウィジェットのインストールの手続きは同じなので、ここではTinyMCEのリッチなテキスト編集機能だけを説明します。プロジェクトのWebサイト(http://tinymce.moxiecode.com/)からエディタをダウンロードし、一時フォルダーで解凍する必要があります。リスト10-6で示されるように、tinymce/jscripts/tiny_mce/ディレクトリをプロジェクトのweb/js/ディレクトリにコピーし、settings.ymlのなかでライブラリへのパスを定義します。

リスト10-6 - TinyMCEライブラリのパスをセットアップする

all:
  .settings:
    rich_text_js_dir:  js/tiny_mce

セットアップが終了したら、てテキストエリアのリッチなテキスト編集機能を利用できるようにrich=trueオプションを追加して切り替えます。tinymce_optionsオプションを使うことでJavaScriptエディタに対してカスタムオプションを指定することもできます。リスト10-7を示してください。

リスト10-7 - リッチなテキストエリア

[php]
<?php echo textarea_tag('name', 'default content', 'rich=true size=10x20') ?>
 => a rich text edit zone powered by TinyMCE
<?php echo textarea_tag('name', 'default content', 'rich=true size=10x20 tinymce_options=language:"fr",theme_advanced_buttons2:"separator"') ?>
=> a rich text edit zone powered by TinyMCE with custom parameters

国、言語と通貨の選択

国の選択フィールドを表示する必要がある場合を考えます。しかし国の名前はすべての言語とは同じではないので国のドロップダウンリストのオプションはユーザーのcultureにしたがって変わります(cultureに関する詳細な情報は13章を参照)。リスト10-8で示されるように、select_country_tag()ヘルパーがあなたの代わりにすべてを行います: これは国の名前を国際化し、ISOの標準国コードを値として使います。

リスト10-8 - 国のタグヘルパーを選ぶ

[php]
<?php echo select_country_tag('country', 'AL') ?>
 => <select name="country" id="country">
      <option value="AF">アフガニスタン</option>
      <option value="AL" selected="selected">アルバニア</option>
      <option value="DZ">アルジェリア</option>
      <option value="AS">アメリカ領サモア</option>
  ...

select_country_tag()ヘルパーと同じように、selext_language_tag()ヘルパーはリスト10-9で示されるような言語のリストを表示します。

リスト10-9 - 言語のタグヘルパーを選ぶ

[php]
<?php echo select_language_tag('language', 'en') ?>
 => <select name="language" id="language">
      ...
      <option value="elx">エラム語</option>
      <option value="en" selected="selected">英語</option>
      <option value="enm">中英語(1100-1500)</option>
      <option value="ang">古英語(ca.450-1100)</option>
      <option value="myv">エルジャ語</option>
      <option value="eo">エスペラント語</option>
      ...

3番目のヘルパーはselect_currency_tag()ヘルパーで、リスト10-10のような、通貨の一覧を表示します。

リスト10-10 - 通貨タグヘルパーを選ぶ

[php]
<?php echo select_currency_tag('currency', 'EUR') ?>
 => <select name="currency" id="currency">
      ...
      <option value="ETB">エチオピアビル</option>
      <option value="ETD">エチオピアドル</option>
      <option value="EUR" selected="selected">ユーロ</option>
      <option value="XBA">欧州複合単位(EURCO)</option>
      <option value="XEU">欧州通貨単位</option>
      ...

NOTE 3つのすべてのヘルパーはオプションの配列である3番目の引数を受けとります。これは表示されるオプションを特定のセット: array('countries' => array ('FR', 'DE'))のように制限できます。select_language_tag()に対するオプションの名前はlanguagesselect_currency_tag()に対するオプションの名前はcurrenciesです。 たいていの場合 集合を既知でサポートされる値のリスト、とりわけ期限切れの項目を含む可能性があるリスト、に制限することに意味があります。

select_currency_tag()はオプションに表示されるものに影響を与えるdisplayという名前の追加オプションを提供します。これはsymbolcodeもしくはnameの1つに設定できます。

オブジェクトのためのフォームヘルパー

フォームの要素がオブジェクトのプロパティを編集するために使われるとき、標準のリンクヘルパーを書く作業が退屈になることがあります。たとえば、Customerオブジェクトのtelephone属性を編集するには、つぎのように書くことになります:

[php]
<?php echo input_tag('telephone', $customer->getTelephone()) ?>
=> <input type="text" name="telephone" id="telephone" value="0123456789" />

属性の名前を繰り返すことを避けるために、symfonyはそれぞれのフォームヘルパーに対して代わりのオブジェクトフォームヘルパーを提供します。オブジェクトフォームヘルパーはオブジェクトとメソッドの名前からフォーム要素の名前とデフォルト値を推測します。以前のinput_tag()はつぎのコードと同等です:

[php]
<?php echo object_input_tag($customer, 'getTelephone') ?>
=> <input type="text" name="telephone" id="telephone" value="0123456789" />

object_input_tag()に関して簡潔さは重要ではないかもしれません。しかしながら、すべての標準のフォームヘルパーは対応するオブジェクトフォームヘルパーを持ち、これらはすべて同じ構文を共有します。これはフォームの生成作業をとても楽にします。scaffoldingと生成されたadministration(14章を参照)でオブジェクトフォームヘルパーが広範囲に使われる理由はそういうわけです。リスト10-11はヘルパーからのオブジェクトのリストです。

リスト10-11 - オブジェクトフォームヘルパーの構文

[php]
<?php echo object_input_tag($object, $method, $options) ?>
<?php echo object_input_date_tag($object, $method, $options) ?>
<?php echo object_input_hidden_tag($object, $method, $options) ?>
<?php echo object_textarea_tag($object, $method, $options) ?>
<?php echo object_checkbox_tag($object, $method, $options) ?>
<?php echo object_select_tag($object, $method, $options) ?>
<?php echo object_select_country_tag($object, $method, $options) ?>
<?php echo object_select_language_tag($object, $method, $options) ?>

object_password_tag()ヘルパーは存在しません。ユーザーが以前入力したものに基づいて、デフォルト値をパスワードのタグに渡すことはよくない習慣だからです。

CAUTION 通常のフォームヘルパーとは異なり、use_helper('Object')を持つテンプレート内でObjectヘルパーグループの使用を明示的に宣言した場合のみオブジェクトフォームヘルパーは利用できます。

すべてのオブジェクトフォームヘルパーのなかでもっとも面白いのはドロップダウンリストに関連するobjects_for_select()object_select_tag()です。

ドロップダウンをオブジェクトで投入する

options_for_select()ヘルパーは、ほかの標準ヘルパーで以前説明されたものですが、リスト10-12で示されるように、PHPの連想配列をオプションのリストに変換します。

リスト10-12 - options_for_select()を利用して配列に基づくオプションのリストを作成する

[php]
<?php echo options_for_select(array(
  '1' => 'Steve',
  '2' => 'Bob',
  '3' => 'Albert',
  '4' => 'Ian',
  '5' => 'Buck'
), 4) ?>
 => <option value="1">Steve</option>
    <option value="2">Bob</option>
    <option value="3">Albert</option>
    <option value="4" selected="selected">Ian</option>
    <option value="5">Buck</option>

Propelのクエリの結果から得られた、Authorクラスのオブジェクトの配列がすでに存在することを前提とします。この配列に基づくオプションのリストを作りたい場合、リスト10-13で示されるように、それぞれのオブジェクトのidnameを読みとるためにループする必要があります。

リスト10-13 - options_for_select()を利用してオブジェクトの配列に基づくオプションのリストを作成する

[php]
// アクションのなかで
$options = array();
foreach ($authors as $author)
{
  $options[$author->getId()] = $author->getName();
}
$this->options = $options;

// テンプレートのなかで
<?php echo options_for_select($options, 4) ?>

この種の処理作業はよく行われるので、symfonyはこの作業を自動化するヘルパーを用意します: objects_for_select()はオブジェクトの配列から直接オプションリストを作ります。ヘルパーは2つの追加パラメーターが必要です: valueを読みとるために使われるメソッド名と生成される<option>タグのテキスト内容です。リスト10-13はつぎのシンプルなフォームと同等です:

[php]
<?php echo objects_for_select($authors, 'getId', 'getName', 4) ?>

これはスマートで速く書けますが、外部キーのカラムを処理するとき、symfonyはさらに掘り下げます。

外部キーのカラムに基づいてドロップダウンリストを作成する

外部キーのカラムが取得できる値は外部テーブルのレコードの主キーの値です。たとえば、articleテーブルがauthorテーブルへの外部キーであるauthor_idカラムを持つ場合、このカラムで利用可能な値はauthorテーブルのすべてのレコードのidです。基本的には、記事の作者を編集するドロップダウンリストはリスト10-14のようになります。

リスト10-14 - objects_for_select()を利用して外部キーに基づくオプションのリストを作成する

[php]
<?php echo select_tag('author_id', objects_for_select(
  AuthorPeer::doSelect(new Criteria()),
  'getId',
  '__toString',
  $article->getAuthorId()
)) ?>
=> <select name="author_id" id="author_id">
      <option value="1">Steve</option>
      <option value="2">Bob</option>
      <option value="3">Albert</option>
      <option value="4" selected="selected">Ian</option>
      <option value="5">Buck</option>
    </select>

object_select_tag()は単独ですべてを行います。これは外部テーブルの可能なレコードの名前で投入されたドロップダウンリストを表示します。ヘルパーはスキーマから外部テーブルと外部カラムを推測できるので、構文はとても簡潔です。リスト10-13はつぎのコードと同等です:

[php]
<?php echo object_select_tag($article, 'getAuthorId') ?>

object_select_tag()ヘルパーはパラメーターとして渡されるメソッド名に基づいて関連するピアクラスの名前(この例ではAuthorPeer)を推測します。しかしながら、related_classオプションを3番目の引数に設定することで独自のクラスを指定できます。<option>タグのテキスト内容はレコード名で、オブジェクトクラスの__toString()メソッドの結果です($author->__toString()メソッドが未定義の場合、主キーが代わりに使われます)。加えて、オプションのリストは空の基準値を持つdoSelect()メソッドで構成されます; これは作成日順に並べられたすべてのレコードを返します。特定の順番でレコードの部分集合だけを表示したい場合、この選択をオブジェクトの配列として返すピアクラスのなかでメソッドを作り、これをpeer_methodオプションに設定します。最後に、include_blankinclude_customオプションを設定することでドロップダウンリストのトップに、空白のオプションもしくはカスタムオプションを追加できます。リスト10-15はobject_select_tag()ヘルパーに対するこれらの異なるオプションを示しています。

リスト10-15 - object_select_tag()ヘルパーのオプション

[php]
// 基本構文
<?php echo object_select_tag($article, 'getAuthorId') ?>
// AuthorPeer::doSelect(new Criteria())からリストを作成する

// 可能な値を読みとるために使われるピアクラスを変更する
<?php echo object_select_tag($article, 'getAuthorId', 'related_class=Foobar') ?>
// FoobarPeer::doSelect(new Criteria())からリストを作成する

// 可能な値を読みとるためにピアクラスを変更する
<?php echo object_select_tag($article, 'getAuthorId','peer_method=getMostFamousAuthors') ?>
// AuthorPeer::getMostFamousAuthors(new Criteria())からリストを作成する

// リストのトップで<option value="">&nbsp;</option>を追加する
<?php echo object_select_tag($article, 'getAuthorId', 'include_blank=true') ?>

// リストのトップで<option value="">Choose an author</option>を追加する
<?php echo object_select_tag($article, 'getAuthorId',
  'include_custom=Choose an author') ?>

オブジェクトを更新する

オブジェクトヘルパーを利用してオブジェクトのプロパティを編集するための専用フォームはアクションのなかで扱うよりも簡単です。たとえば、nameageaddress属性を持つAuthorクラスのオブジェクトがある場合、フォームはリスト10-16のように書きます。

リスト10-16 - オブジェクトヘルパーだけを持つフォーム

[php]
<?php echo form_tag('author/update') ?>
  <?php echo object_input_hidden_tag($author, 'getId') ?>
  名前: <?php echo object_input_tag($author, 'getName') ?><br />
  年齢:  <?php echo object_input_tag($author, 'getAge') ?><br />
  アドレス: <br />
         <?php echo object_textarea_tag($author, 'getAddress') ?>
</form>

リスト10-17で示されるように、フォームが投稿されたときに、呼び出されるauthorモジュールのupdateアクションはPropelによって生成されたfromArray()メソッドによって簡単にオブジェクトを更新できます。

リスト10-17 - オブジェクトフォームヘルパーに基づくフォーム投稿を扱う

[php]
public function executeUpdate($request)
{
  $author = AuthorPeer::retrieveByPk($request->getParameter('id'));
  $this->forward404Unless($author);

  $author->fromArray($this->getRequest()->getParameterHolder()->getAll(),BasePeer::TYPE_FIELDNAME);
  $author->save();

  return $this->redirect('/author/show?id='.$author->getId());
}

フォームのバリデーション

NOTE このセクションで説明されている機能はsymfony 1.1で廃止されsfCompat10プラグインを有効にした場合のみに動作します。

6章ではリクエストパラメーターをバリデートするためにアクションクラスでvalidateXXX()メソッドを使う方法を説明しました。しかしながら、フォーム投稿をバリデートするこのテクニックを利用する場合、同じコードの一部を何度も書き直すことになります。symfonyはアクションクラスのなかのPHPコードの代わりに、YAMLファイルだけに依存する、フォームのバリデーションの代替テクニックを提供します。

フォームのバリデーション機能を実証するために、最初にリスト10-18で示されているサンプルのフォームを考えてみましょう。nameemailage、とmessageフィールドを持つ、古典的な問い合わせフォームです。

リスト10-18 - 問い合わせフォームのサンプル(modules/contact/templates/indexSuccess.php)

[php]
<?php echo form_tag('contact/send') ?>
  名前:    <?php echo input_tag('name') ?><br />
  Eメール:   <?php echo input_tag('email') ?><br />
  年齢:     <?php echo input_tag('age') ?><br />
  メッセージ: <?php echo textarea_tag('message') ?><br />
  <?php echo submit_tag() ?>
</form>

フォームのバリデーションの原則はユーザーが不正なデータを入力しフォームに投稿した場合、つぎのページでエラーメッセージを表示することです。簡単な英語で、サンプルフォームに対して有効なデータを定義してみましょう:

問い合わせフォームのためにもっと複雑なバリデーションルールを定義することができますが、バリデーションの可能性を実証するにはこれで十分です。

NOTE フォームのバリデーションはサーバーサイドかつ/もしくはクライアントサイドで行われます。サーバーサイドのバリデーションは間違ったデータでデータベースが汚染されることを避けるために必須です。クライアントサイドのバリデーションはユーザーエクスペリエンスを大いに高めますが、オプションです。クライアントサイドのバリデーションはカスタムJavaScriptで行われます。

バリデーター

上記の例においてnameフィールドとemailフィールドが共通のバリデーションルールを共有していることがわかります。バリデーションルールのいくつかはWebフォームで頻繁に使われるのでsymfonyはこれらのバリデーターを実装するPHPコードにまとめます。バリデーター(validator)はexecute()メソッドを提供するシンプルなクラスです。このメソッドはパラメーターとしてフィールドの値を必要とし、値が有効な場合はtrueを、そうでなければfalseを返します。

symfonyはいくつかのバリデーターを搭載しています(この章の後のほうの"symfonyの標準バリデーター"で説明されます)が、今回はsfStringValidatorを重点的にとり組んでみましょう。このバリデーターは入力が文字列であり、サイズが2つの指定された文字数の間に収まっていることをチェックします(initialize()メソッドを呼び出すときに定義されます)。nameフィールドをバリデートするために求められるものがまさにこれです。リスト10-19はバリデーションメソッドでこのバリデーターを使う方法を示しています。

リスト10-19 - リクエストパラメーターを再利用可能なバリデーターでバリデートする(modules/contact/action/actions.class.php)

[php]
public function validateSend($request)
{
  $name = $request->getParameter('name');

  // nameフィールドが求められる
  if (!$name)
  {
    $this->getRequest()->setError('name', '名前のフィールドは空白であってはなりません');

    return false;
  }

  // nameフィールドは2文字から100文字の間のテキストエントリーでなければならない
  $myValidator = new sfStringValidator($this->getContext(), array(
    'min'       => 2,
    'min_error' => 'この名前は短すぎます(最小は2文字)',
    'max'       => 100,
    'max_error' => 'この名前は長すぎます(最大で100文字)',
  ));
  if (!$myValidator->execute($name, $error))
  {
    return false;
  }

  return true;
}

ユーザーがリスト10-18のフォームでnameフィールドのaの値を投稿する場合、sfStringValidatorexecute()メソッドはfalseを返します(文字列の最少の長さが2文字だからです)。validateSend()メソッドが失敗し、executeSend()メソッドの代わりにhandleErrorSend()メソッドが呼び出されます。

TIP sfRequestオブジェクトのsetError()メソッドはテンプレートに情報を渡すので、テンプレートはエラーメッセージを表示できます(この章の後の"エラーメッセージをフォームに表示する"のセクションで説明されます)。バリデーターはエラーを内部で設定するので、異なるバリデーションを行わない場合のために異なるエラーを定義できます。これがsfStringValidatormin_errormax_error初期化パラメーターの目的です。

上記の例で定義されたすべてのルールはバリデーターに翻訳できます:

フィールドが求められるという事実はバリデーターによって処理されません。

バリデーションファイル

validateSend()メソッドのバリデーターで問い合わせフォームのバリデーションを簡単に実装できますが、多くのコードを繰り返すことを意味します。symfonyはフォームのためにバリデーションルールを定義するYAMLを使う代替方法を提供します。たとえば、リスト10-20はnameフィールドのバリデーションルールの翻訳を示し、その結果はリスト10-19のものと同等です。

リスト10-20 - バリデーションファイル(modules/contact/validate/send.yml)

fields:
  name:
    required:
      msg:       名前のフィールドは空白であってはなりません
    sfStringValidator:
      min:       2
      min_error: この名前は短すぎます。(最小で2文字)
      max:       100
      max_error: この名前は長すぎます。(最大で100文字)

バリデーションファイルにおいて、fieldsヘッダーは、必要であればバリデートされるフィールド、と値が存在するときにテストされるべきバリデーターの一覧を表示します。それぞれのバリデーターの値はバリデーターを手動で初期化するときに使うものと同じです。フィールドは必要なバリデーターの数だけバリデートできます。

NOTE バリデーターが失敗するときはバリデーションプロセスは停止しません。symfonyはすべてのバリデーターをテストし、少なくともそれらの1つが失敗した場合にバリデーションが失敗したことを宣言します。バリデーションファイルのルールのいくつかが失敗した場合でも、symfonyはまだvalidateXXX()メソッドを探しそれを実行します。2つのバリデーションのテクニックはお互いを補い合っています。利点は複数の失敗をともなうフォームにおいて、すべてのエラーメッセージが表示されることです。

バリデーションファイルはモジュールのvalidate/ディレクトリに設置され、これらがバリデートしなければならないアクションの名前を取って名づけられます。たとえばリスト10-19はvalidate/send.ymlという名前のファイルに保存されなければなりません。

フォームを再表示する

デフォルトでは、symfonyはバリデーションの処理が失敗するときはいつもアクションクラスのhandleErrorSend()メソッドを探す、もしくはメソッドが存在しない場合はsendError.phpテンプレートを表示します。

失敗したバリデーションをユーザーに知らせる通常の方法はエラーメッセージをともなうフォームを再表示することです。この目的のには、リスト10-21で示されるように、handleErrorSend()メソッドをオーバーライドし、フォームを表示するアクション(たとえば、module/index)へのリダイレクトで終わらせる必要があります。

リスト10-21 - フォームを再表示する(modules/contact/actions/actions.class.php)

[php]
class ContactActions extends sfActions
{
  public function executeIndex()
  {
    //フォームを表示する
  }

  public function handleErrorSend()
  {
    $this->forward('contact', 'index');
  }

  public function executeSend()
  {
    //フォーム投稿を処理する
  }
}

フォームを表示してフォーム投稿を処理するために同じアクションを選ぶ場合、リスト10-22で示されるように、handleErrorSend()メソッドはsendSuccess.phpからフォームを再表示するために、単にsfView::SUCCESSを返します。

リスト10-22 - フォームを表示して処理する単独のアクション(modules/contact/actions/actions.class.php)

[php]
class ContactActions extends sfActions
{
  public function executeSend()
  {
    if (!$this->getRequest()->isMethod('post'))
    {
      // テンプレートのためのデータを表示する

      // フォームを表示する
      return sfView::SUCCESS;
    }
    else
    {
      // 投稿を処理する
      ...
      $this->redirect('mymodule/anotheraction');
    }
  }
  public function handleErrorSend()
  {
    // テンプレートに対してデータを表示する

    // フォームを表示する
    return sfView::SUCCESS;
  }
}

executeSend()メソッドとhandleErrorSend()メソッドのなかで繰り返すことを避けるために、データを用意する必要のあるロジックをアクションクラスのprotectedとして定義されたメソッドにリファクタリングすることができます。

新しい設定によって、ユーザーが無効な名前を入力した場合、フォームは再び表示されますが、入力されたデータは失われ、エラーメッセージは失敗の理由を表示しません。最後の問題を解決するには、誤ったフィールドに近いエラーメッセージを挿入するために、フォームを表示するテンプレートを修正しなければなりません。

フォームのなかでエラーメッセージを表示する

バリデーターパラメーターとして定義されたエラーメッセージはフィールドがバリデーションを失敗したときにリクエストに追加されます(リスト10-18で示されるように、setError()メソッドを用いてエラーを手動で追加できる)。sfRequestオブジェクトはエラーメッセージを読みとるために2つの便利なメソッド: hasError()getError()を提供します。それぞれのメソッドはパラメーターとしてフィールドの名前を必要とします。加えて、1つもしくは多くのフォールドが無効なデータを含むという事実が注目されるようにhasErrors()メソッドでアラートをフォームのトップに表示できます。リスト10-23と10-24はこれらのメソッドの使いかたを示しています。

リスト10-23 - フォームのトップでエラーメッセージを表示する(templates/indexSuccess.php)

[php]
<?php if ($sf_request->hasErrors()): ?>
  <p>あなたが入力したデータは無効のようです。
  つぎのエラーを修正して再投稿して下さるようお願いします:</p>
  <ul>
  <?php foreach($sf_request->getErrors() as $name => $error): ?>
    <li><?php echo $name ?>: <?php echo $error ?></li>
  <?php endforeach; ?>
  </ul>
<?php endif; ?>

リスト10-24 - フォーム内部でエラーメッセージを表示する(templates/indexSuccess.php)

[php]
<?php echo form_tag('contact/send') ?>
  <?php if ($sf_request->hasError('name')): ?>
    <?php echo $sf_request->getError('name') ?> <br />
  <?php endif; ?>
  Name:    <?php echo input_tag('name') ?><br />
  ...
  <?php echo submit_tag() ?>
</form>

リスト10-23のgetError()メソッドを使用して条件文を書くのは少し冗長です。Validationヘルパーグループの使用を宣言することを前提とすると、symfonyがgetError()メソッドを置き換えるためにform_error()ヘルパーを提供する理由はそういうわけです。リスト10-25はこのヘルパーを使うことでリスト10-24を置き換えています。

リスト10-25 - 省略記法でフォーム内部からエラーメッセージを表示する

[php]
<?php use_helper('Validation') ?>
<?php echo form_tag('contact/send') ?>

           <?php echo form_error('name') ?><br />
  名前:    <?php echo input_tag('name') ?><br />
  ...
  <?php echo submit_tag() ?>
</form>

form_error()ヘルパーはメッセージをより見やすくするためにそれぞれのエラーメッセージの前後に特別な文字を追加します。デフォルトでは、文字は下を指し示す矢印(&darr;エンティティに対応)ですが、settings.ymlファイルで変更できます:

all:
  .settings:
    validation_error_prefix:    ' &darr;&nbsp;'
    validation_error_suffix:    ' &nbsp;&darr;'

バリデーションを失敗した場合、フォームはエラーを正しく表示しますが、ユーザーによって入力されたデータは失われます。本当にユーザーフレンドリーにするにはフォームを再投入する必要があります。

フォームを再投入する

エラー処理はforward()メソッドを通して行われるので(リスト10-21で示される)、オリジナルのリクエストはまだアクセス可能で、ユーザーによって入力されたデータはリクエストパラメーターのなかに存在します。リスト10-26で示されるように、デフォルト値をそれぞれのフィールドに追加することでフォームを再投入できます。

リスト10-26 - バリデーションが失敗したときにフォームを再投入するためにデフォルト値を設定する(templates/indexSuccess.php)

[php]
<?php use_helper('Validation') ?>
<?php echo form_tag('contact/send') ?>
           <?php echo form_error('name') ?><br />
  名前:    <?php echo input_tag('name', $sf_params->get('name')) ?><br />
           <?php echo form_error('email') ?><br />
  Eメール:   <?php echo input_tag('email', $sf_params->get('email')) ?><br />
           <?php echo form_error('age') ?><br />
  年齢:     <?php echo input_tag('age', $sf_params->get('age')) ?><br />
           <?php echo form_error('message') ?><br />
  メッセージ: <?php echo textarea_tag('message', $sf_params->get('message')) ?><br />
  <?php echo submit_tag() ?>
</form>

繰り返しますが、これを書く作業はとても退屈です。YAMLバリデーションファイルにおいて、symfonyは要素のデフォルト値を変更せずにフォームのすべてのフィールドを再投入する代替方法を提供します。フォームに対してfillin:機能を有効にするだけです。構文はリスト10-27で説明されています。

リスト10-27 - バリデーションが失敗したときにフォームを再投入するためにfillinを有効にする(validate/send.yml)

fillin:
  enabled: true  # フォームの再投入を有効にする
  param:
    name: test  # ページに1つのフォームのみが存在する場合のフォーム名
    skip_fields:   [email]  # これらのフィールドを再投入しない
    exclude_types: [hidden, password] # これらのフィールドタイプを再投入しない
    check_types:   [text, checkbox, radio, password, hidden] # これらを再投入する
    content_type:  html  # htmlは寛容なデフォルト。ほかのオプションはxmlとxhtml(xmlと同じだがxmlの最初の部分はなし)

デフォルトでは、自動の再投入はテキスト入力、チェックボックス、ラジオボタン、テキストエリア、と選択コンポーネント(シンプルなものと複数)に対して動作しますが、、パスワードもしくは隠しタグは再投入しません。fillin機能はファイルタグに対して動作しません。

NOTE fillin機能はXMLのレスポンスの内容をユーザーに送信する直前に解析することで機能します。デフォルトではfillinはHTMLを出力します。

fillinにXHTMLを出力させたい場合、param: content_type: xmlを設定しなければなりません。レスポンスが厳密に有効なXHTMLドキュメントではない場合fillinは機能しません。 3番目に利用可能なcontent_typexhtmlxmlと同じですがIE6で奇妙な動作を引き起こすレスポンスからxmlのプロローグをとり除きます。

ユーザーが入力した値をフォームの入力に書き戻すまえにこれらを変換したい場合があります。リスト10-28で示されるように、conveters:キーの下で変換方法を定義すればエスケーピング、URLの書き換え、特別な文字をエンティティに変換する、関数を通して呼び出されたほかの変換などはフォームのフィールドに適用できます。

リスト10-28 - fillinのまえに入力を変換する(validate/send.yml)

fillin:
  enabled: true
  param:
    name: test
    converters:         # 適用するコンバータ
      htmlentities:     [first_name, comments]
      htmlspecialchars: [comments]

symfonyの標準バリデーター

symfonyはフォーム用の標準バリデーターをいくつか備えています:

それぞれのバリデーターはデフォルトのパラメーターとエラーメッセージのセットを持ちますが、 YAMLファイルのなかで、initialize()バリデーターメソッドを通して簡単にオーバーライドできます。 つぎのセクションでバリデーターを説明し、使いかたの例を示します。

文字列バリデーター

sfStringValidatorによって文字に関連する制約をパラメーターに適用できるようになります。

sfStringValidator:
  values:       [foo, bar]
  values_error: 認められる値はfooとbarのみです
  insensitive:  false  # trueの場合、値との比較は大文字と小文字を区別しない
  min:          2
  min_error:    少なくとも2文字入力してください
  max:          100
  max_error:    100文字以下で入力してください

数字バリデーター

sfNumberValidatorパラメーターが数字であることをバリデートし、これによってサイズの制約を適用することができます。

sfNumberValidator:
  nan_error:    整数を入力してください
  min:          0
  min_error:    値は最少で0でなければなりません
  max:          100
  max_error:    値は100以下でなければなりません

Eメールバリデーター

sfEmailValidatorはEメールアドレスとして有効なパラメーターが含まれるかをバリデートします。

sfEmailValidator:
  strict:       true
  email_error:  このEメールアドレスは無効です

RFC822はEメールアドレスのフォーマットを定義します。しかしながら、これは一般的に受け入れられるフォーマットよりも寛容です。たとえば、RFCに従えばme@localhostは有効なEメールアドレスですが、おそらくは受信したくないでしょう。strictパラメーターをtrueに設定したとき、name@domain.extenisonにマッチするEメールアドレスだけが有効です。falseに設定したとき、RFC822がルールとして使われます。

URLバリデーター

sfUrlValidatorはフィールドが正しいURLであるかチェックします。

sfUrlValidator:
  url_error:    このURLは無効です

正規表現バリデーター

sfRegexValidatorによって値をPerlと互換性のある正規表現のパターンとマッチできます。

sfRegexValidator:
  match:        No
  match_error:  URLを含む投稿はスパムと見なされます
  pattern:      /http.*http/si

matchパラメーターはリクエストパラメーターが有効なパターンにマッチする(値はYes)、もしくは無効なパターン(値はNo)にマッチするかを決定します。

比較バリデーター

sfCompareValidatorは2つの異なるリクエストパラメーターを比較します。パスワードをチェックするためにとても便利です。

fields:
  password1:
    required:
      msg:      パスワードを入力してください
  password2:
    required:
      msg:      パスワードを再入力してください
    sfCompareValidator:
      check:    password1
      compare_error: 2つのパスワードが一致しません

checkパラメーターは現在のフィールドがマッチしなければならないフィールドの名前を含みます。

デフォルトでは、バリデーターはパラメーターが等しいことをチェックします。operatorパラメーターを指定することでこのふるまいを変更できます。利用可能な演算子はつぎのとおりです: >>=<<===!=

Propel/Doctrine独自のバリデーター

sfPropelUniqueValidatorはリクエストパラメーターの値がすでにデータベースに存在していないかをバリデートします。ユニークインデックスのためにとても便利です。

fields:
  nickname:
    sfPropelUniqueValidator:
      class:        User
      column:       login
      unique_error: このログインはすでに存在します。別のものを入力してください。

この例において、バリデーターはloginカラムがバリデートするフィールドと同じ値を持つUserクラスのレコードに対してデータベースを調べます。

CAUTION sfPropelUniqueValidatorは競合条件の影響を受けやすいです。想像しにくいですが、複数ユーザーの環境において、結果は自身が返す瞬間を変えることがあります。重複のINSERTエラーを処理する準備もすべきです。

-

NOTE プロジェクト用にDoctrine ORMを有効にした場合、上記のPropel用に説明した同じ方法で sfDoctrineUniqueValidatorを使うこともできます。

ファイルバリデーター

sfFileValidatorはファイルのアップロードフィールドにフォーマット(mime-typeの配列)とサイズの制限を適用します。

fields:
  image:
    file:       True
    required:
      msg:      画像ファイルをアップロードしてください
    sfFileValidator:
      mime_types:
        - 'image/jpeg'
        - 'image/png'
        - 'image/x-png'
        - 'image/pjpeg'
      mime_types_error: PNGとJPEGの画像のみ許可されます
      max_size:         512000
      max_size_error:   最大のサイズは512KBです

フィールドに対してfile属性はTrueに設定され、テンプレートはフォームをmultipartとして宣言しなければならないことを覚えておいてください。

コールバックバリデーター

sfCallbackValidatorはバリデーションをサードパーティのコールバックメソッドもしくはバリデーションを行う関数に委譲します。コールバックメソッドもしくは関数はtrueもしくはfalseを返さなければなりません。

fields:
  account_number:
    sfCallbackValidator:
      callback:      is_numeric
      invalid_error: 数字を入力してください。
  credit_card_number:
    sfCallbackValidator:
      callback:      [myTools, validateCreditCard]
      invalid_error: 有効なクレジットカードの番号を入力してください。

コールバックメソッドもしくは関数はバリデートされた値を最初のパラメーターとして受けとります。新しい完全なバリデータークラスを作成するよりも、既存のメソッドの機能を再利用するときにとても役立ちます。

TIP この章の後のほうの"カスタムバリデーターを作成する"のセクションで説明されている独自のバリデーターを書くこともできます。

日付バリデーター

sfDateValidatorは投稿された日付が有効かつ/もしくは指定された範囲にあることをチェックします。

sfDateValidator:
  date_error:    You have entered an invalid date
  compare:       "2007-05-01"
  operator:      ">="  #defaults to "==" if not supplied
  compare_error: "Enter a date later than 1st May 2007"

TIP この章の後の"カスタムバリデーターを作成する"のセクションで説明されているように、独自バリデーターを書くこともできます。

名前つきバリデーター

バリデータークラスと設定を繰り返す必要であることがわかる場合、これを名前つきバリデーターのもとでまとめることができます。連絡フォームの例において、emailフィールドは同じsfStringValidatorパラメーターをnameフィールドとして必要とします。同じ設定を2回繰り返すことを避けるために、名前つきのバリデーターであるmyStringValidatorを作成できます。これを行うために、myStringValidatorラベルをvalidators:ヘッダーの下に追加し、まとめたい名前つきのバリデーターの詳細な内容を持つclassキーとparamキーを設定します。リスト10-29で示されるように、fieldsセクションの通常の名前つきバリデーターとまったく同じように、名前つきバリデーターを利用できます。

リスト10-29 - バリデーションファイルで名前つきバリデーターを再利用する(validte/send.yml)

validators:
  myStringValidator:
    class: sfStringValidator
    param:
      min:       2
      min_error: このフィールドは短すぎます(最少で2文字)
      max:       100
      max_error: このフィールドは長すぎます(最大で100文字)

fields:
  name:
    required:
      msg:       名前のフィールドは空白であってはなりません
    myStringValidator:
  email:
    required:
      msg:       Eメールのフィールドは空白であってはなりません
    myStringValidator:
    sfEmailValidator:
      email_error:  このEメールは無効です

メソッドへのバリデーションを制限する

デフォルトでは、バリデーションファイルに設定されたバリデーターは、アクションがPOSTメソッドで呼び出されたときに実行されます。リスト10-30で示されるように、異なるメソッドに対して異なるバリデーションをできるようにするために、methodsキーにおいて別の値を指定することでこの設定をグローバルもしくはフィールド単位でオーバーライドできます。

リスト10-30 - フィールドをテストするときを定義する(validate/send.yml)

methods:         [post]     # これはデフォルトの設定

fields:
  name:
    required:
      msg:       名前のフィールドは空白にはできません
    myStringValidator:
  email:
    methods:     [post, get] # グローバルメソッドの設定をオーバーライドする
    required:
      msg:       Eメールフィールドは空白にはできません
    myStringValidator:
    sfEmailValidator:
      email_error:  このEメールアドレスは無効です

バリデーションファイルはどのようになるのか?

これまでのところ、バリデーションファイルの断片しか見てきませんでした。すべてを組み立てるとき、バリデーションのルールはYAMLで明確な変換を見つけます。リスト10-31はこの章の前の方で定義したすべてのルールに対応するサンプルの問い合わせフォームのための完全なバリデーションファイルを示しています。

リスト10-31 - 完全なバリデーションファイルのサンプル

fillin:
  enabled:      true

validators:
  myStringValidator:
    class: sfStringValidator
    param:
      min:       2
      min_error: このフィールドは短すぎます(最少で2文字)
      max:       100
      max_error: このフィールドは長すぎます(最大で100文字)

fields:
  name:
    required:
      msg:       名前のフィールドは空白のままにできません
    myStringValidator:
  email:
    required:
      msg:       Eメールフィールドは空白のままにできません
    myStringValidator:
    sfEmailValidator:
      email_error:  このEメールアドレスは無効です
  age:
    sfNumberValidator:
      nan_error:    整数を入力してください
      min:          0
      min_error:    "あなたははまだ生まれていません。どのようにメッセージを送りたいのですか?"
      max:          120
      max_error:    "ちょっと、おばあちゃん、インターネットをサーフィンするには年をとりすぎていませんか?"
  message:
    required:
      msg:          メッセージフィールドを空白のままにできません

複雑なバリデーション

バリデーションファイルは多くのニーズを満たしますが、バリデーションがとても複雑なときに十分ではないかもしれません。この場合、アクションのvalidateXXX()メソッドに戻るもしくはつぎのセクションであなたの問題の解決方法が見つかります。

カスタムバリデーターを作成する

それぞれのバリデーターはsfValidatorクラスを継承するクラスです。symfonyに搭載されたバリデータークラスがあなたのニーズに適していない場合、これをオートロードできるlib/ディレクトリの任意の場所で、新しいバリデータークラスを作成できます。構文はとてもシンプルです: バリデーターが実行されるとき、バリデーターのexecute()メソッドが呼び出されます。initialize()メソッドでデフォルト設定を定義することもできます。

execute()メソッドは最初のパラメーターとしてバリデートする値と2番目のパラメーターとして投げるエラーメッセージを受けとります。両方の値は参照として渡されるので、メソッドの範囲内からエラーメッセージを修正できます。

initialize()メソッドはYAMLファイルからContext Singletonとパラメーターの配列を受けとります。最初に親のsfValidatorクラスのinitalize()メソッドを呼び出さなければならず、そしてデフォルト値を設定します。

すべてのバリデーターは$this->getParameterHolder()によってアクセス可能なパラメーターホルダーを持ちます。

たとえば、文字列がスパムではないことをチェックするためにsfSpamValidatorを作りたい場合、リスト10-32で示されるコードをsfSpamValidator.class.phpファイルを追加します。$valueが文字列の'http'max_url倍多くの含むのかをチェックします。

リスト10-32 - カスタムバリデーターを作成する(lib/sfSpamValidator.class.php)

[php]
class sfSpamValidator extends sfValidator
{
  public function execute(&$value, &$error)
  {
    // max_url=2に対して、正規表現は/http.*http/is
    $re = '/'.implode('.*', array_fill(0, $this->getParameter('max_url') + 1, 'http')).'/is';

    if (preg_match($re, $value))
    {
      $error = $this->getParameter('spam_error');

      return false;
    }

    return true;
  }

  public function initialize ($context, $parameters = null)
  {
    // 親を初期化する
    parent::initialize($context);

    // パラメーターのデフォルト値を設定する
    $this->setParameter('max_url', 2);
    $this->setParameter('spam_error', 'This is spam');

    // パラメーターを設定する
    $this->getParameterHolder()->add($parameters);

    return true;
  }
}

バリデーターがautoloadableディレクトリに追加されると同時に(そしてキャッシュがクリアされる)、リスト10-33で示されるように、バリデーションファイルでこれを使うことができます。

リスト10-33 - カスタムバリデーターを使う(validate/send.yml)

fields:
  message:
    required:
      msg:          メッセージフィールドを空白にできません
    sfSpamValidator:
      max_url:      3
      spam_error:   このサイトを立ち去ってください、汚らわしいスパマーさん!

フォームフィールドに対して配列構文を使う

PHPによってフォームフィールドに対して配列構文を利用できます。独自のフォームを書くとき、もしくはPropelのadministration(14章を参照)の生成フォームを使うとき、リスト10-34のようなHTMLのコードで終わります。

リスト10-34 - 配列構文によるフォーム

[php]
<label for="story_title">タイトル:</label>
<input type="text" name="story[title]" id="story_title" value="default value"
       size="45" />

バリデーションファイルで入力名を(角かっこで)そのまま使うと解析で引き起こされたエラーが投じられます。ここでの解決方法はリスト10-35で示されるように、fieldsセクションにおいて、角かっこ[]を波かっこ{}で置き換えることで、symfonyはあとでバリデーターに送られる名前の変換を考慮するようになります。

リスト10-35 - 配列構文を利用したフォームのためのバリデーションファイル

fields:
  story{title}:
    required:     Yes

空のフィールド上でバリデーターを実行する

空の値の上で、必要ないフィールド上で、バリデーターを実行することが必要な場合があります。たとえば、ユーザーがパスワードを変更したい(したくない場合)にこの事例があてはまります。この場合、確認パスワードを入力しなければなりません。リスト10-36で例をご覧ください。

リスト10-36 - 2つのパスワードフィールドを持つフォームのためのバリデーションファイルのサンプル

fields:
  password1:
  password2:
    sfCompareValidator:
      check:         password1
      compare_error: 2つのパスワードが一致しません

バリデーションの処理はつぎのように実行されます:

password1not nullであるかどうかpassword2バリデーターを実行したい場合があります。幸いにも、groupパラメーターのおかげでsymfonyバリデーターはこの事例に対処します。フィールドがグループのなかに存在する場合、それが空ではなく同じグループのフィールドの1つが空ではないかバリデーターを実行します。

ですので、リスト10-36で示されているように設定を変更する場合、バリデーションプロセスは正しくふるまいます。

リスト10-36 - 2つのパスワードフィールドとグループによるフォームのためのバリデーションファイルのサンプル

fields:
  password1:
    group:           password_group
  password2:
    group:           password_group
    sfCompareValidator:
      check:         password1
      compare_error: 2つのパスワードが一致しません

バリデーションの処理はつぎのように実行します:

まとめ

symfonyのテンプレートのなかでフォーム(form)を書く作業は標準のフォームヘルパーとスマートなオプションによって円滑になります。オブジェクトのプロパティを編集するためにフォームを設計するとき、バリデーションファイル(validation file)、バリデーションヘルパー、と再投入機能は頑強でユーザーフレンドリーなサーバーのフィールド値コントロール機能を開発するために必要な作業量を減らします。そして、アクションクラスのなかでカスタムバリデーターを書くもしくはvalidateXXX()メソッドを作ることで、より複雑なバリデーションのニーズに対応することができます。