文件

§處理表單提交

§啟用/停用表單模組

預設情況下,啟用 PlayJava sbt 外掛時,Play 會包含 Java 表單模組 (play-java-forms),因此如果專案中已經有 enablePlugins(PlayJava),則無需啟用任何內容。

表單模組在 PlayImport 中也可用作 javaForms,可以在 build.sbt 中搭配 libraryDependencies += javaForms 使用。

注意:如果您不使用表單,則可以使用 PlayMinimalJava sbt 外掛取代 PlayJava,以移除表單依賴項。這樣也可以移除表單模組中使用的多個傳遞性依賴項,包括多個 Spring 模組和 Hibernate 驗證器。

§定義表單

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

上述表單定義了 emailpassword 文字欄位,以及 profilePicture 檔案輸入欄位,表示對應的 HTML 表單必須定義為 multipart/form-data 編碼,才能上傳檔案。
如您所見,預設情況下,您必須定義 getter 和 setter 方法,以便 Play 能夠存取表單欄位。不過,您也可以透過在 conf/application.conf 中設定 play.forms.binding.directFieldAccess = true,來啟用「直接欄位存取」(適用於所有表單)。在此模式中,Play 會忽略 getter 和 setter 方法,並嘗試直接存取欄位

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<String,String> 產生 User 結果值,以取得文字資料,並從 Map<String, FilePart<?>> 產生 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.formatmessages() 作為引數,這是 play.18n.Messages 執行個體,定義於 JavaI18N

§使用初始預設值填寫表單

有時您會想要使用現有值填寫表單,通常是為了編輯

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

如果您想定義自訂物件與表單欄位字串之間的對應,以及反之亦然,您需要為這個物件註冊一個新的格式化器。
您可以透過註冊 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<String>) - 這會強制您覆寫 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<T> 介面採用一個類型參數,用來決定 validate() 方法的傳回類型。
因此,根據您是否想要透過 validate() 新增單一全域錯誤、一個錯誤 (也可能是全域錯誤) 或多個 (可能是全域) 錯誤到表單中,您必須使用 StringValidationErrorList<ValidationError> 作為類型引數。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 精靈,在每個步驟中只應驗證指定的約束子集。

或者想想 Web 應用程式的註冊和登入流程。通常對於這兩個流程,您都希望使用者輸入電子郵件地址和密碼。因此,這些流程所需的表單幾乎相同,除了註冊流程之外,使用者還必須輸入密碼確認。為了讓事情更有趣,我們假設使用者已經登入時,也可以在設定頁面中變更其使用者資料 - 這將需要第三個表單。

對於這種情況,使用三個不同的表單並非一個好主意,因為您無論如何都會對大多數表單欄位使用相同的約束註解。如果您已為 name 欄位定義 255 的最大長度約束,然後想要將其變更為僅 100 的限制,該怎麼辦?您必須針對每個表單變更此設定。正如您所想像的,如果您忘記更新其中一個表單,這可能會導致錯誤。

幸運的是,我們可以簡單地群組約束

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 群組定義為兩個介面

public interface SignUpCheck {}
public interface LoginCheck {}

對於註冊程序,我們只需將 SignUpCheck 群組傳遞給 form(...) 方法

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

在這種情況下,電子郵件地址是必需的,並且必須是有效的電子郵件地址,密碼和密碼確認都是必需的,並且兩個密碼必須相等(因為 @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(...) 方法(不只一個)。清楚說明:這些群組將會一次全部驗證 - 而非 依序驗證。

對於進階用法,群組約束可以包含另一個群組。您可以使用 群組繼承 來執行這項操作。

§定義約束群組的順序

您可以 依序驗證 群組。這表示群組會依序驗證 - 但只有在先前的群組驗證成功後,才會驗證下一個群組。(不過,目前無法決定群組內部約束的驗證順序 - 這將會是 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)註解您的表單,並實作 ValidatableWithPayload(而非僅 Validatable

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 等。

注意:您只需要為每個橫向問題建立一個類別層級約束。例如,我們將在本節建立的約束是可重複使用的,可用於所有需要存取資料庫的驗證程序。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 中內部使用 - 有一個例外:當您撰寫自己的自訂類別層級約束時,這個類別會出現,但您無論如何只需要將它傳遞給 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

提示:您可能已了解到,您甚至可以實作多個介面,因此可以在表單類別上新增多個類別層級約束註解。透過驗證群組,您之後可以呼叫所需的驗證方法 (或甚至在一個驗證過程中同時呼叫多個方法)。

下一步:防範 CSRF


在此文件檔中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件檔指南 後,請隨時提交拉取請求。有問題或建議要分享?請前往 我們的社群論壇 與社群展開討論。