On input validation 2

by Naneau

I posted about it yesterday. I’ve thought about it some more. Wrote some code. And I’m happy! I can validate input without fuss again. Yay. I really did get tired from writing trivial form validation rules into my controllers time and time again. Allow me to demonstrate my new approach.

First, I think about what fields I need, and what kind of validation they require. I subclass my validator class ‘Naneau_Validator_Abstract’, which does little more than apply validators to fields and store error messages. In the constructor I add Zend_Validate objects to fields:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

require_once 'Naneau/Validator/Abstract.php';

require_once 'Zend/Validate/StringLength.php';
require_once 'Zend/Validate/Regex.php';
require_once 'Zend/Validate/Alnum.php';
require_once 'Zend/Validate/Digits.php';

class Naneau_Validator_Demo extends Naneau_Validator_Abstract {

    public function __construct() {
        $this->setRequired('name', 'Name is a required field');
        $this->addFieldValidator('name', new Zend_Validate_StringLength(1,150), 'Name must be between 1 and 5 characters long, yes, 5!');
        //name

        $this->addFieldValidator('numeric', new Zend_Validate_Digits(), 'Numeric must be... numeric.');
        //alnum

        $this->setRequired('alnum', 'Alnum is a required field');
        $this->addFieldValidator('alnum', new Zend_Validate_Alnum(), 'Alnum must be alpha-numeric, d\'oh!');
        //alnum

        $this->setRequired('url', 'Url is a required field');
        $this->addFieldValidator('url', new Zend_Validate_Regex('/^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\//i'), 'Url has to be valid, try adding a trailing "/"');
        //url
    }
}

This will give me a nice validator class, that I can use in my controllers like:

1
2
3
4
5
6
7
8
9
10
11
12
13
function someAction() {
    Zend_Loader::loadClass('Naneau_Validator_Demo');
    $validator = new Naneau_Validator_Demo();
    //the validator (in this case, for 'demo')

    if (!empty($_POST) && $validator->validateArray($_POST)) {
        //save whatever it is I wanted to validate
        //redirect
    }

    //display a form
    //assign error messages to the view
}

This pattern is very reusable, and I think I can even go as far as to automate it. Right now it’s based on Zend Framework classes. But the same principle works for other frameworks as well. The concept of writing a validator, that is decoupled from the form it will be used to validate, stays the same.

But wait, there’s more! If you have a standard validator that can validate an array of values, why not do it using ajax calls. See this little demo for how handy it can be. It uses the same validator class, and just a very basic controller to validate it. If you put all your validator classes in the same directory, it’s trivial to find one for each form you output, and add form validation unobtrusively and automatically. It means that you don’t have to write any validation rules in JavaScript. All you have to do is add an id attribute to the form element, which the controller could then match to a validator class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * What to put in front of validator classes
 *
 * @var string
 */

private $_prepend = 'Naneau_Validator_';

public function formAction() {
    if ($validator = $this->getValidator()) {
        $return = array(
        'success' => $validator->validateArray($_POST),
        'messages' => $validator->getMessages()
        );

        $this->sendJsonResponse($return);
    }
    else {
        $this->sendJsonResponse(array('success' => true));
        //no validation means... true!
        //yes, that doesn't make any sense :x
    }
}

/**
 * get validator out of request
 * @return Naneau_Validator_Abstract
 */

private function getValidator() {
    try {
        $validator = $this->_prepend . ucfirst($this->getRequest()->getParam('form'));
        Zend_Loader::loadClass($validator);
        return new $validator;
    }
    catch (Exception $e) {
        return false;
    }
}

Note that the original controller also has a function to validate a single field. But validating fields on each blur through an ajax call causes quite a bit of overhead ;) .

Anyway, there it is! Naneau Validator Abstract