zen of coding

Set::merge() and dynamic validation rules

Here’s another trick with Set::merge()…

Let’s say we’ve defined some basic validation rules in our Profile model, something like:

var $validate = array (
   'name' => array(
      'rule' => array('notEmpty'),
      'required' => false,
      'message' => 'Please enter a name'
     )

  .... and so on for all other fields ...

);

Now we’ve got an action where this particular set of rules isn’t going to fly…

Maybe the data is coming from an external (somewhat untrusted) source and in this case we need to make sure that the ‘name’ is present in the data array (i.e. ‘required’=>true) and that it’s not only ‘notEmpty’, but now it has to be ‘alphaNumeric’.

So we do an easy “update” to our default rules, which we’ve previously defined in the model:

$this->Profile->validate = Set::merge($this->Profile->validate, array(
      'name'=>array(
         'required'=>true,
         'rule'=>array('alphaNumeric')
      )
));

What happened is that now all our default rules remain as we have defined them in the model, but the ‘name’ field is now going to be validated using this new set of rules.

The theory is that you set your $validate array with some default rules that will apply in most of the situations, and use this technique when your validation rules have to change for a few fields in any given action.

To make things even cleaner you should probably add a method to AppModel called something like modifyValidate, and then you could use:

$this->Profile->modifyValidate(… array of new rules…);

  • GreyCells

    You can also define multiple ‘sets’ of validation rules within the model and apply them in the beforeValidate() callback via the ‘options’ arg to save(). This is the the second argument to save() that used to be just validate: true or false, but can now be an array of options that is passed to beforeValidate() and validates().

    I don’t think the API is up to date yet, but I’m pretty sure this is in RC2.

  • teknoid

    @GreyCells

    Interesting, I haven’t heard about this. Do you have an example of how that works? Does it also merge/override default rules set in the model?

  • Jonathan Snook came up with another method: http://snook.ca/archives/cakephp/multiple_validatable_behavior/

  • GreyCells

    The only ‘options’ values that are acted on automatically in the model are ‘validate (boolean), ‘callbacks’ (boolean) and ‘fieldList’ (array, which becomes $model->whitelist). Any other named lkeys are ignored, so have to be acted upon manually in the beforeValidate() or validates() method.

    There are many ways to skin this particular cat. You could pass a boolean option value:

    $this->MyModel->save(null, array(‘doMoreValidation’ => true));

    Then in beforeValidate($options) or validates($options) you would check if $options[‘doMoreValidation’] is set and true, then take the appropriate action.

    Or you could setup a bunch of different rule sets (only var validate gets acted upon automatically) and substitute the appropriate one in beforeValidate().

    Or (v2.0), you could pass a replacement/merge validation array and merge/replace in beforeValidates().

    It all happens in the first dozen or so lines of Model->save() (co a recent svn) – that $options var is the one that’s passed to beforeValidate() and validates().

  • teknoid

    @Richard@Home

    Yep, I saw that one a little while ago. Nice solution, it’s very flexible, but seems a little extra if you need something basic.

  • teknoid

    @GreyCells

    Thanks for sharing, I’m going to do some investigating there ;)

  • andyroo2000

    This totally saved my life. It looks like it’s much easier to do this all in the model in newer versions of cakephp, but my app is on 1.3, so this seems like the easiest way to make separate validations rules for an admin. Thanks!

%d bloggers like this: