From c197b10891d197682643ebdc55371308d5545034 Mon Sep 17 00:00:00 2001 From: Mark Cahill Date: Mon, 8 Sep 2014 10:40:53 -0400 Subject: [PATCH] Validation of nested arrays, closes #8 --- README.md | 18 +++++++ src/Valitron/Validator.php | 68 ++++++++++++++++++++++++-- tests/Valitron/ValidateTest.php | 87 +++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f7eefc0..4854fa1 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,23 @@ if($v->validate()) { } ``` +You may use dot syntax to access members of multi-dimensional arrays, +and an asterisk to validate each member of an array: + +```php +$v = new Valitron\Validator(array('settings' => array( + array('threshold' => 50), + array('threshold' => 90) +))); +$v->rule('max', 'settings.*.threshold', 100); +if($v->validate()) { + echo "Yay! We're all good!"; +} else { + // Errors + print_r($v->errors()); +} +``` + Setting language and language dir globally: ```php @@ -92,6 +109,7 @@ V::lang('ar'); * `accepted` - Checkbox or Radio must be accepted (yes, on, 1, true) * `numeric` - Must be numeric * `integer` - Must be integer number + * `array` - Must be array * `length` - String must be certain length * `lengthBetween` - String must be between given lengths * `lengthMin` - String must be greater than given length diff --git a/src/Valitron/Validator.php b/src/Valitron/Validator.php index 746a179..52da7a8 100644 --- a/src/Valitron/Validator.php +++ b/src/Valitron/Validator.php @@ -186,6 +186,18 @@ class Validator return $this->validateRequired($field, $value) && in_array($value, $acceptable, true); } + /** + * Validate that a field is an array + * + * @param string $field + * @param mixed $value + * @return bool + */ + protected function validateArray($field, $value) + { + return is_array($value); + } + /** * Validate that a field is numeric * @@ -772,6 +784,47 @@ class Validator $this->_labels = array(); } + private function get($data, $identifiers) { + $identifier = array_shift($identifiers); + + // Glob match + if ($identifier === '*') + { + $values = array(); + foreach($data as $row) + { + list($value, $multiple) = $this->get($row, $identifiers); + if ($multiple) + { + $values = array_merge($values, $value); + } + else + { + $values[] = $value; + } + } + return array($values, true); + } + + // Dead end, abort + elseif ($identifier === NULL || ! isset($data[$identifier])) + { + return array(NULL, false); + } + + // Match array element + elseif (count($identifiers) === 0) + { + return array($data[$identifier], false); + } + + // We need to go deeper + else + { + return $this->get($data[$identifier], $identifiers); + } + } + /** * Run validations and return boolean result * @@ -781,10 +834,10 @@ class Validator { foreach ($this->_validations as $v) { foreach ($v['fields'] as $field) { - $value = isset($this->_fields[$field]) ? $this->_fields[$field] : null; + list($values, $multiple) = $this->get($this->_fields, explode('.', $field)); // Don't validate if the field is not required and the value is empty - if ($v['rule'] !== 'required' && !$this->hasRule('required', $field) && (! isset($value) || $value === '')) { + if ($v['rule'] !== 'required' && !$this->hasRule('required', $field) && (! isset($values) || $values === '' || ($multiple && count($values) == 0))) { continue; } @@ -795,7 +848,16 @@ class Validator $callback = array($this, 'validate' . ucfirst($v['rule'])); } - $result = call_user_func($callback, $field, $value, $v['params']); + if (! $multiple) + { + $values = array($values); + } + + $result = true; + foreach($values as $value) + { + $result = $result && call_user_func($callback, $field, $value, $v['params']); + } if (!$result) { $this->error($field, $v['message'], $v['params']); } diff --git a/tests/Valitron/ValidateTest.php b/tests/Valitron/ValidateTest.php index acd566e..d9c006d 100644 --- a/tests/Valitron/ValidateTest.php +++ b/tests/Valitron/ValidateTest.php @@ -273,6 +273,93 @@ class ValidateTest extends BaseTestCase $this->assertTrue($v->validate()); } + public function testArrayValid() + { + $v = new Validator(array('colors' => array('yellow'))); + $v->rule('array', 'colors'); + $this->assertTrue($v->validate()); + } + + public function testAssocArrayValid() + { + $v = new Validator(array('settings' => array('color' => 'yellow'))); + $v->rule('array', 'settings'); + $this->assertTrue($v->validate()); + } + + public function testArrayInvalid() + { + $v = new Validator(array('colors' => 'yellow')); + $v->rule('array', 'colors'); + $this->assertFalse($v->validate()); + } + + public function testArrayAccess() + { + $v = new Validator(array('settings' => array('enabled' => true))); + $v->rule('boolean', 'settings.enabled'); + $this->assertTrue($v->validate()); + } + + public function testArrayAccessInvalid() + { + $v = new Validator(array('settings' => array('threshold' => 500))); + $v->rule('max', 'settings.threshold', 100); + $this->assertFalse($v->validate()); + } + + public function testForeachArrayAccess() + { + $v = new Validator(array('settings' => array( + array('enabled' => true), + array('enabled' => true) + ))); + $v->rule('boolean', 'settings.*.enabled'); + $this->assertTrue($v->validate()); + } + + public function testForeachArrayAccessInvalid() + { + $v = new Validator(array('settings' => array( + array('threshold' => 50), + array('threshold' => 500) + ))); + $v->rule('max', 'settings.*.threshold', 100); + $this->assertFalse($v->validate()); + } + + public function testNestedForeachArrayAccess() + { + $v = new Validator(array('widgets' => array( + array('settings' => array( + array('enabled' => true), + array('enabled' => true) + )), + array('settings' => array( + array('enabled' => true), + array('enabled' => true) + )) + ))); + $v->rule('boolean', 'widgets.*.settings.*.enabled'); + $this->assertTrue($v->validate()); + } + + public function testNestedForeachArrayAccessInvalid() + { + $v = new Validator(array('widgets' => array( + array('settings' => array( + array('threshold' => 50), + array('threshold' => 90) + )), + array('settings' => array( + array('threshold' => 40), + array('threshold' => 500) + )) + ))); + $v->rule('max', 'widgets.*.settings.*.threshold', 100); + $this->assertFalse($v->validate()); + } + public function testInInvalid() { $v = new Validator(array('color' => 'yellow'));