Page 1 of 1

prewritten input sanitization classes???

Posted: Wed Oct 26, 2005 6:55 pm
by fendtele83
does any one know of any good ones? free or not free? thanks.

Re: prewritten input sanitization classes???

Posted: Wed Oct 26, 2005 7:06 pm
by Chris Corbyn
fendtele83 wrote:does any one know of any good ones? free or not free? thanks.
Check code snippets, I wrote a simple validation class, Jcart added to it, and perhaps others have since... ;)

viewtopic.php?t=34883&highlight=validation+class

Posted: Sun Oct 30, 2005 1:38 am
by McGruff
Here's one I use, which works by setting rules (objects) for different array keys:

Code: Select all

$suspect_array = array('foo'=>'bar');
$validator =& new ArrayValidator;
$validator->setRuleAt('foo', new FooRule);
$validator->setTarget($suspect_array);
$foo = $validator->getValidated('foo');
Use setRuleAt() and setRulesAt() to define validation rules for different keys (you can stack multiple rules per key if you need to). At the same time you can specify if the key is required to exist and if the value at that key is required to validate (the default). The rule interface is:

Code: Select all

class Rule
{
    function isValid() {}
    function getError() {}
}
The class was specifically designed for input validation. The methods: hasMissingKeys(), hasRequiredValueErrors(), etc can be used to decide what to do with the request eg should a form be redisplayed, does the request have bad syntax, etc.

The getValidated method will return null if any rule checks fail.

Rule objects make the validator configurable, per request (I first saw the idea in WACT).

Code: Select all

/*
    Validate array values & keys.
    Support for:
    - arrays up to 2D
    - alien key checks
    - missing key checks & optional keys
    - optional form fields (which don't have to validate)
*/
class ArrayValidator
{
    var $value_errors = array();
    var $reqval_errors = array();   // indexes whose values are required
                                    // to validate but did not
    var $missing_keys = array();
    var $alien_keys = array();
    var $unvalidated = array();     // any keys which were not checked
    var $_allowed_keys = array();
    var $_specs = array();
    var $_target = array();

