ドキュメント

§フォーム送信の処理

§フォームモジュールの有効化/無効化

デフォルトでは、Play は `PlayJava` sbt プラグインを有効にするときに Java フォームモジュール ( `play-java-forms` ) を含むため、プロジェクトに `enablePlugins(PlayJava)` が既に存在する場合は、有効にする必要はありません。

フォームモジュールは `PlayImport` でも `javaForms` として使用でき、`build.sbt` で `libraryDependencies += javaForms` を使用して使用できます。

注記: フォームを使用していない場合は、 `PlayJava` の代わりに `PlayMinimalJava` sbt プラグインを使用してフォームの依存関係を削除できます。これにより、Spring モジュールや Hibernate validator など、フォームモジュールによってのみ使用されるいくつかの推移的な依存関係も削除できます。

§フォームの定義

`play.data` パッケージには、HTTP フォームデータの送信と検証を処理するためのいくつかのヘルパーが含まれています。フォーム送信を処理する最も簡単な方法は、既存のクラスをラップする `play.data.Form` を定義することです。

import play.libs.Files.TemporaryFile;
import play.mvc.Http.MultipartFormData.FilePart;

public class User {

  protected String email;
  protected String password;
  protected FilePart<TemporaryFile> profilePicture;

  public void setEmail(String email) {
    this.email = email;
  }

  public String getEmail() {
    return email;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getPassword() {
    return password;
  }

  public FilePart<TemporaryFile> getProfilePicture() {
    return profilePicture;
  }

  public void setProfilePicture(FilePart<TemporaryFile> pic) {
    this.profilePicture = pic;
  }
}

上記のフォームは、 `email` と `password` のテキストフィールドと `profilePicture` のファイル入力フィールドを定義しているため、対応する HTML フォームはファイルをアップロードできるように `multipart/form-data` エンコードで定義する必要があります。
ご覧のとおり、デフォルトでは、Play がフォームフィールドにアクセスできるようにゲッターとセッターメソッドを定義する必要があります。ただし、 `conf/application.conf` で `play.forms.binding.directFieldAccess = true` を設定することで、「直接フィールドアクセス」(すべてのフォーム) を有効にすることもできます。このモードでは、Play はゲッターとセッターメソッドを無視し、フィールドに直接アクセスしようとします。

import play.libs.Files.TemporaryFile;
import play.mvc.Http.MultipartFormData.FilePart;

public class User {

  public String email;
  public String password;
  public FilePart<TemporaryFile> profilePicture;
}

注記: 「直接フィールドアクセス」を使用していて、フォームバインディング中に Play がフィールドにアクセスできない場合 (例: フィールドまたはフィールドを含むクラスが `public` として定義されていない場合)、Play は `field.setAccessible(true)` を内部的に呼び出して、リフレクションを介してフィールドにアクセスしようとします。Java のバージョン (8 以上)、JVM、および セキュリティマネージャー の設定によっては、 *不正なリフレクティブアクセス* に関する警告が表示されたり、最悪の場合、 `SecurityException` がスローされる可能性があります。

クラスをラップするには、コントローラーに `play.data.FormFactory` を注入する必要があります。これにより、フォームを作成できます。

Form<User> userForm = formFactory.form(User.class);

すべてのフォームで「直接フィールドアクセス」を有効にする代わりに、特定のフォームのみで有効にすることができます。

Form<User> userForm = formFactory.form(User.class).withDirectFieldAccess(true);

注記: 基になるバインディングは、 Spring データバインダー を使用して行われます。

このフォームは、テキストデータには `HashMap` から、ファイルデータには `Map>` から `User` の結果値を生成できます。

Map<String, String> textData = new HashMap<>();
textData.put("email", "[email protected]");
textData.put("password", "secret");

Map<String, FilePart<?>> files = new HashMap<>();
files.put("profilePicture", myProfilePicture);

User user = userForm.bind(lang, attrs, textData, files).get();

スコープでリクエストが使用可能な場合は、リクエストの内容から直接バインドできます。

User user = userForm.bindFromRequest(request).get();

§制約の定義

`JSR-380` (Bean Validation 2.0) アノテーションを使用して、バインディングフェーズでチェックされる追加の制約を定義できます。

public class User {

