Confusing Validation vs. Application Rules in CakePHP3
Asked Answered
G

1

7

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?

Ghetto answered 27/6, 2015 at 10:55 Comment(0)
V
5

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.

Veator answered 27/6, 2015 at 20:14 Comment(6)
So to avoid surprises while saving data it IS the best way to always use validation also as application rules? I think this would be the only way to make sure the data is still valid if the entity is manipulated between creating and saving the entity? Can you give some concrete examples when the application rules are absolutely better then validation (no code, just use cases where there is a big advantage)? Because I think with conditional validation I can do mostly the same as with application rulesTetrapody
There's the example with the free shipping as application rule. I could also do it with a conditional validation return $context['data']['price'] < 100 && $context['data']['shipping_mode'] === 'free';Tetrapody
I already gave an example in my answer: Application rules run in a transaction, so they are the only way to ensure the data will remain consistent. Specially for rules that involve calculations.Cutinize
And yes, if you are in doubt of how your application data may come from, I'd suggest using Validation as application rules.Cutinize
Ok, that makes sense - in the conditional validation I can not be sure if the context data is valid. But another question was also how can I make an application rule only running in a specific action? Example: Free shipping should always be validated (as application rule) when the user enters the form, but not if the admin uses another form in the same table (bad example but you should get the point). I can define which validation to use at patchEntity with ['validate' => 'customValidationForThisAction'], but not which application rules (besides general addCreate/addUpdate)?Tetrapody
No, you cannot. When rules are dependent on use cases, that meas they belong to Validation. Application rules are about the absolute truth in your app. What you can have is a rule detecting the originating user and check upon that. You cal also alter the rules at runtime by calling $table->rulesChecker() and operating directly on the object before calling save()Cutinize

© 2022 - 2024 — McMap. All rights reserved.