    /*
        param (array)   target array index
        param (array)   an array of Rule objects
        param (bool)    $req_exists: is $index required to exist in the 
                        target array or is it an optional key?
        param (bool)    $req_val: is the value required to validate if 
                        the target array is to be considered valid.
    */
    function setRulesAt($index, &$rules, $req_exists = true, $req_val = true)
    {
        $spec =& $this->_getAt('_specs', $index);
        $spec['rules'] =& $rules;
        $spec['req_exists'] = $req_exists;
        $spec['req_val'] = $req_val;
        if( !in_array($index, $this->_allowed_keys)) {
            $this->_allowed_keys[] = $index;
        }
    }
    /*
        param (string)  target array index   [edit: type should be "array"]
        param (object) a Rule object
        param (bool)    $req_exists: is $index required to exist in the 
                        target array or is it an optional key?
        param (bool)    $req_val: is the value required to validate if 
                        the target array is to be considered valid.
    */
    function setRuleAt($index, &$rule, $req_exists = true, $req_val = true)
    {
        $spec =& $this->_getAt('_specs', $index);
        $spec['rules'][] =& $rule;
        $spec['req_exists'] = $req_exists;
        $spec['req_val'] = $req_val;
        if(!in_array($index, $this->_allowed_keys)) {
            $this->_allowed_keys[] = $index;
        }
    }
    function &_getAt($property, $index)
    {
        $p =& $this->$property;
        if(count($index) == 1) {
            return $p[$index[0]];
        }
        if(count($index) == 2) {
            return $p[$index[0]][$index[1]];
        }
    }
    /*
        param (array)   
    */
    function setTarget($target) 
    {
        $this->_target = $target;
        $this->value_errors = array();
        $this->reqval_errors = array();
        $this->missing_keys = array();
        $this->unvalidated = array();
        $this->alien_keys = array();
        $this->_alienKeyCheck();
    }
    function _alienKeyCheck() 
    {
        foreach(array_keys($this->_target) as $target_key_1st_dim) {
            if(is_array($this->_target[$target_key_1st_dim])) {
                foreach(array_keys($this->_target[$target_key_1st_dim]) 
                    as $target_key_2nd_dim) {
                    $this->_checkIndex(array(
                        $target_key_1st_dim, 
                        $target_key_2nd_dim));
                }
            } else {
                $this->_checkIndex(array($target_key_1st_dim));
            }
        }
    }
    function _checkIndex($index)
    {
        if( !in_array($index, $this->_allowed_keys)) {
            $this->_logAlienKey($index);
        }
    }
    /*
        param (array)   a target array index
        return (mixed)  value at $index if it validates; null if not
    */
    function getValidated($index)
    {
        $spec =& $this->_getAt('_specs', $index);
        $suspect = $this->_getTargetValueAt($index, $spec['req_exists']);
        if( !is_null($suspect)) {
            $validated = $this->_validateValue(
                $spec, 
                $index, 
                $suspect);
        } else {
            $validated = null;
        }
        return $validated;
    }
    function _getTargetValueAt($index, $req_exists) 
    {
        if( count($index) == 1 
            and array_key_exists($index[0], $this->_target)) {
            return $this->_target[$index[0]];
        }
        if( count($index) == 2
            and array_key_exists($index[0], $this->_target)
            and array_key_exists($index[1], $this->_target[$index[0]])) {
            return $this->_target[$index[0]][$index[1]];
        }
        if($req_exists) {
            $this->_logMissingKey($index);
        }
        return null; #!! values === null not supported
    }
    function _validateValue(&$spec, $index, $suspect)
    {
        if( !is_array($spec['rules'])) {
            return null;
        }
        $errors = array();
        foreach(array_keys($spec['rules']) as $key) {
            if( !$spec['rules'][$key]->isValid($suspect)) {
                $errors[] = $spec['rules'][$key]->getError();
            }
        }
        if( !count($errors)) {
            return $suspect;
        } else {
            $this->_logValueErrors($errors, $index);
            if($spec['req_val']) {
                $this->_logRequiredToValidateError($index);
            }
            return null;
        }
    }
    function _logAlienKey($index)
    {
        $this->alien_keys[] = $index;
    }
    function _logMissingKey($index)
    {
        $this->missing_keys[] = $index;
    }
    function _logValueErrors($errors, $index)
    {
        $ref =& $this->_getAt('value_errors', $index);
        $ref = $errors;
    }
    function _logRequiredToValidateError($index) 
    {
        $this->reqval_errors[] = $index;
    }
    /*
        return (bool)
    */
    function hasValueErrors()
    {
        return (bool)count($this->value_errors);
    }
    /*
        return (bool)
    */
    function hasRequiredValueErrors()
    {
        return (bool)count($this->reqval_errors);
    }
    /*
        return (bool)
    */
    function hasMissingKeys()
    {
        return (bool)count($this->missing_keys);
    }
    /*
        return (bool)
    */
    function hasAlienKeys()
    {
        return (bool)count($this->alien_keys);
    }
    /*
        return (array)
    */
    function getMissingKeys() 
    {
        return $this->missing_keys;
    }
    /*
        return (array)
    */
    function getAlienKeys() 
    {
        return $this->alien_keys;
    }
    /*
        return (array)
    */
    function getRequiredValueErrors()
    {
        return $this->reqval_errors;
    }
    /*
        return (array)
    */
    function getValueErrors()
    {
        return $this->value_errors;
    }
    /*
        param (array)
        return (array)
    */
    function getValueErrorsAt($index) 
    {
        $errors = array();
        if(count($index) == 1) {
            if(array_key_exists($index[0], $this->value_errors)) {
                $errors = $this->value_errors[$index[0]];
            }
        }
        if(count($index) == 2) {
            if(array_key_exists($index[0], $this->value_errors) 
                and array_key_exists($index[1], 
                $this->value_errors[$index[0]])) {
                $errors = $this->value_errors[$index[0]][$index[1]];
            }
        }
        return $errors;
    }
}
If anyone's interested, here's the unit test (which might need a few more tests to protect against some radical refactorings):

Code: Select all

class TestOfArrayValidator extends UnitTestCase 
{
    function TestOfArrayValidator()
    {
        $this->UnitTestCase();
    }

    //
    // behaviours when no specs are set
    //

