Creating a generic validator for arrays of strings in Lithium

Today i needed to validate some input from a form before passing it into MongoDB in Lithium and found a shortcoming in the inbuilt validation system. It does not support array of values.

Consider the following simple validation rule on a string input that works for blocking empty string for appearing in your database:

<?php
class Bar extends \lithium\data\Model                                                                                                                                                                          
{       
    public $validates = array(                                                                                                                                                                                     
        'foo' => array(                                                                                                                                                                                      
            array(                                                                                                                                                                                                 
                'notEmpty',
                'message' => 'Must have foo',
            ),
        ),
    );
}

Try passing an array to “foo” and it fails nicely. After filing a ticket for this kind of feature and discussed it on the IRC channel, I sat down and created a middleground solution by building my own validator that reused the inbuilt validator patterns for strings, only supporting array usage. Check this out:

<?php
Validator::add('array', function(&$value, $format = null, array $options = array()) {
    $options += array(
        'threshold' => 0,
        'validator' => 'notEmpty'
    );
    if (is_array($value)) {
        $failed = 0;
        $valueCount = count($value);
        foreach ($value as $item) {
            if (is_string($item) && !Validator::rule($options['validator'], $item))
                $failed++;
        }
        /**
         * Threshold meanings:
         * 0 = Ever set value must validate
         * +x = At least x values must validate
         * -x = At max x values can fail
         */
        if ($options['threshold'] == 0)
            return $failed == 0;
        elseif ($options['threshold'] < 0)
            return ($failed <= abs($options['threshold']));                                                                                                                                                        
        else                                                                                                                                                                                                       
            return (($valueCount - $failed) >= $options['threshold']);                                                                                                                                             
    }                                                                                                                                                                                                              
    elseif (is_string($value))
        return Validator::rule($options['validator'], $value);                                                                                                                                                     
    return true;
}); 
        
class Bar extends \lithium\data\Model                                                                                                                                                                          
{       
    public $validates = array(                                                                                                                                                                                     
        'foo' => array(                                                                                                                                                                                      
            array(                                                                                                                                                                                                 
                'array',                                                                                                                                                                                           
                'validator' => 'notEmpty',
                'message' => 'Must have foo',
                'threshold' => 1 // Need at least on
            ),
        ),
    );
}

I hope we can see this functionality in Lithium itself, but for now this little validator allows you to reuse any (not tested on all) validations found in Validator. Enjoy