Sharable

システム&ウェブのシェアラブル

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

2012年4月11日

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

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

目次

フォームに変換する方法

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

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

public class User {
 public String email;
 public String password;
}

public class Application extends Controller {
 public static Result index() {
  Form<User> userForm = form(User.class); // ←
  return ok("sample");
 }
}

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

public class Application extends Controller {
 public static Result index() {
  Map<String,String> anyData = new HashMap();
  anyData.put("email", "soylatte@example.com");
  anyData.put("password", "secret");
  User user = userForm.bind(anyData).get();// ←
  return ok("sample");
 }
}

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

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

springが利用されている

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

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

//play.data.Form
public Form<T> bindFromRequest(String... allowedFields) {
 return bind(requestData(), allowedFields);
}

・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-]

ちょいめもさん。

 
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}"; 
 }
}

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

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

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){
  // 認証処理
 }
}

失敗時の処理

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

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

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

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

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

userForm.fill(new User("bob@gmail.com", "secret"))

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

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

String name = form.filed("name").value();

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

//Form<User>の場合
User user = form.get();
String name = user.getName();

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

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

// データバインディングの対象となるclassを第一引数に渡します。
public static <T> void register
 (final Class<T> clazz, final SimpleFormatter<T> formatter)

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

実際の用例です。

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

 // 変換するルールを正規表現で記載しています。
 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");
 }
});

アセスメント

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

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

このエントリーをはてなブックマークに追加