    function testGetValidatedAlwaysReturnsNull()
    {
        $index_0 = array(0);
        $value_0 = 'foo';
        $index_1 = array(1);
        $value_1 = 'alien';
        $target = array($value_0, $value_1);

        $validator =& new ArrayValidator;
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);
        $this->assertIdentical($validator->getValidated($index_1), null);

        $this->assertIdentical($validator->hasValueErrors(), false);
        $this->assertIdentical($validator->getValueErrors(), array());
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array());
        
        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());
        
        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());
        
        $this->assertIdentical($validator->hasAlienKeys(), true);
        $this->assertIdentical(
            $validator->getAlienKeys(), 
            array($index_0, $index_1));        
    }
    function testNoMissingKeyErrors() 
    {
        // above
    }
    function testAllKeysAreLoggedAsAlien()
    {
        // above
    }
    
    //
    // 1st dim keys/values
    //

    function testCanDetectAlienKey()
    {
        $index_0 = array(0);
        $value_0 = 'foo';
        $index_1 = array(1);
        $value_1 = 'alien';
        $target = array($value_0, $value_1);

        $rule =& new MockExtendedRule();
        $rule->expectOnce('isValid', array($value_0));
        $rule->setReturnValue('isValid', true);
        $rule->expectNever('getError');

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule);
        $validator->setTarget($target);

        $this->assertIdentical(
            $validator->getValidated($index_0), 
            $value_0);      

        $this->assertIdentical($validator->hasValueErrors(), false);
        $this->assertIdentical($validator->getValueErrors(), array());
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array());
        
        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), true);
        $this->assertIdentical(
            $validator->getAlienKeys(), 
            array($index_1));
    }
    function testCanDetectMissingKey() 
    {
        $index_0 = array(0);
        $value_0 = 'foo';
        $target = array();

        $rule =& new MockExtendedRule();
        $rule->expectNever('isValid');
        $rule->expectNever('getError');

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule, true);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);      

        $this->assertIdentical($validator->hasValueErrors(), false);
        $this->assertIdentical($validator->getValueErrors(), array());
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array());

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), true);
        $this->assertIdentical(
            $validator->getMissingKeys(), 
            array($index_0));

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }
    function testNoMissingKeyErrorWithAbsentOptionalKey() 
    {
        $index_0 = array(0);
        $value_0 = 'foo';
        $target = array();

        $rule =& new MockExtendedRule();
        $rule->expectNever('isValid');
        $rule->expectNever('getError');

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule, false);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);      
        
        $this->assertIdentical($validator->hasValueErrors(), false);
        $this->assertIdentical($validator->getValueErrors(), array());
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array());

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }
    function testCanDetectInvalidValue()
    {
        $index_0 = array(0);
        $value_0 = 'foo';
        $target = array($value_0);

        $error_0 = 'aargh!';
        $rule =& new MockExtendedRule();
        $rule->expectOnce('isValid', array($value_0));
        $rule->setReturnValue('isValid', false);
        $rule->expectOnce('getError');
        $rule->setReturnValue('getError', $error_0);

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);        

        $this->assertIdentical($validator->hasValueErrors(), true);
        $this->assertIdentical(
            $validator->getValueErrors(), 
            array(array($error_0)));
        $this->assertIdentical($validator->getValueErrorsAt(
            $index_0), 
            array($error_0));

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            true);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array($index_0));

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }
    function testHaveErrorWithInvalidValueRequiredToValidate() 
    {
        // above
    }
    function testNoRequiredValueErrorWithInvalidValueNotRequiredToValidate() 
    {
        $index_0 = array(0);
        $value_0 = 'foo';
        $target = array($value_0);

        $error_0 = 'aargh!';
        $rule =& new MockExtendedRule();
        $rule->expectOnce('isValid', array($value_0));
        $rule->setReturnValue('isValid', false);
        $rule->expectOnce('getError');
        $rule->setReturnValue('getError', $error_0);

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule, true, false);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);        
        
        $this->assertIdentical($validator->hasValueErrors(), true);
        $this->assertIdentical(
            $validator->getValueErrors(), 
            array(array($error_0)));
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array($error_0));

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }

    //
    // 2nd dim keys/values
    //
    
    function testCanDetect2ndDimAlienKey()
    {
        $index_0 = array(0, 0);
        $value_0 = 'foo';
        $index_1 = array(0, 1);
        $value_1 = 'alien';
        $target = array(array($value_0, $value_1));

        $rule =& new MockExtendedRule();
        $rule->expectOnce('isValid', array($value_0));
        $rule->setReturnValue('isValid', true);
        $rule->expectNever('getError');

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule);
        $validator->setTarget($target);

        $this->assertIdentical(
            $validator->getValidated($index_0), 
            $value_0);      
        
        $this->assertIdentical($validator->hasValueErrors(), false);
        $this->assertIdentical($validator->getValueErrors(), array());
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array());

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), true);
        $this->assertIdentical(
            $validator->getAlienKeys(), 
            array($index_1));
    }
    function testCanDetect2ndDimMissingKey() 
    {
        $index_0 = array(0, 0);
        $value_0 = 'foo';
        $target = array();

        $rule =& new MockExtendedRule();
        $rule->expectNever('isValid');
        $rule->expectNever('getError');

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule, true);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);      
        
        $this->assertIdentical($validator->hasValueErrors(), false);
        $this->assertIdentical($validator->getValueErrors(), array());
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array());

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), true);
        $this->assertIdentical(
            $validator->getMissingKeys(), 
            array($index_0));

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }
    function testNoMissingKeyErrorWithAbsentOptional2ndDimKey() 
    {
        $index_0 = array(0, 0);
        $value_0 = 'foo';
        $target = array();

        $rule =& new MockExtendedRule();
        $rule->expectNever('isValid');
        $rule->expectNever('getError');

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule, false);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);      
        
        $this->assertIdentical($validator->hasValueErrors(), false);
        $this->assertIdentical($validator->getValueErrors(), array());
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array());

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }
    function testCanDetect2ndDimInvalidValue() 
    {
        $index_0 = array(0, 0);
        $value_0 = 'foo';
        $target = array(array($value_0));

        $error_0 = 'aargh!';
        $rule =& new MockExtendedRule();
        $rule->expectOnce('isValid', array($value_0));
        $rule->setReturnValue('isValid', false);
        $rule->expectOnce('getError');
        $rule->setReturnValue('getError', $error_0);

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);        
        
        $this->assertIdentical($validator->hasValueErrors(), true);
        $this->assertIdentical(
            $validator->getValueErrors(), 
            array(array(array($error_0))));
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array($error_0));

        $this->assertIdentical($validator->hasRequiredValueErrors(), true);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array($index_0));

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }
    function testHaveErrorWithInvalid2ndDimValueRequiredToValidate() 
    {
        // above
    }
    function testNoErrorWithInvalid2ndDimValueNotRequiredToValidate()
    {
        $index_0 = array(0, 0);
        $value_0 = 'foo';
        $target = array(array($value_0));

        $error_0 = 'aargh!';
        $rule =& new MockExtendedRule();
        $rule->expectOnce('isValid', array($value_0));
        $rule->setReturnValue('isValid', false);
        $rule->expectOnce('getError');
        $rule->setReturnValue('getError', $error_0);

        $validator =& new ArrayValidator;
        $validator->setRuleAt($index_0, $rule, true, false);
        $validator->setTarget($target);

        $this->assertIdentical($validator->getValidated($index_0), null);        
        
        $this->assertIdentical($validator->hasValueErrors(), true);
        $this->assertIdentical(
            $validator->getValueErrors(), 
            array(array(array($error_0))));
        $this->assertIdentical(
            $validator->getValueErrorsAt($index_0), 
            array($error_0));

        $this->assertIdentical(
            $validator->hasRequiredValueErrors(), 
            false);
        $this->assertIdentical(
            $validator->getRequiredValueErrors(), 
            array());

        $this->assertIdentical($validator->hasMissingKeys(), false);
        $this->assertIdentical($validator->getMissingKeys(), array());

        $this->assertIdentical($validator->hasAlienKeys(), false);
        $this->assertIdentical($validator->getAlienKeys(), array());
    }
}

