Data Validation

Programming maxim #1: Never trust user input. Data validation in our Model allows us to ensure that the data entered matches what we are expecting and are capable of storing in our database fields.

Basic Data Validation

As part of the database interface, the Model also performs data validation. There are many basic rules implemented in CakePHP by default, but you can also add as complex a custom rule as you would like.

Add the following functions to the /src/Model/Table/UsersTable.php for basic field validation.

public function validationDefault(Validator $validator): Validator
{
  // usernames are required when creating a user, can’t be empty, must be scalar (not an array or object), and must be unique
  $validator
    ->scalar('username')
    ->maxLength('username', 15)
    ->requirePresence('username', 'create')
    ->notEmptyString('username')
    ->add('username', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
     
  $validator
    ->scalar('password')
    ->requirePresence('password', 'create')
    ->notEmptyString('password', 'create')
    ->allowEmptyString('password', null, 'update') // Allow updating the user without updating the password
    ->minLength('password', 8, 'Passwords must be at least 8 characters long.');
     
  $validator
    ->scalar('first_name')
    ->maxLength('first_name', 30)
    ->requirePresence('first_name', 'create')
    ->notEmptyString('first_name');
      
  $validator
    ->scalar('last_name')
    ->maxLength('last_name', 30)
    ->requirePresence('last_name', 'create')
    ->notEmptyString('last_name');
      
  $validator
    ->email('email')
    ->requirePresence('email', 'create')
    ->notEmptyString('email')
    ->add('email', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
   
  return $validator;
}
    
public function buildRules(RulesChecker $rules): RulesChecker
{
  $rules->add($rules->isUnique(['username']));
  $rules->add($rules->isUnique(['email']));
     
  return $rules;
} 

Observe that we are not validating role, passkey, timeout, or slug. These are values we are creating in code, so the user should never have access to change them, therefore we don't need to worry about automatic validation for those fields, we just need to ensure when creating them that they fit our needs exactly.

Complex Validation

More complex validation is performed within the Model with either CakePHP's built in functionality (e.g. comparing two values with sameAs) or custom functions.

A common validation when creating users is to enter the password twice to ensure the confirmation of the correct value being entered. With CakePHP this is easily accomplished with a temporary virtual field (confirm_password) and the sameAs rule.

Add the following validator to the validationDefault() function:

  $validator
    ->scalar('confirm_password')
    ->requirePresence('confirm_password', 'create')
    ->notEmptyString('confirm_password', 'create')
    ->allowEmptyString('confirm_password', null, 'update')
    // password has to be listed first because that's the field being saved to the database 
    // if password has a value but confirm_password is empty, this check is not run
    ->sameAs('password', 'confirm_password', 'Passwords do not match.');

More Secure Passwords

Another common validation is to require more secure passwords. We already have an 8 character requirement above, but we should at least follow the 8/4 rule.

Add the following function to our Users Table class and update the validator for the password field:

  /**
   * Checks password for a single instance of each:
   * number, uppercase, lowercase, and special character
   *
   * @param string $password
   * @return boolean
   */
  public function checkCharacters(string $password): bool
  {
    // number
    if (!preg_match("#[0-9]#", $password)) {
      return false;
    }
    // Uppercase
    if (!preg_match("#[A-Z]#", $password)) {
      return false;
    }
    // lowercase
    if (!preg_match("#[a-z]#", $password)) {
      return false;
    }
    // special characters
    if (!preg_match("#\W+#", $password)) {
      return false;
    }
    return true;
  }
  
  public function validationDefault(Validator $validator): Validator
  {
    ...
    $validator
      ->scalar('password')
      ->requirePresence('password', 'create')
      ->notEmptyString('password', 'create')
      ->allowEmptyString('password', null, 'update')
      ->minLength('password', 8, 'Passwords must be at least 8 characters long.')
      ->add('password', 'custom', [
        'rule' => [$this, 'checkCharacters'],
        'message' => 'The password must contain 1 number, 1 uppercase, 1 lowercase, and 1 special character'
      ]);
      ...

Phone Number Validation

Now that you know how to add validation to your Model, practice adding validation to your PhoneNumbers Model in /src/Model/Table/PhoneNumbersTable.php

Multiple Datasources

By default CakePHP uses the default database connection configuration for all database communication. If, however, you want to use multiple data stores, for example an Oracle and a MySql database, or 2 different MySql databases, you need to identify the connection when it isn't default. To configure your model to always use a different datasource, add the following function to the table's Model (e.g. the /src/Model/Table/UsersTable.php). Also be sure to have configured the alternate datasource in  your /config/app_local.php file.

  /**
   * Set connection to use users datasource
   * @return string
   */
  public static function defaultConnectionName(): string
  {
    // Always use oracle datasource for Users data
    return 'oracle';
  }

 So if we have an oracle database configuration, and this function is in our Users Table class, the Users data will always use the oracle database.