Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusing Validation vs. Application Rules in CakePHP3

Multiple questions about validation which may belong together, because they are all kind of adressing the new validation concept in CakePHP 3.

I have read the chapters (1, 2, 3) in the cookbook multiple times but honestly I don't understand how to do it the right way. I also know there is currently a issue/discussion at GitHub about the Validation in CakePHP3 which may address the same topic.

Validation errors are triggered e.g. with patchEntity. So I would think it is better to ALWAYS check/display errors BEFORE doing the save action:

// src/Controller/UsersController.php
public function add() {
  $user = $this->Users->newEntity();
  if ($this->request->is('post')) {
    $user = $this->Users->patchEntity($user, $this->request->data, ['validate' => 'default'] );
    if ( $user->errors() ) {
      $this->Flash->error('There was a Entity validation error.');
    } else {
      // Optional: Manipulate Entity here, e.g. add some automatic values
      // Be aware: Entity content will not be validated again by default
      if ( $this->Users->save($user) ) {
        $this->Flash->succeed('Saved successfully.');
        return $this->redirect(['controller' => 'Users', 'action' => 'index']);
      } else {
        $this->Flash->error('Not saved - ApplicationRule validation error.');
      }
    }
  }
  $this->set('user', $user);
}

Why do the cookbook tutorials not make use of $user->errors() before saving data? As far as I understand it save doesn't need to be called if there was a validation error already?! Another way would be to combine the error-check and save action:

if ( !$user->errors() && $this->Users->save($user) ) {
  $this->Flash->succeed('Saved successfully.');
  return $this->redirect(['controller' => 'Users', 'action' => 'index']);
} else {
  $this->Flash->error('There was a validation OR ApplicationRule error.');
}

Are you using this? Should I use it? Or if not, why not?

Why is CakePHP showing the validation errors even if I do NOT use $user->errors() in the controller, like in all the cookbook examples? I thought save will NOT check the entity validation?!

Example: isUnique

According to the cookbook "Ensuring email uniqueness" is a use case for application rules.

// src/Model/Table/UsersTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\RulesChecker;
use Cake\ORM\Rule\IsUnique;
// Application Rules
public function buildRules(RulesChecker $rules) {
  $rules->add($rules->isUnique(['email'], 'This email is already in use'));
  return $rules;
}

The error would only be triggered with a save-call in the controller. But it is also possible to check uniqueness in the validation. Why is it better to NOT do it this way?

// src/Model/Table/UserTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
public function validationDefault(Validator $validator) {
  $validator
    ->add('email', [
      'unique' => [
        'rule' => 'validateUnique',
        'provider' => 'table',
        'message' => 'This email is already in use'
        ],
      ])
  return $validator;
}

If I can add the ApplicationRule in the Validation, why would/should I use ApplicationRules at all?

How can I define in the ApplicationRule WHEN the Rule should be applied only in a specific action (not all create/update calls)?

I also don't see or understand the benefit of the two separated validation states when the entity is manipulated after the patchEntity-call.

In case I add some values automatically to the entity, I want to be sure that the values are all still valid before saving them to the database (as in CakePHP2). So I would guess it's better/nessecary to ALWAYS Using Validation as Application Rules?!

How are you dealing with this in general? Are there other examples available to show/demonstrate the benefit and some use cases of the Validation vs. ApplicationRules?

like image 908
Oops D'oh Avatar asked Jun 27 '15 10:06

Oops D'oh


1 Answers

I think your main source of confusion is that you don't know that save() will not save an entity if it contains errors already. For example:

$entity = $users->newEntity(['email' => 'not an email']);
$users->save($entity); // Returns false

The reason it will return false is because save() reads the $entity->errors() result before proceeding with the actual saving process. It is, then, unneeded to check for errors manually before calling save(), just as the examples in the manual show.

The email uniqueness example is kind of tricky, because it is something that you want to check both for user facing forms (what validation is targeted for) and in application rules.

It is important to remember that Validation, as in the validation*() methods, is meant for giving feedback to humans about the data they are providing. You would like to present all errors in a form (including errors for nested properties) before the saving process start.

Since Validation rarely happens inside a database transaction, there is no actual guarantee that between validation and saving the email would still be unique. This is one of the things application rules is trying to solve: Application rules do run in the same transaction as the rest of the saving process, so any checks donde there will have a consistency guarantee.

Another problem application rules solve is that they can work on data that has already been set on the entity, hence you have full access to the current state of an object. That is not possible when patching an entity or when creating a new one, since any passed data is potentially inconsistent.

like image 105
José Lorenzo Rodríguez Avatar answered Sep 23 '22 15:09

José Lorenzo Rodríguez