Posted: Sun Nov 06, 2005 1:03 pm
by Ambush Commander
Hey, McGruff, I want to use this code in my app... does it have any specific license?

Edit - By the way... the method isValid() has a parameter, that is, it's actually isValid($mixed). That tripped me up a little when I was using it.

Posted: Sun Nov 06, 2005 2:26 pm
by McGruff
It's part of a library which I'll probably release under Open Group Test Suite License. However, just regard this as in the public domain.

As well as stopping bad values from getting anywhere near the app, ArrayValidator objects would be used to decide what to do with a particular request, eg:

Code: Select all

/*
        return (bool)
    */
    function hasValidSyntax() 
    {
        if( !$this->_GET_validator->hasAlienKeys()
            and !$this->_GET_validator->hasMissingKeys()
            and !$this->_GET_validator->hasValueErrors()
            and !count($_POST)
            and !count($_COOKIE)
            and !count($_FILES)
            return true;
        
        } else {
        
            return false;
        }
    }
Here, nothing is expected in $_POST, $_COOKIE or $_FILES. $_GET is being checked for value errors as well as making sure that only allowed keys are present and all required keys are present.

Posted: Tue Nov 08, 2005 12:20 pm
by Ambush Commander
Ho McGruff, I do believe there is a bug in your code!

