Maximum power for your validation rules

I already postet an article about custom validation rules some time ago here.
In this post I introduce some of my custom rules which come in handy quite often.

For being able to use it in different projects I do not use the AppModel for it but a plugin Model class. The AppModel then extends this MyModel class.
I do not want to post the methods here in the blog because they might change over time. You can find the current source code at github.
For Cake2.x the class is: MyModel.

You include it when declaring your AppModel:

App::uses('MyModel', 'Tools.Model');

class AppModel extends MyModel {
}

Don’t forget to CakePlugin::load('Tools') the plugin in your bootstrap.

Lets get started.
The following code fragments are part of the $validate array of the specific model you want to validate.

Validating uniqueness

With this enhanced method we can check on other fields – dependent uniqueness so to speak. It can do a little bit more than the core one.

'field' => array(
    'validate' => array(
        'rule' => array('validateUnique', array('user_id')),
        'message' => 'You already have an entry',
    ),
),

In this case the field will only invalidate if this specific user has already an entry.
Two users can have the same field content without interfering with each other.

You can check on as many dependent fields as you want, if the tables are related even across relations:

'field' => array(
    'validate' => array(
        'rule' => array('validateUnique', array('user_id', 'active', 'Role.key')),
        'message' => 'You already have an entry as active member and with this role',
    ),
),

Just make sure recursive/contain is correctly set up and it should all work out just fine.

Note: There is now also a contemporary 2.x behavioral approach to this. And the core rule unique is also supposed to be able to check for multiple dependent fields via array syntax. Never tried that, though.

Validating urls

The core rule does not validate "deep" – meaning it cannot check if the url is actually accessible/correct.
My custom rule works like this:

'field' => array(
    'validateUrl' => array(
        'rule' => array('validateUrl', array('autoComplete'=>true)),
        'message' => 'Not a valid url',
    ),
),

By default, it will check deep and make sure the url actually exists.
With the autoComplete param you can be more flexible with urls that are missing "http://" etc (note/todo: it would be even better if it would save the autocompleted string). Especially in combination with strict=>true.

If we want to allow only links on the same domain we could say

'field' => array(
    'validateUrl' => array(
        'rule' => array('validateUrl', array('autoComplete'=>true, 'sameDomain'=>true)),
        'message' => 'Please provide a valid url on the same domain',
    ),
),

Now /some/link as well as http://samedomain.com/some/link works.
With deep=>false we can disable the deep check for availability.

Validating dates and times

The main improvements are the before/after params:

'end_date' => array(
    'validate' => array(
        'rule' => array('validateDatetime', array('after'=>'start_date')),
        'message' => 'Please provide a valid date later than the start date',
    ),
),

There are also date and time only versions like

'time' => array(
    'validate' => array(
        'rule' => array('validateTime', array('allowEmpty'=>true)),
        'message' => 'Please provide a valid time or leave the field empty',
    ),
),

I also had to hack around problems regarding empty strings or partially invalid dates. thats why this method is quite long compared to others.

Validating keys (primary/foreign)

'id' => array(
    'validate' => array(
        'rule' => array('validateKey'),
        'message' => 'Invalid primary key',
    ),
),
'foreign_id' => array(
    'validate' => array(
        'rule' => array('validateKey', array('allowEmpty'=>true)),
        'message' => 'Invalid foreign key',
    ),
),

The id can be either aiid (int10) or uuid (char36), the method will always validate correctly.
Same goes for foreign_id. But due to the allowEmpty it can also be left empty (or 0 for aiids).

Validating enums

Sometimes the enums are "dynamically" generated. You cannot use the build in "inList" validation rule then.
If you have a method set up, you can call this from within the rule:

'field' => array(
    'validate' => array(
        'rule' => array('validateEnum', 'methodX'),
        'message' => 'Invalid value',
    ),
),

Somewhere in the model define your custom enum value generator:

function methodX() {
  return array(...);
}

Validating identical

'email_confirm' => array(
    'validate' => array(
        'rule' => array('validateIdentical', 'email'),
        'message' => 'The two fields do not match',
    ),
),

As you can see this rule comes in handy if you need to confirm a string to another in the post data. Simply pass the field name along as second param.

Last Words

Hopefully many other cake developers find those enhanced rules useful and maybe they set the core team thinking about implementing some of the features in the future versions of cake.

Please note:
Some of the internal methods, constants etc used might be not available without stuff from my Tools plugin.
Those validation methods above might have to be adjusted to your application. Same goes for the test case which uses some of my own methods/libs to work with.

Update 2013-08

Cake1.x syntax updated to Cake2.x

3 Comments

  1. Excellent post, but unfortunately the required external libraries are missing (Tools.Common, Tools.BlockedEmail and indisposable)…

  2. I agree – its not ideal.
    But this is supposed to be a guide on how to do it.
    The exact implementation can still be done yourself.

    Anyway, I will try to provide the missing dependencies ASAP.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.