Lv.15 フォーム(Form)には便利機能がいっぱい [play framework2.0] [java]

play!のplay.data.Formには様々な便利な使い方があります。Springを利用した変換処理、そ
の過程でのValidation(これもSpring)など。Formへの変換処理は独自に規定することもで
きます
。Formが取り扱うClassのプロパティにアノテーションをつけることで、バリデーション
ルールも適用できます。アノテーション自体の拡張もできます。

Formクラスの中身を知らなくても十分安全安心に使える優れものです。今回は、少しFormの
中身も覗きながら行ってみます。

目次

フォームに変換する方法

フォーム用のヘルパーがいくつかある。

ClassからForm<T>に変換する

[java toolbar=”false” highlight=”8″]
public class User {
public String email;
public String password;
}

public class Application extends Controller {
public static Result index() {
Form userForm = form(User.class); // ←
return ok(“sample”);
}
}[/java]

MapからForm<T>に変換する

[java toolbar=”false”]
public class Application extends Controller {
public static Result index() {
Map anyData = new HashMap();
anyData.put(“email”, “soylatte@example.com”);
anyData.put(“password”, “secret”);
User user = userForm.bind(anyData).get();// ←
return ok(“sample”);
}
}[/java]

requestからForm<T>に変換する

追記2012-05-03
対象となるModel(ここでいうUser)の各種プロパティがgetterとsetterを持っていないと、変換されませんでした。
[java toolbar=”false”]
public class Application extends Controller {
public static Result index() {
User user = userForm.bindFromRequest().get();// ←
return ok(“sample”);
}
}[/java]

springが利用されている

これらの変換処理にはspringのDataBinderが利用されています。

ソースは長すぎるので、かいつまんでbindFromRequestを見てみます。

[java toolbar=”false”]
//play.data.Form
public Form bindFromRequest(String… allowedFields) {
return bind(requestData(), allowedFields);
}[/java]

・requestDataメソッド
ここで使われているrequestData()は、リクエストボディの中身を、urlFormEncoded、
multipartFormData、jsonData、クエリごとに一旦Map化して、戻り値であるMap<String,String>
に統合して返してくれる処理です。

・bindメソッド
そのMapをbind(Map<String,String> data, String… allowedFields)に渡しています。bindにて、
SpringのDataBinderを利用してMap化されたリクエストのBody(=data)を、バリデート&
Form<T>型に変換してくれます。

さらにFormのgetメソッドを呼ぶことで、Form<T>のT型インスタンスが取得できるという流れ
です。

バリデーション

JSR-303アノテーションを使って制約条件を設けることができます。
play.data.validation.Constraintsには、下記が用意されています。

  • Required
  • Min
  • Max
  • MinLength
  • MaxLength
  • Email
  • Pattern

アノテーションを増やしたい

拡張して電話番号用のアノテーションを作ってみます。

※代表的な正規表現

郵便番号  \d{3}-\d{4}
携帯番号  090-\d{4}-\d{4}
電話番号  \d{1,4}?-\d{1,4}?-\d{1,4}
生年月日  \d{4}-\d{2}-\d{2}
メルアド  [!#-9A-~]+@[a-z0-9-_]+\.+[a-z0-9-_]+\.+[a-z0-9-]

ちょいめもさん。

[java toolbar=”false”]
public class MyConstraints extends Constraints
{
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = PatternValidator.class)
@play.data.Form.Display
(name=”constraint.tel”, attributes={})
public static @interface Tel {
String message() default “電話じゃなきゃダメだよ”;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// 電話番号の正規表現
String value() default “\d{1,4}?-\d{1,4}?-\d{1,4}”;
}
}[/java]

独自のバリデーションメソッド

アノテーションじゃなくても、バリデーションメソッドを追加できる模様。

[java toolbar=”false”]public class User {

@Required
public String email;
public String password;

// これがバリデーションメソッド。なんら特別な話ではなくて、単に
// メソッドを自分で実装するだけ。
public String validate() {
if(authenticate(email,password) == null) {
return “Invalid email or password”;
}
return null;
}

// 公式ドキュメントにあるauthenticateは用意されているわけでは
// なく、こんな感じでどこかに定義してあるメソッドを呼んでるだけ
private String authenticate(String e, String p){
// 認証処理
}
}[/java]

失敗時の処理

badRequestメソッドが用意されています。

[java toolbar=”false”]if(userForm.hasErrors()) {
// このformという変数はform.scala.htmlのこと
return badRequest(form.render(userForm));
} else {
User user = userForm.get();
return ok(“Got user ” + user);
}[/java]

まだまだある!Formの便利機能

フォームに一連の情報をセットしたい

たとえばデフォルトの値を指定したいときにplay.data.Formのfillメソッドが利用できます。

[java toolbar=”false”]userForm.fill(new User(“bob@gmail.com”, “secret”))[/java]

get()以外でFormの値にアクセスしたい

get()を使えばForm<T>のT型インスタンスを取得できます。インスタンスごと取得するまでもな
いときなど、fieldメソッドが使えます。

[java toolbar=”false”]String name = form.filed(“name”).value();[/java]

これはget()を使った以下の処理と同等です。

[java toolbar=”false”]//Formの場合
User user = form.get();
String name = user.getName();[/java]

独自のデータバインディングを実装したい

play.data.format.Formattersクラスのregisterメソッドにて、独自のデータバインディングルール
を登録します。内部的にはorg.springframework.format.Formatter<T>を利用しています。

[java toolbar=”false”]
// データバインディングの対象となるclassを第一引数に渡します。
public static void register
(final Class clazz, final SimpleFormatter formatter)
[/java]

第一引数のクラスを変換するときは独自のルールを使ってね、という約束が生まれます。

実際の用例です。

[java toolbar=”false”]
//LocalTimeの変換は独自のルールを使ってね、という約束を登録します
Formatters.register
(LocalTime.class, new Formatters.SimpleFormatter()
{

// 変換するルールを正規表現で記載しています。
private Pattern timePattern
= Pattern.compile(“([012]?\\\\d)
(?:[\\\\s:\\\\._\\\\-]+([0-5]\\\\d))?”);

/** 入力時の変換ルール */
@Override
public LocalTime parse(String input, Locale l)
throws ParseException {
// 入力されたStringが正規表現と合致しているかどうか
Matcher m = timePattern.matcher(input);
if (!m.find()) throw new ParseException(“No valid Input”,0);

int hour = Integer.valueOf(m.group(1));
int min = m.group(2) == null
? 0
: Integer.valueOf(m.group(2));
// 無事変換された値を返す
return new LocalTime(hour, min);
}

/** 出力時の変換ルール */
@Override
public String print(LocalTime localTime, Locale l) {
return localTime.toString(“HH:mm”);
}
});[/java]

アセスメント

  • Form変換処理の内部ロジックの概略を理解している
  • Formのバリデーションを指定する方法が分かる
  • Formのバリデーションを拡張する方法が分かる
  • Formにデフォルトの値を設定する方法が分かる

公式ドキュメントのForm definitionsを参考にしました。