  @Required protected String email;
  protected String password;

  public void setEmail(String email) {
    this.email = email;
  }

  public String getEmail() {
    return email;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getPassword() {
    return password;
  }
}

ヒント: `play.data.validation.Constraints` クラスには、いくつかの組み込み検証アノテーションが含まれています。これらの制約アノテーションはすべて `@Repeatable` として定義されています。これにより、たとえば、同じ要素に対して同じアノテーションを複数回再利用できますが、そのたびに異なる `groups` を使用できます。ただし、一部の制約では、たとえば `@ValidateWith`/`@ValidatePayloadWith` のように、それ自体を繰り返す方が理にかなっています。

後述の 高度な検証 セクションでは、クロスフィールド検証、部分的なフォーム検証、検証中に注入されたコンポーネント (データベースへのアクセスなど) を使用する方法について説明します。

§バインディングエラーの処理

もちろん、制約を定義できる場合は、バインディングエラーを処理できる必要があります。

if (userForm.hasErrors()) {
  return badRequest(views.html.form.render(userForm));
} else {
  User user = userForm.get();
  return ok("Got user " + user);
}

通常、上記のように、フォームは単にテンプレートに渡されます。グローバルエラーは、次のようにレンダリングできます。

@if(form.hasGlobalErrors) {
    <p class="error">
        @for(error <- form.globalErrors) {
            <p>@error.format(messages)</p>
        }
    </p>
}

特定のフィールドのエラーは、 `error.format` を使用して次のようにレンダリングできます。

@for(error <- form("email").errors) {
    <p>@error.format(messages)</p>
}

`error.format` は `messages()` を引数として受け取ります。これは JavaI18N で定義されている `play.18n.Messages` インスタンスです。

§初期デフォルト値でフォームに値を設定

場合によっては、既存の値 (通常は編集用) でフォームに値を設定する必要があります。

userForm = userForm.fill(new User("[email protected]", "secret"));

ヒント: `Form` オブジェクトは不変です。`bind()` や `fill()` のようなメソッドの呼び出しは、新しいデータで設定された新しいオブジェクトを返します。

§動的フィールドを持つフォームの処理

動的フィールドを持つ html フォームからデータを取得する必要がある場合は、 `DynamicForm` を使用できます。

public Result hello(Http.Request request) {
  DynamicForm requestData = formFactory.form().bindFromRequest(request);
  String firstname = requestData.get("firstname");
  String lastname = requestData.get("lastname");
  return ok("Hello " + firstname + " " + lastname);
}

§カスタム DataBinder の登録

カスタムオブジェクトとフォームフィールド文字列の間のマッピングを定義する場合は、このオブジェクトの新しい Formatter を登録する必要があります。
適切な初期化を行う `Formatters` のプロバイダーを登録することで、これを実現できます。
JavaTime の `LocalTime` のようなオブジェクトの場合、次のようになります。

import java.text.ParseException;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import play.data.format.Formatters;
import play.data.format.Formatters.SimpleFormatter;
import play.i18n.MessagesApi;

@Singleton
public class FormattersProvider implements Provider<Formatters> {

  private final MessagesApi messagesApi;

  @Inject
  public FormattersProvider(MessagesApi messagesApi) {
    this.messagesApi = messagesApi;
  }

  @Override
  public Formatters get() {
    Formatters formatters = new Formatters(messagesApi);

    formatters.register(
        LocalTime.class,
        new SimpleFormatter<LocalTime>() {

          private Pattern timePattern = Pattern.compile("([012]?\\d)(?:[\\s:\\._\\-]+([0-5]\\d))?");

          @Override
          public LocalTime parse(String input, Locale l) throws ParseException {
            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 LocalTime.of(hour, min);
          }

          @Override
          public String print(LocalTime localTime, Locale l) {
            return localTime.format(DateTimeFormatter.ofPattern("HH:mm"));
          }
        });

    return formatters;
  }
}

プロバイダーを定義したら、それをバインドする必要があります。

import com.google.inject.AbstractModule;
import play.data.format.Formatters;

public class FormattersModule extends AbstractModule {