Code: Select all

function &_getAt($property, $index)
    {
        $p =& $this->$property;
        if(count($index) == 1) {
            return $p[$index[0]];
        }
        if(count($index) == 2) {
            return $p[$index[0]][$index[1]];
        }
    }
vs.

Code: Select all

$suspect_array = array('foo'=>'bar');
$validator =& new ArrayValidator;
$validator->setRuleAt('foo', new FooRule);
$validator->setTarget($suspect_array);
$foo = $validator->getValidated('foo');
"foo" is the index, and so we grab a reference to the relevant array row using _getAt. However, getAt assumes that index is an array!

Code: Select all

$spec =& $this->_getAt('_specs', $index);
Ho, that won't do. But casting index into an array causes problems... perhaps this is an example error?

Edit - This appears to be fixed by adding a convenience check within the functions: check if index is an array, if it isn't, $index = array($index). The unit test cases do seem to be passing index arrays.

Edit 2 - Hmm... as I'm using this class, it looks like support for defaults for optional values is something I'd like.

Posted: Tue Nov 08, 2005 1:25 pm
by McGruff
The comments for setRuleAt() are misleading: I've listed the parameter type as a string when it should have said "array". This is maybe a good example of tests as documentation: comments can get out of sync with the code but unit tests never lie. It's also an example of sloppiness on my part. When you're close to something it's easy to forget all the assumptions you're making.

An array parameter was chosen to support two dimensional target arrays (eg checkbox options) - you can pass either one or two values in a single arg. You could either (a) always pass arrays for consistency or (b) accept arrays and strings at the cost of a line or two of extra code to deal with different types.

I suspect there is something I'm missing which would handle variable array depth better, supporting arrays of any depth. On the other hand the requirements were simply to support GPC input which, although you could have arrays of any depth, in practice doesn't need to go beyond two dimensions. In best TDD style, you just do the simplest thing that works and refactor later if new requirements emerge.

Posted: Tue Nov 08, 2005 1:35 pm
by Ambush Commander
The comments for setRuleAt() are misleading: I've listed the parameter type as a string when it should have said "array". This is maybe a good example of tests as documentation: comments can get out of sync with the code but unit tests never lie. It's also an example of sloppiness on my part. When you're close to something it's easy to forget all the assumptions you're making.
Well said. I'm going to love my unit tests even more.
An array parameter was chosen to support two dimensional target arrays (eg checkbox options) - you can pass either one or two values in a single arg. You could either (a) always pass arrays for consistency or (b) accept arrays and strings at the cost of a line or two of extra code to deal with different types.
Since one-dimensional arrays are going to be what the class will mostly be dealing with, I think the convenience is worth the cost of consistency. However, the cost may be a bit higher than you'd think since the functions that give information about failed keys and whatnot always return the index as an array, even when it's one dimensional. I have half the mind to scrap the two-dimensional support altogether (and simply chain the ArrayValidators together--not sure how that would work though).

Nonetheless, this is an extremely useful class.