Conditional Validation in CakePHP

Main Thread 3 min read

I've been using CakePHP as the framework for an Intranet project. Although new to CakePHP, I knew that for a long term project an MVC, rapid development framework provided a strong foundation. That has proven itself time and again in the last 9 months. Sometimes though, determining how to do something within a framework can be difficult.

The other day, I had a requirement for one of the applications for conditional data validation. CakePHP provides flexible and simple validation of your Model data that combined forms very complex rules. Unfortunately, nothing out of the box handled conditional validation.

To be fair, there are some native options that may work for you in simple cases. You could add 'allowEmpty' => true to your rules. Yet, setting anything at the Model rule level applies across all Controllers. Conditional validation validates the Model one way in Controller A, but differently in Controller B. Yes, I know that's lame for data integrity. But we developers must support real world demands from clients. Anyway, there are also ways to validate from the controller. These looked promising. But in practice, the code quickly becomes bloated. You begin to have calls to validate() before every save(). Even more for multi-model saveAll() calls. You also have to list the Model fields to validate in the Controller. Too much coupling in my opinion. So this becomes a maintenance issue beyond a few isolated cases. Another alternative is create custom validation rules. Similar to the above though, code becomes bloated outside a few rules. In addition, you are now responsible for validating the data yourself and can't take advantage of CakePHP's core rules (e.g. email, ssn).

Before I began developing anything, I did a quick Google search. The only thing I found was an outdated Model Behavior. So I decided to make my own with the following goals:

  • I wanted it to be as native as possible. This meant keeping the core validation rules. I merely wanted to enable, disable, or modify validation rules on the fly.
  • I wanted to keep with Fat Models, Skinny Controllers. Meaning low coupling – always a plus. This way the controller simply tells the model how to validate from a high level. Not explicitly what to validate.

So here's what I came up with. Validation rules could be set by passing a condition to a Model method. All the logic to setup the appropriate validation is encapsulated within this method. The controller simply calls this function with the argument to perform conditional validation for that Model. An example call would be:

1$this->Employee->setValidationRules('it');
2// ...
3$this->Employee->save($this-data);

If no conditions are passed, the Model would use the default validation rules. In order to set these automatically, I overrode the constructor method. The reason I put this here instead of leaving it as a Model property was to allow the Controller to reset the validation rules.

1function __construct($id = false, $table = null, $ds = null) {
2 parent::__construct($id, $table, $ds);
3 
4 $this->setValidationRules();
5}
6 
7function setValidationRules($condition = null) {
8 if ($condition == 'it') {
9 // turn off field requirements for nea_it users as they don't have this data yet
10 unset($this->validate['computer_type']);
11 unset($this->validate['computer_service_tag']);
12 $this->validate['company_email'] => array('boolean' => array('rule' => array('email', 'allowEmpty' => true)));
13 }
14 else {
15 // default validation rules
16 $this->validate = array('rental_car_cards' => array('boolean' => array('rule' => array('boolean'))),
17 'company_car' => array('boolean' => array('rule' => array('boolean'))),
18 'company_car_allowance' => array('boolean' => array('rule' => array('boolean'))),
19 'company_cell_phone' => array('boolean' => array('rule' => array('boolean'))),
20 'company_cell_phone_plan' => array('dependent' => array('rule' => array('notempty'))),
21 'computer' => array('boolean' => array('rule' => array('boolean'))),
22 'computer_type' => array('dependent' => array('rule' => array('notempty'))),
23 'computer_service_tag' => array('dependent' => array('rule' => array('notempty'))),
24 'company_email' => array('boolean' => array('rule' => array('email'))));
25 }
26}

It's primitive. But it does satisfy the client's spec and meets my goals. I think adding some callback hooks to reset the validation automatically could be helpful. Then conditional validation would behave much like bindModel() or $this->Model->recursive where it only effects the next call to the Model. Which is more Cakeish. Yet validating data on the same Model under two different conditions in the same request is probably very rare. In the end, I think it's a straightforward way to do conditional validation in CakePHP.

Find this interesting? Let's continue the conversation on Twitter.