  @Override
  protected void configure() {

    bind(Formatters.class).toProvider(FormattersProvider.class);
  }
}

最後に、Play のデフォルトの `FormattersModule` を無効にし、代わりに `application.conf` で独自のモジュールを有効にする必要があります。

play.modules.enabled += "com.example.FormattersModule"
play.modules.disabled += "play.data.format.FormattersModule"

バインディングが失敗すると、エラーキーの配列が作成され、メッセージファイルで最初に定義されたものが使用されます。この配列には一般的に以下が含まれます。

["error.invalid.<fieldName>", "error.invalid.<type>", "error.invalid"]

エラーキーは Spring DefaultMessageCodesResolver によって作成され、ルートの「typeMismatch」は「error.invalid」に置き換えられます。

§高度な検証

Play の組み込み検証モジュールは、内部で Hibernate Validator を使用しています。つまり、 `JSR-380` (Bean Validation 2.0) で定義されている機能を利用できます。Hibernate Validator のドキュメントは こちらにあります。

§クロスフィールド検証

オブジェクト全体の状態を検証するには、 クラスレベルの制約 を使用できます。
独自のクラスレベルの制約を実装するという負担から解放するために、Play は、少なくとも最も一般的なユースケースをカバーするそのような制約の一般的な実装をすぐに提供します。

それでは、これがどのように機能するかを見てみましょう。アドホック検証を定義するには、Play が提供するクラスレベルの制約 (`@Validate`) でフォームクラスにアノテーションを付け、対応するインターフェース (この場合は `Validatable`) を実装するだけです。これにより、`validate` メソッドをオーバーライドすることが強制されます。

import play.data.validation.Constraints;
import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;

@Validate
public class User implements Validatable<String> {

  @Constraints.Required protected String email;
  protected String password;

  @Override
  public String validate() {
    if (authenticate(email, password) == null) {
      // You could also return a key defined in conf/messages
      return "Invalid email or password";
    }
    return null;
  }

  // getters and setters

上記の例で返されるメッセージは、グローバルエラーになります。エラーは `play.data.validation.ValidationError` として定義されています。
また、この例では、 `validate` メソッドと `@Constraints.Required` 制約が同時に呼び出されることに注意してください。つまり、 `@Constraints.Required` が成功したかどうかに関係なく、 `validate` メソッドが呼び出されます (逆も同様です)。順序の導入方法については、後で説明します。

`Validatable` インターフェースは型パラメーターを受け取るため、 `validate()` メソッドの戻り値の型が決まります。
したがって、単一のグローバルエラー、1 つのエラー (グローバルエラーの場合もある)、または複数の (グローバルエラーの場合もある) エラーを `validate()` を介してフォームに追加できるかどうかによっては、型引数として `String`、 `ValidationError`、または `List` を使用する必要があります。`validate` メソッドの他の戻り値の型は、Play によって無視されます。

`validate()` メソッド内で検証が成功した場合は、 `null` または空の `List` を返す必要があります。その他の `null` 以外の値 (空の文字列を含む) を返すことは、検証失敗として扱われます。

`ValidationError` オブジェクトを返すことは、特定のフィールドに追加の検証がある場合に役立ちます。

import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;
import play.data.validation.ValidationError;

@Validate
public static class LoginForm implements Validatable<ValidationError> {

  // fields, getters, setters, etc.

  @Override
  public ValidationError validate() {
    if (authenticate(email, password) == null) {
      // Error will be displayed for the email field:
      return new ValidationError("email", "Invalid credentials");
    }
    return null;
  }
}

List<ValidationError> を返すことで、複数のバリデーションエラーを追加できます。これを使用すると、特定のフィールドに対するバリデーションエラー、グローバルエラー、またはこれらのオプションを組み合わせたものを追加できます。

import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;
import play.data.validation.ValidationError;
import java.util.List;
import java.util.ArrayList;

@Validate
public static class SignUpForm implements Validatable<List<ValidationError>> {

  // fields, getters, setters, etc.

