11.4 Model validation

Using annotation tag @validate, we can define validation rules for Model properties. These constraints will be checked on Model INSERT or UPDATE.
If any of the defined rules is not respected, the Repository will throw an Exception, and an array of properties errors will be available for further processing and client notification.

Properties validation rules with @validate

/**
 * @orm
 * @validate(minLength=4, maxLength=20)
 */
protected $name;

Special rules

  • @validate(null) It allows the value to be NULL; if is NULL, no other rules will be checked.
  • @validate(empty) It allows the value to be EMPTY, if is EMPTY, no other rules will be checked.

Basic constraints

  • @validate(null)
  • @validate(true)
  • @validate(false)
  • @validate(boolean)

String constraints

  • @validate(email)
  • @validate(ip)
  • @validate(length=X) check the value string to be of X characters
  • @validate(minLength=X) check the minimum lenght of the string to be at least X characters
  • @validate(maxLength=X) check the maximum lenght of the string to be X characters
  • @validate(regex=”REGEX”) where REGEX can be any valid RegularExpression. Example: @validate(regex="/^(OPEN|CLOSED)$/") values can be only OPEN or CLOSED
  • @validate(URL) value must be a valid URL

Number constraints

  • @validate(max=X) integer value must be <= X
  • @validate(min=X) integer value must be >= X
  • @validate(range=X,Y) integer value must be X <= value <= Y

Date & time constraints

  • @validate(date)
  • @validate(datetime)
  • @validate(year)

Others

  • @validate(callback=”FUNCTION”) will call a user defined function.

Some usefull examples:

<?php
namespace myproject\model;
class User {
  
  	/**
     * @orm(type="integer", primarykey, autoincrement)
  	 */
  	protected $id;
  
  	/**
     * @orm(type="boolean")
  	 * @validate(boolean)
     */
  	protected $active;
  
		/**
     * @orm
  	 * @validate(minLength=5, maxLength=20)
     */
  	protected $name;
  
		/**
     * @orm
  	 * @validate(empty, email)
     */
  	protected $email; // this can be empty string OR valid email
  
  	/**
     * @orm
  	 * @validate(null, email)
     */
  	protected $anotherEmail; // this can be NULL string OR valid email
  
		/**
     * @orm
  	 * @validate(callback=”myproject\model\User::customValidate”)
     */
  	protected $value;
  
    static function customValidate($value) {
        if …… return true;
        else return false;
    }
}

Validation subsets with @orm-validate-subset

The ORM framework has support for partial validation of a Model.

This can be useful for complex model that need to be created by a client in multiple steps (i.e. a multi-step form). In this case we can not apply all validation rules on form first step submission, the model will be always invalid!

By defining some validation subsets, we are allowed to verify only some Model properties when required.

The definition is a simple arbitrary subset name, with the list of properties:

<?php
namespace myproject\model;
/**
 * @orm(source="users")
 * @orm-validate-subset(basic="id, name, surname, age")
 * @orm-validate-subset(financial="id, taxID, bankAccount, paypal")
 */
class User {
	use \metadigit\core\db\orm\EntityTrait;
  
  // properties definition .....
 
}

Now we can use those subsets called "basic" and "financial".
This is done with the 3rd parameter of Repository insert() or update() method.

<?php
// insert only basic data, no validation check on financial data
$data = [ 'name'=>'Jack', 'surname'=>'Brown', 'age'=>22 ];
$User = $UsersRepository->insert(null, $data, 'basic');

// now add financial info
$data = [ 'taxId'=>'XYZWABC', 'bankAccount'=>'12345678', 'paypal'=>'QWERTY123' ];
$UsersRepository->insert($User->id, $data, 'financial');

Full Model validation callback

Every Repository has a predefined empty function invoked after @validate rules checking.
It has 2 parameters, the Model instance, and the validation mode:

  • TRUE
  • or a @orm-validate-subset name
<?php
namespace metadigit\core\db\orm;

class Repository {
  
  /**
   * Validate Entity.
   * Empty implementation, can be overridden by subclasses
   * @param object $Entity
   * @param string|null $validateMode
   * @return array map of properties & error codes, empty if VALID
   */
  function validate($Entity, $validateMode) {
    return [];
  }
}

This callback can be overridden in Repository implementation with custom code that perform complex validations on the whole Model object.
Example:

<?php
namespace myproject\model;

class UsersRepository extends \metadigit\core\db\orm\Repository {

	/**
	 * @param User $User
	 * @param string|null $validateMode
	 * @return array
	 */
	function validate($User, $validateMode) {
		$errors = [];
		
    // force email to be present if type ADMIN
    if($User->type == 'ADMIN' && empty($User->email))
      $errors['email'] = 'required-for-admin';
    
    // force age to be >= 18 for CUSTOMERS
    if($User->type == 'CUSTOMER' && $User->age < 18)
      $errors['age'] = 'age-18-required';
    
    // check financial rules (skip if $validateMode=='basic')
    if($validateMode == true || $validateMode == 'financial') {
      if(empty($user->bankAccount) && empty($User->paypal))
        $errors['bankAccount'] = $errors['paypal'] = 'bank-or-paypal-required';
    }
    
    return $errors;
	}
}