Play による HTTP データのバリデーション
バリデーションは、そのデータが妥当な値を持っている、または指定された要件を満たしていることを保証します。バリデーションを使ってデータベースに保存する前にデータが正しいことを確認したり、あるいは HTTP パラメータに直接バリデーションを使用することで簡易なフォームの妥当性を検証することができます。
どのように動作するのでしょうか?
各リクエストは、エラーを集める Validation オブジェクトを持ちます。 validation 変数を使用することで、コントローラから直接このオブジェクトにアクセスすることができます。 play.data.validation.Validation のクラスの static なメソッドを使用することで、この API のサブセットにアクセスすることも可能です。
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 の場合、以下のように表示されます:
name is required
age is required
デフォルトメッセージは、メッセージバンドルを参照するキーです。このため、 conf/messages ファイルには次のような記述があります:
validation.required=%s is required
アプリケーションがサポートする言語毎にこのデフォルトメッセージを変更して上書きすることができます。 %s プレースホルダはエラーのキーに置き換えられます。 error.message(String field) メソッドを使用して、これを上書きすることができます。
例えば、以下のようにします:
Error error = validation.required(name).error;
if(error != null) {
System.out.println(error.message("The name"));
}
チェック毎に異なるメッセージを指定することもできます:
Error error = validation.required(name).message("Fill the name!").error;
if(error != null) {
System.out.println(error.message());
}
テンプレートへのエラーの表示
ほとんどの場合、ビューテンプレートにエラーメッセージを表示したくなると思います。 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}
アノテーションの使用
同じことを、アノテーションを使用することで行えます:
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 パッケージに必要なバリデータを見つけることができませんか? 自分で書いてしまいましょう。総称的な @CheckWith アノテーションを使って、独自の Check 実装を紐付けることができます。
例えば、以下のようにします:
public class User {
@Required
@CheckWith(MyPasswordCheck.class)
public String password;
static class MyPasswordCheck extends Check {
public abstract boolean isSatisfied(Object user, Object password) {
return notMatchPreviousPasswords(password);
}
}
}