  @Override
  public List<ValidationError> validate() {
    final List<ValidationError> errors = new ArrayList<>();
    if (authenticate(email, password) == null) {
      // Add an error which will be displayed for the email field:
      errors.add(new ValidationError("email", "Access denied"));
      // Also add a global error:
      errors.add(new ValidationError("", "Form could not be submitted"));
    }
    return errors;
  }
}

ご覧のように、ValidationErrorのキーとして空文字列を使用すると、グローバルエラーになります。

もう一点:エラーメッセージを記述する代わりに、conf/messagesで定義されたメッセージキーを使用し、それに引数を渡すことができます。テンプレートでバリデーションエラーを表示するとき、メッセージキーとその引数はPlayによって自動的に解決されます。

// Global error without internationalization:
new ValidationError("", "Errors occurred. Please check your input!");
// Global error; "validationFailed" should be defined in `conf/messages` - taking two arguments:
new ValidationError("", "validationFailed", Arrays.asList(arg1, arg2));
// Error for the email field; "emailUsedAlready" should be defined in `conf/messages` - taking
// the email as argument:
new ValidationError("email", "emailUsedAlready", Arrays.asList(email));

§グループによる部分的なフォームバリデーション

ユーザーがフォームを送信する場合、すべての制約を一度に検証するのではなく、一部の制約のみを検証したいユースケースがあります。たとえば、各ステップで指定された制約のサブセットのみを検証するUIウィザードを考えてみてください。

または、ウェブアプリケーションのサインアップとログインのプロセスを考えてみてください。通常、どちらのプロセスでも、ユーザーはメールアドレスとパスワードを入力する必要があります。したがって、これらのプロセスはほぼ同じフォームを必要としますが、サインアッププロセスでは、ユーザーはパスワードの確認も入力する必要があります。さらに複雑にするために、ユーザーが既にログインしている状態で設定ページでユーザーデータを変更できることを想定してみましょう。これは3番目のフォームが必要になります。

このような場合に3つの異なるフォームを使用するのは、実際には良いアイデアではありません。ほとんどのフォームフィールドには同じ制約アノテーションを使用するからです。nameフィールドの最大長制約を255に定義し、後で100に変更したい場合はどうでしょうか?各フォームでこれを変更する必要があります。想像できるように、フォームの1つを更新し忘れた場合、これはエラーが発生しやすいでしょう。

幸いなことに、簡単に制約をグループ化できます。

import javax.validation.groups.Default;
import play.data.validation.Constraints;
import play.data.validation.Constraints.Validatable;
import play.data.validation.Constraints.Validate;
import play.data.validation.ValidationError;

@Validate(groups = {SignUpCheck.class})
public class PartialUserForm implements Validatable<ValidationError> {

  @Constraints.Required(groups = {Default.class, SignUpCheck.class, LoginCheck.class})
  @Constraints.Email(groups = {Default.class, SignUpCheck.class})
  private String email;

  @Constraints.Required private String firstName;

  @Constraints.Required private String lastName;

  @Constraints.Required(groups = {SignUpCheck.class, LoginCheck.class})
  private String password;

  @Constraints.Required(groups = {SignUpCheck.class})
  private String repeatPassword;

  @Override
  public ValidationError validate() {
    if (!checkPasswords(password, repeatPassword)) {
      return new ValidationError("repeatPassword", "Passwords do not match");
    }
    return null;
  }

