Play による HTTP データのバリデーション
バリデーションは、そのデータが妥当な値を持っている、または指定された要件を満たしていることを保証します。バリデーションを使ってデータベースに保存する前にデータが正しいことを確認したり、あるいは HTTP パラメータに直接バリデーションを使用することで簡易なフォームの妥当性を検証することができます。
Play のバリデーションはどのように動作するのでしょうか?
各リクエストは、エラーを集める Validation オブジェクトを持ちます。 バリデーションを定義する方法は三つあります。
- コントローラのメソッドの中から、コントローラの validation フィールドにあるメソッドを直接呼び出します。 play.data.validation.Validation のクラスの static なメソッドを使用することで、この API のサブセットにアクセスすることも可能です。
- コントローラのメソッド引数の宣言部分にバリデーション用のアノテーションを追加します。
- アクションメソッドの POJO 引数に @Valid アノテーションを追加し、この POJO のプロパティにバリデーション用のアノテーションを追加します。
validation オブジェクトは、 play.data.validation.Error オブジェクトのコレクションを保持します。各エラーには、2 つのプロパティがあります:
- key 。key は、どのデータ要素がエラーを引き起こしたか判断する手助けになります。キーの値は任意に設定できますが、Play がエラーを生成するときは、Java の変数名に従ったデフォルトの規約を使用します。
- message 。message は、エラーのテキスト記述を含みます。message は、平文のメッセージか、または (通常は国際化対応のために) メッセージバンドルの key を参照します。
最初の方法を使って、単純な HTTP パラメータの妥当性を検証するやり方を見てみましょう:
public static void hello(String name) {
validation.required(name);
…
}
このコードは、name 変数が正しく設定されていることをチェックします。正しく設定されていない場合、対応するエラーが現在のエラーコレクションに追加されます。
妥当性の検証が必要なだけ、この操作をくり返すことができます:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
…
}
バリデーションエラーメッセージ
バリデーションの終了時に、何かエラーが作成されているか、そしてそれらを表示するかどうかをチェックすることができます:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
if(validation.hasErrors()) {
for(Error error : validation.errors()) {
System.out.println(error.message());
}
}
}
name と age が null の場合、以下のように表示されます:
Required
Required
これは、 $PLAY_HOME/resources/messages に定義されたデフォルトのメッセージが以下の通りであるためです:
validation.required=Required
このバリデーションメッセージをカスタマイズする方法が三つあります。
- アプリケーションの messages ファイルでメッセージを再定義することにより、デフォルトメッセージを上書きする。
- 追加のバリデーション引数として、カスタマイズしたメッセージを提供する。
- 追加のバリデーション引数として、国際化したメッセージへのキーを提供する。
バリデーションメッセージの国際化
これらのメッセージを上書きするもっとも単純な方法は、アプリケーションの conf/messages ファイルにおいて同じメッセージキーを使用することです。例えば、以下のようにします:
validation.required = Please enter a value
国際化 で述べられているように、他の言語による地域化されたメッセージを提供することもできます。
バリデーションメッセージの引数
メッセージの中で、エラーのキー文字列のためのプレースホルダを使うことができます。
validation.required=%s is required
この出力結果は以下のように変わります:
name is required
age is required
このエラーキーはデフォルトではパラメータ名であり、パラメータそのものをメッセージ検索に使用します。例えば、 hello アクションメソッド 上の name 引数は以下のようにして地域化することができます:
name = Customer name
この結果、出力は以下のようになります:
Customer name is required
age is required
error.message(String key) メソッドを使ってエラーキーを変更することもできます。例えば:
Error error = validation.required(name).error;
if(error != null) {
System.out.println(error.message("Customer name"));
}
いくつかの組み込みのバリデータにはバリデーション引数に対応した追加のメッセージ引数が定義されています。例えば、‘match’ バリデーションは正規表現を指定するために、前述の %s プレースホルダとはパラメータインデックスに‘2’を指定する点で異なる String 型の第二引数を定義します:
validation.match=Must match %2$s
同様に、‘range’ バリデーションは 2 および 3 でインデックス付けされた二つの数値型の追加パラメータを定義します:
validation.range=Not in the range %2$d through %3$d
その他のバリデーションがどのようなパラメータを持つか確認するには $PLAY_HOME/resources/messages を見てみてください。
地域化されたバリデーションメッセージのカスタマイズ
$PLAY_HOME/resources/messages のバリデーションメッセージは、Play の 組み込みバリデーション それぞれに用意されたデフォルトのメッセージキーを使用します。例えば、以下のようにして異なるメッセージキーを指定することができます:
validation.required.em = You must enter the %s!
アクションメソッド中の手動バリデーションで、このメッセージキーを使用します:
validation.required(manualKey).message("validation.required.em");
あるいは、このキーをアノテーションの message パラメータで使用します:
public static void hello(@Required(message="validation.required.em") String name) {
…
}
このバリデーション用アノテーションと同じテクニックを JavaBean プロパティに使用することができます:
public static void hello(@Valid Person person) {
…
}
public class Person extends Model {
@Required(message = "validation.required.emphasis")
public String name;
…
}
(地域化されない) 文字列バリデーションメッセージのカスタマイズ
Play のメッセージ検索は、キーに対応するメッセージが定義されていない場合、単にそのメッセージキーを返すので、必要があればメッセージキーの替わりに文字列のメッセージそのものを使用することもできます。手動バリデーションにおいて、上の例と同様にこれを使用します:
validation.required(manualKey).message("Give us a name!");
以下のようにして、アクションメソッドパラメータに使用します:
public static void save(@Required(message = "Give us a name!") String name) {
…
}
以下のようにして、JavaBean プロパティのアノテーションに使用します:
public static void save(@Valid Person person) {
…
}
public class Person extends Model {
@Required(message = "Give us a name!")
public String name;
…
}
テンプレートへのエラーの表示
ほとんどの場合、ビューテンプレートにエラーメッセージを表示したくなると思います。 errors オブジェクトを使用することで、テンプレート中でエラーメッセージにアクセスすることができます。いくつかのタグは、エラーを表示する手助けをします:
サンプルを見てみましょう:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
render(name, age);
}
そして、現在のテンプレートは以下のようになります:
#{ifErrors}
<h1>Oops…</h1>
#{errors}
<li>${error}</li>
#{/errors}
#{/ifErrors}
#{else}
Hello ${name}, you are ${age}.
#{/else}
しかし、実際のアプリケーションでは、もとのフォームを再表示したくなると思います。これを行うためには、2 つのアクションが存在します: 1 つのアクションではフォームを表示し、もう 1 つのアクションでは POST を扱います。
もちろん、バリデーションは 2 番目のアクションで実行され、ここで何らかのエラーが発生した場合には 1 番目のアクションにリダイレクトしなければなりません。この場合、リダイレクトの間にエラーを保持する特別なトリックが必要になります。 validation.keep() メソッドを使用してください。このメソッドは、次のアクションのためにエラーコレクションを保持します。
実際のサンプルを見てみましょう:
public class Application extends Controller {
public static void index() {
render();
}
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
if(validation.hasErrors()) {
params.flash(); // add http parameters to the flash scope
validation.keep(); // keep the errors for the next request
index();
}
render(name, age);
}
}
そして view/Application/index.html テンプレートは以下のようになります:
#{ifErrors}
<h1>Oops…</h1>
#{errors}
<li>${error}</li>
#{/errors}
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="name" value="${flash.name}" />
</div>
<div>
Age: <input type="text" name="age" value="${flash.age}" />
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
エラーを生成したフィールドの隣にそれぞれのエラーメッセージを表示することによって、より良いユーザー体験を作成することができます:
#{ifErrors}
<h1>Oops…</h1>
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="name" value="${flash.name}" />
<span class="error">#{error 'name' /}</span>
</div>
<div>
Age: <input type="text" name="age" value="${flash.age}" />
<span class="error">#{error 'age' /}</span>
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
アノテーションによるバリデーション
play.data.validation パッケージ内のアノテーションは、 Validation オブジェクトのそれぞれのメソッドに対応したアノテーションによって入力チェック制約を指定する、もっと簡単な代替手段を提供します。この入力チェック用アノテーションは、コントローラのメソッド引数を注釈するだけで使用することができます:
public static void hello(@Required String name, @Required @Min(0) Integer age) {
if(validation.hasErrors()) {
params.flash(); // add http parameters to the flash scope
validation.keep(); // keep the errors for the next request
index();
}
render(name, age);
}
複雑なオブジェクトのバリデーション
アノテーションを使用して、モデルオブジェクトに容易に制約を追加することができ、コントローラではすべてのプロパティが妥当でなければならないことを指定します。User クラスを使用して前述の例を書き直してみましょう。
はじめは、プロパティにバリデーション用のアノテーションを設定した User クラスです:
package models;
public class User {
@Required
public String name;
@Required
@Min(0)
public Integer age;
}
次は、 User オブジェクトのプロパティが妥当でなければならないことを指定する @Valid アノテーションを設定した hello アクションを以下のように変更します:
public static void hello(@Valid User user) {
if(validation.hasErrors()) {
params.flash(); // add http parameters to the flash scope
validation.keep(); // keep the errors for the next request
index();
}
render(name, age);
}
最後に、フォームを以下のように変更します:
#{ifErrors}
<h1>Oops…</h1>
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="user.name" value="${flash['user.name']}" />
<span class="error">#{error 'user.name' /}</span>
</div>
<div>
Age: <input type="text" name="user.age" value="${flash['user.age']}" />
<span class="error">#{error 'user.age' /}</span>
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
組み込みのバリデーション
play.data.validation パッケージには、 Validation オブジェクトにおいて、またはアノテーションとして使用できる、いくつかの 組み込みのバリデーション が含まれています。
カスタムバリデーション
play.data.validation パッケージに必要なバリデータを見つけることができませんか? 自分で書いてしまいましょう。総称的な @CheckWith アノテーションを使って、独自の Check 実装を紐付けることができます。
例えば、以下のようにします:
public class User {
@Required
@CheckWith(MyPasswordCheck.class)
public String password;
static class MyPasswordCheck extends Check {
public boolean isSatisfied(Object user, Object password) {
return notMatchPreviousPasswords(password);
}
}
}
考察を続けます
Play アプリケーションの最後の層は ドメインオブジェクトモデル です: