Caution: You are browsing the legacy symfony 1.x part of this website.

第2章 - フォームのバリデーション

1.1
Symfony version Language

1章で、基本的な問い合わせフォームを作り表示する方法を学びました。この章ではフォームのバリデーションを管理する方法を学びます。

始める前に

1章で作成した問い合わせフォームの機能は、まだ十分ではありません。ユーザーが無効なEメールアドレスを投稿したり、投稿したメッセージが空である場合、何が起こるでしょうか?このような場合、図2-1で示されるように、ユーザーに入力を訂正することを求めるエラーメッセージを表示するようにしてみます。

図2-1 - エラーメッセージを表示する

エラーメッセージを表示する

問い合わせフォームに実装するバリデーションルールは下記の通りです:

  • name : オプション
  • email : 必須、値は有効なEメールアドレスでなければなりません
  • subject: 必須、選択した値は値のリストに対して有効でなければなりません
  • message: 必須、メッセージの長さは少なくとも4文字でなければなりません

note

なぜsubjectフィールドをバリデートする必要があるのでしょうか?<select>タグでは、ユーザーは予め定義された値しか選択できません。通常のユーザーは、表示された選択肢の1つのみを選択できます。しかし、Firefox Developer Toolbarのようなツールを利用するか、curlwgetでリクエストをシミュレートすることで、定義された値とは異なる値を投稿できます。

リスト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は、アプリケーションとユーザー間のインタラクションを示しています。まず最初に、フォームをユーザーに表示します。ユーザーがフォームを投稿すると、入力が有効な場合はthank youページにリダイレクトされ、入力は無効な値を含む場合は、エラーメッセージとともにフォームが再表示されます。

図2-2 - アプリケーションとユーザー間のインタラクション

アプリケーションとユーザー間のインタラクション

バリデータ

symfonyのフォームはフィールドで構成されます。1章で見てきたように、それぞれのフィールドはユニークな名前で識別されます。フィールドをユーザーに表示するために、それぞれのフィールドにウィジェットがバインドされました。ここでは、それぞれのフィールドにバリデーションルールを適用する方法を見てみましょう。

sfValidatorBaseクラス

それぞれのフィールドのバリデーションは、sfValidatorBaseクラスを継承するオブジェクトによって行われます。問い合わせフォームのバリデーションを行うために、4つのそれぞれのフィールド: nameemailsubjectmessageに対してバリデータオブジェクトを定義します。リスト2-2は、フォームクラス内でsetValidators()メソッドを利用して、これらのバリデータを実装する方法を示しています。

リスト2-2 - バリデータをContactFormクラスに追加する

// lib/form/ContactForm.class.php
class ContactForm extends sfForm
{
  protected static $subjects = array('Subject A', 'Subject B', 'Subject C');
 