  // getters and setters

SignUpCheckLoginCheckグループは、2つのインターフェースとして定義されています。

public interface SignUpCheck {}
public interface LoginCheck {}

サインアッププロセスでは、form(...)メソッドにSignUpCheckグループを渡します。

Form<PartialUserForm> form =
    formFactory().form(PartialUserForm.class, SignUpCheck.class).bindFromRequest(request);

この場合、メールアドレスは必須であり、有効なメールアドレスである必要があります。パスワードとパスワードの確認の両方が必須であり、2つのパスワードは等しくなければなりません(@Validateアノテーションによってvalidateメソッドが呼び出されるためです)。しかし、名と姓は気にしません。空でも構いませんし、サインアップページでこれらの入力フィールドを削除することもできます。

ログインプロセスでは、代わりにLoginCheckグループを渡します。

Form<PartialUserForm> form =
    formFactory().form(PartialUserForm.class, LoginCheck.class).bindFromRequest(request);

これで、メールアドレスとパスワードの入力のみが必須になります。それ以上は必要ありません。メールアドレスが有効かどうかは気になりません。検証しないため、他のフォームフィールドをユーザーに表示することもないでしょう。

ユーザーがユーザーデータ(パスワードを除く)を変更できるページがあるとします。

Form<PartialUserForm> form =
    formFactory().form(PartialUserForm.class, Default.class).bindFromRequest(request);

これはまさに以下のと同じです。

Form<PartialUserForm> form =
    formFactory().form(PartialUserForm.class).bindFromRequest(request);

この場合、以下の制約が検証されます。メールアドレスは必須であり、有効である必要があります。また、名と姓も必須です。これは、制約アノテーションがgroupを *明示的に* 定義していない場合、Defaultグループが使用されるためです。
パスワードの制約は何もチェックしていないことに注意してください。これらはgroup属性を *明示的に* 定義していますが、Defaultグループを含んでいないため、ここでは考慮されません。

最後の例のように、javax.validation.groups.Defaultグループのみを渡す場合、省略できます。これはデフォルトであるためです。
しかし、他のグループを渡すとすぐに、バリデーションプロセスでそのフィールドを考慮したい場合は、Defaultグループを *明示的に* 渡す必要があります。

ヒント:form(...)メソッドには、好きなだけ多くのグループを渡すことができます(1つだけではありません)。明確にしておくと、これらのグループは順番ではなく、同時に検証されます。

高度な使用方法では、制約のグループに別のグループを含めることができます。グループの継承を使用することでこれを行うことができます。

§制約グループの順序の定義

グループをシーケンスで検証できます。これは、グループが順番に検証されることを意味します。ただし、次のグループは、前のグループが正常に検証された場合にのみ検証されます。(ただし、現時点では、グループ *内* で制約が検証される順序を決定することはできません。これはBean Validationの将来のバージョンの一部になります。 Bean Validationの将来のバージョンの一部となるでしょう

上記の例に基づいて、グループシーケンスを定義します。

import javax.validation.GroupSequence;
import javax.validation.groups.Default;

@GroupSequence({Default.class, SignUpCheck.class, LoginCheck.class})
public interface OrderedChecks {}

これで使用できます。

Form<PartialUserForm> form =
    formFactory().form(PartialUserForm.class, OrderedChecks.class).bindFromRequest(request);

このグループシーケンスを使用すると、最初にDefaultグループに属するすべてのフィールド(グループを定義していないフィールドも含まれます)が検証されます。Defaultグループに属するすべてのフィールドが正常に検証された場合にのみ、SignUpCheckに属するフィールドが検証されます。

グループシーケンスを使用することは、データベースをクエリしたり、他のブロッキングアクションを実行するvalidateメソッドがある場合に特に有効です。バリデーションが基本レベルで失敗した場合(メールアドレスが無効、数値が文字列など)、メソッドを実行しても意味がありません。そのような場合、他のすべての注釈ベースの制約をチェックした後、そしてそれらがパスした場合にのみ、validateを呼び出したいでしょう。たとえば、サインアップするユーザーは有効なメールアドレスを入力する必要があり、それが有効な場合にのみ、その後でメールアドレスのデータベース検索を行う必要があります。

§バリデータへのペイロードの渡す

必要に応じて、ValidationPayloadオブジェクト(バリデーションプロセスに必要な有用な情報を含む)をvalidateメソッドに渡すこともできます。
このようなペイロードを渡すには、フォームに@ValidateWithPayload@Validateではなく)アノテーションを付け、ValidatableWithPayloadValidatableではなく)を実装します。

import java.util.Map;

import com.typesafe.config.Config;

import play.data.validation.Constraints.ValidatableWithPayload;
import play.data.validation.Constraints.ValidateWithPayload;
import play.data.validation.ValidationError;
import play.data.validation.ValidationPayload;

import play.i18n.Lang;
import play.i18n.Messages;

@ValidateWithPayload
public class ChangePasswordForm implements ValidatableWithPayload<ValidationError>
public static class ChangePasswordForm implements ValidatableWithPayload<ValidationError> {

  // fields, getters, setters, etc.

  @Override
  public ValidationError validate(ValidationPayload payload) {
    Lang lang = payload.getLang();
    Messages messages = payload.getMessages();
    TypedMap attrs = payload.getAttrs();
    Config config = payload.getConfig();
    // ...
  }
}

§DIサポート付きのカスタムクラスレベル制約

より洗練されたバリデーションプロセスが必要になる場合があります。たとえば、ユーザーがサインアップするとき、そのメールアドレスが既にデータベースに存在するかどうかを確認し、存在する場合はバリデーションに失敗するようにします。

制約はランタイム依存性注入の両方をサポートしているため、Databaseオブジェクトを注入する独自のクラスレベル制約を簡単に作成できます。これは、後でバリデーションプロセスで使用できます。もちろん、MessagesApiJPAApiなど、他のコンポーネントを注入することもできます。

注:クロス懸念事項ごとに1つのクラスレベル制約のみを作成する必要があります。たとえば、このセクションで作成する制約は再利用可能であり、データベースにアクセスする必要があるすべてのバリデーションプロセスで使用できます。Playが依存性注入されたコンポーネントを含む汎用的なクラスレベル制約を提供しない理由は、Playがプロジェクトで有効にしているコンポーネントを認識できないためです。

まず、後でフォームに実装するvalidateメソッドを含むインターフェースをセットアップしましょう。このメソッドにはDatabaseオブジェクトが渡されることがわかります(データベースのドキュメントを参照してください)。

ペイロードなし
import play.db.Database;

public interface ValidatableWithDB<T> {
  public T validate(final Database db);
}
ペイロード付き
import play.data.validation.Constraints.ValidationPayload;
import play.db.Database;

public interface ValidatableWithDB<T> {
  public T validate(final Database db, final ValidationPayload payload);
}

フォームクラスに付けるクラスレベルのアノテーションも必要です。

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Repeatable(ValidateWithDB.List.class)
@Constraint(validatedBy = ValidateWithDBValidator.class)
public @interface ValidateWithDB {
  String message() default "error.invalid";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  /** Defines several {@code @ValidateWithDB} annotations on the same element. */
  @Target({TYPE, ANNOTATION_TYPE})
  @Retention(RUNTIME)
  public @interface List {
    ValidateWithDB[] value();
  }
}

最後に、制約の実装は次のようになります。

ペイロードなし
import javax.inject.Inject;
import javax.validation.ConstraintValidatorContext;
import play.data.validation.Constraints.PlayConstraintValidator;
import play.db.Database;

public class ValidateWithDBValidator
    implements PlayConstraintValidator<ValidateWithDB, ValidatableWithDB<?>> {

  private final Database db;

  @Inject
  public ValidateWithDBValidator(final Database db) {
    this.db = db;
  }

  @Override
  public void initialize(final ValidateWithDB constraintAnnotation) {}

  @Override
  public boolean isValid(
      final ValidatableWithDB<?> value,
      final ConstraintValidatorContext constraintValidatorContext) {
    return reportValidationStatus(value.validate(this.db), constraintValidatorContext);
  }
}
ペイロード付き
import javax.inject.Inject;
import javax.validation.ConstraintValidatorContext;
import play.data.validation.Constraints.PlayConstraintValidatorWithPayload;
import play.data.validation.Constraints.ValidationPayload;
import play.db.Database;

public class ValidateWithDBValidator
    implements PlayConstraintValidatorWithPayload<ValidateWithDB, ValidatableWithDB<?>> {

  private final Database db;

  @Inject
  public ValidateWithDBValidator(final Database db) {
    this.db = db;
  }

  @Override
  public void initialize(final ValidateWithDB constraintAnnotation) {}

  @Override
  public boolean isValid(
      final ValidatableWithDB<?> value,
      final ValidationPayload payload,
      final ConstraintValidatorContext constraintValidatorContext) {
    return reportValidationStatus(value.validate(this.db, payload), constraintValidatorContext);
  }
}

注:ValidationPayloadConstraintValidatorContextを混同しないでください。前者はPlayによって提供され、Playでフォームを扱う際の日常業務で使用します。後者はBean Validation仕様で定義されており、Play内部でのみ使用されます。ただし、例外が1つあります。これは、独自のクラスレベル制約を作成する場合に表示されます。ただし、reportValidationStatusメソッドに渡すだけで済みます。

ご覧のように、Databaseオブジェクトを制約のコンストラクタに注入し、後でvalidateを呼び出すときに使用します。ランタイム依存性注入を使用する場合、Guiceは自動的にDatabaseオブジェクトを注入しますが、コンパイル時依存性注入の場合は、自分で行う必要があります。

import play.ApplicationLoader;
import play.BuiltInComponentsFromContext;
import play.data.FormFactoryComponents;
import play.data.validation.MappedConstraintValidatorFactory;
import play.db.DBComponents;
import play.db.HikariCPComponents;
import play.filters.components.NoHttpFiltersComponents;
import play.routing.Router;

public class ValidateWithDBComponents extends BuiltInComponentsFromContext
    implements FormFactoryComponents, DBComponents, HikariCPComponents, NoHttpFiltersComponents {

  public ValidateWithDBComponents(ApplicationLoader.Context context) {
    super(context);
  }

  @Override
  public Router router() {
    return Router.empty();
  }

  @Override
  public MappedConstraintValidatorFactory constraintValidatorFactory() {
    return new MappedConstraintValidatorFactory()
        .addConstraintValidator(
            ValidateWithDBValidator.class, new ValidateWithDBValidator(database("default")));
  }
}

注:databaseインスタンスは自分で作成する必要はありません。実装されたインターフェースに既に定義されています。

このようにして、必要に応じてバリデータを使用できます。

独自のクラスレベル制約を作成する場合、reportValidationStatusメソッドに次のオブジェクトを渡すことができます。ValidationErrorList<ValidationError>、またはString(グローバルエラーとして処理されます)。他のオブジェクトはPlayによって無視されます。

最後に、カスタムクラスレベル制約を使用してフォームを検証できます。

ペイロードなし
import play.data.validation.Constraints;
import play.data.validation.ValidationError;
import play.db.Database;

@ValidateWithDB
public class DBAccessForm implements ValidatableWithDB<ValidationError> {

