diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index 58db73d2dcb..73caa7214c1 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -17,6 +17,7 @@ use ArrayAccess; use Cake\Validation\RulesProvider; use Cake\Validation\ValidationSet; +use Cake\Utility\Hash; use Countable; use IteratorAggregate; @@ -97,9 +98,11 @@ public function errors(array $data, $newRecord = true) $requiredMessage = __d('cake', 'This field is required'); $emptyMessage = __d('cake', 'This field cannot be left empty'); } + $flat = Hash::flatten($data); foreach ($this->_fields as $name => $field) { - $keyPresent = array_key_exists($name, $data); + $isPath = strpos($name, '.') !== false; + $keyPresent = array_key_exists($name, $isPath ? $flat : $data); if (!$keyPresent && !$this->_checkPresence($field, $newRecord)) { $errors[$name]['_required'] = isset($this->_presenceMessages[$name]) @@ -107,15 +110,15 @@ public function errors(array $data, $newRecord = true) : $requiredMessage; continue; } - if (!$keyPresent) { continue; } + $value = $isPath ? $flat[$name] : $data[$name]; $providers = $this->_providers; $context = compact('data', 'newRecord', 'field', 'providers'); $canBeEmpty = $this->_canBeEmpty($field, $context); - $isEmpty = $this->_fieldIsEmpty($data[$name]); + $isEmpty = $this->_fieldIsEmpty($value); if (!$canBeEmpty && $isEmpty) { $errors[$name]['_empty'] = isset($this->_allowEmptyMessages[$name]) @@ -128,7 +131,7 @@ public function errors(array $data, $newRecord = true) continue; } - $result = $this->_processRules($name, $field, $data, $newRecord); + $result = $this->_processRules($name, $value, $field, $data, $newRecord); if ($result) { $errors[$name] = $result; } @@ -547,13 +550,13 @@ protected function _fieldIsEmpty($data) * * @param string $field The name of the field that is being processed * @param ValidationSet $rules the list of rules for a field + * @param mixed $value The field value. * @param array $data the full data passed to the validator * @param bool $newRecord whether is it a new record or an existing one * @return array */ - protected function _processRules($field, ValidationSet $rules, $data, $newRecord) + protected function _processRules($field, $value, ValidationSet $rules, $data, $newRecord) { - $value = $data[$field]; $errors = []; // Loading default provider in case there is none $this->provider('default'); diff --git a/tests/TestCase/Validation/ValidatorTest.php b/tests/TestCase/Validation/ValidatorTest.php index 6d1622a26db..ae79972b3c0 100644 --- a/tests/TestCase/Validation/ValidatorTest.php +++ b/tests/TestCase/Validation/ValidatorTest.php @@ -46,6 +46,31 @@ public function testAddingRulesToField() $this->assertCount(2, $validator); } + /** + * Testing you can add nested field rules + * + * @return void + */ + public function testAddingNestedRulesToField() + { + $validator = new Validator; + $validator->add('user.username', 'not-blank', ['rule' => 'notBlank']); + $this->assertCount(0, $validator->field('user')); + + $set = $validator->field('user.username'); + $this->assertInstanceOf('Cake\Validation\ValidationSet', $set); + $this->assertCount(1, $set); + + $validator->add('user.username', 'letters', ['rule' => 'alphanumeric']); + $this->assertCount(2, $set); + + $validator->remove('user.username', 'letters'); + $this->assertCount(1, $set); + + $validator->requirePresence('user.twitter'); + $this->assertTrue($validator->field('user.twitter')->isPresenceRequired()); + } + /** * Tests that calling field will create a default validation set for it * @@ -163,6 +188,33 @@ public function testErrorsWithPresenceRequired() $this->assertEmpty($validator->errors(['foo' => 'bar'])); } + /** + * Test that errors() can work with nested data. + * + * @return void + */ + public function testErrorsWithNestedFields() + { + $validator = new Validator; + $validator->add('user.username', 'letter', ['rule' => 'alphanumeric']); + $validator->add('comments.0.comment', 'letter', ['rule' => 'alphanumeric']); + + $data = [ + 'user' => [ + 'username' => 'is wrong' + ], + 'comments' => [ + ['comment' => 'is wrong'] + ] + ]; + $errors = $validator->errors($data); + $expected = [ + 'user.username' => ['letter' => 'The provided value is invalid'], + 'comments.0.comment' => ['letter' => 'The provided value is invalid'] + ]; + $this->assertEquals($expected, $errors); + } + /** * Tests custom error messages generated when a field presence is required *