  public function configure()
  {
    $this->setWidgets(array(
      'name'    => new sfWidgetFormInput(),
      'email'   => new sfWidgetFormInput(),
      '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 : Eメールのバリデーションを行います
  • sfValidatorChoice: 入力値と予め定義された選択のリストに対してバリデーションを行います

それぞれのバリデータは、最初の引数としてオプションのリストを取ります。ウィジェットのように、これらのオプションの中には必須であるものと、必須ではないものがあります。例えば、sfValidatorChoiceバリデータは1つの必須オプションである、choicesを取ります。それぞれのバリデータは、sfValidatorBaseクラスで定義されたrequiredtrimオプションを取ることもできます:

オプション デフォルト値 説明
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($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の場合、フォームは有効なので、ユーザーはthank youページにリダイレクトされます:

      if ($this->form->isValid())
      {
        $this->redirect('contact/thankyou?'.http_build_query($this->form->getValues()));
      }
    • 戻り値がtrueではない場合、最初と同じようにindexSuccessテンプレートが表示されます。バリデーション処理によって、ユーザーに表示されるエラーメッセージがフォームに追加されます。

note

フォームが初期の状態のとき、isValid()メソッドは常にfalseを返しgetValues()メソッドは常に空の配列を返します。

図2-3は、アプリケーションとユーザー間のインタラクションで実行されるコードを示しています。

図2-3 - アプリケーションとユーザー間のインタラクションで実行されるコード

アプリケーションとユーザー間のインタラクションで実行されるコード

バリデータの目的

thank youページにリダイレクトする箇所で、$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で定義されたrequiredinvalidエラーがあります:

コード メッセージ 説明
required Required. フィールドは必須で値は空
invalid Invalid. フィールドは無効

すでに利用したバリデータに関するエラーコードは以下の通りです:

バリデータ エラーコード
sfValidatorString max_length
min_length
sfValidatorEmail
sfValidatorChoice

エラーメッセージをカスタマイズするには、バリデーションオブジェクトを作るときに2番目の引数で指定します。リスト2-4ではいくつかのエラーメッセージをカスタマイズし、図2-5ではカスタマイズされたエラーメッセージが実際に表示されています。

リスト2-4 - エラーメッセージをカスタマイズする

class ContactForm extends sfForm
{
  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 sfForm
{
  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_1/)を参照してください。デフォルト値に加えて、それぞれのコード、オプションとエラーメッセージが詳細に記述されています。例えば、sfValidatorStringバリデータのAPIは /api/1_1/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 sfForm
{
  public function configure()
  {
    $this->setWidgets(array('name' => new sfWidgetFormInputString()));
    $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($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プロパティも更新します。

しかし、このコードでnameis_adminフィールドの両方に対して値を渡すテストすると、図2-8で示されるように、"Extra field name."グローバルエラーが表示されます。投稿されたフィールドに、バリデータが関連づけられていないものがあるため、システムでエラーが生成されました。is_adminフィールドはUserFormフォームクラスで定義されていません。

図2-8 - バリデータが見つからないエラー

バリデータが見つからないエラー

これまで説明したすべてのバリデータは、フィールドに関連するエラーを生成します。このグローバルエラーはどこで処理されているのでしょうか?setValidators()メソッドを使うと、symfonyにより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_fieldsfilter_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 sfForm
{
  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_fieldstrueにセットしたバリデーション

<code>allow_extra_fields</code>を<code>true</code>にセットしたバリデーション

よく見ると、フォームが有効であるにも関わらず、どんな値を投稿しても、thank youページ内でnameフィールドの値が空になっています。実際、$this->form->getValues()で返される配列に値は設定されていません。allow_extra_fieldsオプションを無効にすることで、バリデータが存在しないエラーは発生しなくなりますが、filter_extra_fieldsオプションは、デフォルトでtrueに設定されており、バリデータが存在しない値をフィルタリングして、バリデート済みの値から削除します。リスト2-10で示すように、もちろんこのふるまいを変更することもできです。

リスト2-10 - filter_extra_fields保護を無効にする

class ContactForm extends sfForm
{
  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);
  }
}

これでフォームをバリデートして、thank youページで入力された値を読み取ることができるようになります。

4章では、フォームの値からPropelオブジェクトを安全にシリアライズするために、これらの保護を利用できることを説明します。

論理バリデータ

論理バリデータを利用すると、いくつかのバリデータを単独のフィールドに対して定義できます:

  • sfValidatorAnd: フィールドがすべてのバリデータのバリデーションをパスすると有効になります

  • sfValidatorOr : フィールドが少なくとも1つのバリデータのバリデーションをパスすると有効になります

論理バリデータのコンストラクタは、最初の引数としてバリデータのリストを取ります。リスト2-11では、同時に満たす必要がある2つのバリデータをnameフィールドに関連づけるために、sfValidatorAndを使います。

リスト2-11 - sfValidatorAndバリデータを使う

class ContactForm extends sfForm
{
 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 sfForm
{
 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'));

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_dateend_dateフィールドのバリデーションでは、比較できるフォーマット(デフォルトではY-m-d H:i:s)に常に変換されます。

デフォルトでは、プレバリデータとポストバリデータは、フォームに対してグローバルエラーを返します。とは言うものの、エラーを特定のフィールドに関連づけたい場合もあります。例えば、sfValidatorSchemaCompareバリデータのthrow_global_errorオプションを使うと、グローバルエラー(図2-10)にするか、またはエラーを最初のフィールドに関連づける(図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 - fileフィールドをContactFormフォームに追加する

// lib/form/ContactForm.class.php
class ContactForm extends sfForm
{
  protected static $subjects = array('Subject A', 'Subject B', 'Subject C');
 
  public function configure()
  {
    $this->setWidgets(array(
      'name'    => new sfWidgetFormInput(),
      'email'   => new sfWidgetFormInput(),
      'subject' => new sfWidgetFormSelect(array('choices' => self::$subjects)),
      'message' => new sfWidgetFormTextarea(),
      'file'    => new sfWidgetFormInputFile(),
    ));
    $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(),
    ));
  }
}

ファイルをアップロードすることを許可するフォーム内にsfWidgetFormInputFileウィジェットが存在する場合、リスト2-18で示されるようにenctype属性をformタグに追加する必要があります。

リスト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

フォームに関連したテンプレートを動的に生成する際に、フォームオブジェクトのisMultipart()メソッドは、enctype属性が必要なフォームの場合trueを返します。

PHPでは、アップロードされたファイルに関する情報は、投稿された他の値とは別に保存されます。リスト2-19で示されるように、この情報を2番目の引数として渡すようにbind()メソッドの呼び出しを修正する必要があります。

リスト2-19 - アップロードされたファイルをbind()メソッドに渡す

class contactActions extends sfActions
{
  public function executeIndex($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();
        // do something with the values
 
        // ...
      }
    }
  }
 
  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タイプに従ってファイルの拡張子を返します
getOriginalName() アップロードされたファイルの名前を返します
getOriginalExtension() アップロードされたファイルの拡張子を返します
getTempName() 一時ファイルのパスを返します
getType() ファイルのmimeタイプを返します
getSize() ファイルのサイズを返します

tip

ファイルのアップロードにおいて、ブラウザから提供されたmimeタイプは信頼できません。セキュリティを最大限に保証するために、ファイルのバリデーション処理において、finfo_openmime_content_type関数、fileツールが順番に使われます。最後の方法として、どの関数もmimeタイプを推測できない場合、もしくはシステムがこれらを提供しない場合、ブラウザのmimeタイプが考慮に入れられます。mimeタイプを推測する関数を追加または変更したい場合、mime_type_guessersオプションをsfValidatorFileコンストラクタに追加します。