Skip to content

Commit

Permalink
Implement nested field validators.
Browse files Browse the repository at this point in the history
This adds support for nested field validators. To better support
model-less forms and the elasticsearch ODM, we will need to validate
nested fields. This set of changes is the smallest change I could make
that implements the basic requirements.

Refs #6496
  • Loading branch information
markstory committed May 12, 2015
1 parent 56085cd commit a86b0c7
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 6 deletions.
15 changes: 9 additions & 6 deletions src/Validation/Validator.php
Expand Up @@ -17,6 +17,7 @@
use ArrayAccess;
use Cake\Validation\RulesProvider;
use Cake\Validation\ValidationSet;
use Cake\Utility\Hash;
use Countable;
use IteratorAggregate;

Expand Down Expand Up @@ -97,25 +98,27 @@ 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])
? $this->_presenceMessages[$name]
: $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])
Expand All @@ -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;
}
Expand Down Expand Up @@ -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');
Expand Down
52 changes: 52 additions & 0 deletions tests/TestCase/Validation/ValidatorTest.php
Expand Up @@ -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
*
Expand Down Expand Up @@ -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
*
Expand Down

0 comments on commit a86b0c7

Please sign in to comment.