  @Constraints.Required @Constraints.Email private String email;

  @Constraints.Required private String firstName;

  @Constraints.Required private String lastName;

  @Constraints.Required private String password;

  @Constraints.Required private String repeatPassword;

  @Override
  public ValidationError validate(final Database db) {
    // Access the database to check if the email already exists
    if (User.byEmail(email, db) != null) {
      return new ValidationError("email", "This e-mail is already registered.");
    }
    return null;
  }

  // getters and setters
ペイロード付き
import play.data.validation.Constraints;
import play.data.validation.Constraints.ValidationPayload;
import play.data.validation.ValidationError;
import play.db.Database;

@ValidateWithDB
public class DBAccessForm implements ValidatableWithDB<ValidationError> {

  @Constraints.Required @Constraints.Email private String email;

  @Constraints.Required private String firstName;

  @Constraints.Required private String lastName;

  @Constraints.Required private String password;

  @Constraints.Required private String repeatPassword;

  @Override
  public ValidationError validate(final Database db, final ValidationPayload payload) {
    // Access the database to check if the email already exists
    if (User.byEmail(email, db) != null) {
      return new ValidationError("email", "This e-mail is already registered.");
    }
    return null;
  }

  // getters and setters

ヒント:複数のインターフェースを実装し、したがってフォームクラスに複数のクラスレベル制約アノテーションを追加できることに気付いたかもしれません。バリデーショングループを使用して、目的のvalidateメソッド(または1つのバリデーションプロセスで複数を一度に)を呼び出すことができます。

次へ:CSRFからの保護


このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。ドキュメントのガイドラインを読んだ後、プルリクエストを送信してください。質問やアドバイスを共有したいですか?コミュニティフォーラムにアクセスして、コミュニティとの会話を開始してください。