###Please note...
This is also a fix for an existing bug, not just an enhancement: the Form helper can't access the values for array fields and is not able to auto-populate them with the existing values.
Eg. In the following, the ```details[age]``` field will always be empty:
{{{
<?php
class TestsController extends \lithium\action\Controller {
public function add()
{
$test = Test::create($this->request->data);
$test->details['age'] = 18;
if ( $this->request->data ) :
$test->save($this->request->data);
endif;
return compact('test');
}
}
?>
}}}
{{{
<?=$this->form->create($test); ?>
<?=$this->form->field('name', array('label' => 'Name')); ?>
<?=$this->form->field('details.age', array('label' => 'Age')); ?>
<?=$this->form->submit('Save'); ?>
<?=$this->form->end(); ?>
}}}
###Currently...
Although, when using MongoDB, one could specify a multidimensional schema for embedded documents, such as:
{{{
public $_schema = array(
'_id' => array('type' => 'id'),
'name' => array('type' => 'string', 'null' => FALSE),
'details' => array('type' => 'object', 'null' => FALSE),
'details.age' => array('type' => 'integer', 'default' => '', 'null' => FALSE),
'details.gender' => array('type' => 'string', 'default' => 'm', 'null' => FALSE),
);
}}}
it's not possible to specify validation rules for the embedded fields, like this:
{{{
public $validates = array(
'name' => array(
array('notEmpty', 'message' => 'Please tell us your name!')
),
'details.age' => array(
array('legalAge', 'message' => 'You must be of legal age to continue!')
)
);
}}}
###My solution
<del>
Add this to the ```lithium\util\Validator::check()``` method, right before the ```foreach``` loop
{{{
$values = Set::flatten($values);
}}}
</del>
Actually, a better way would be adding the following to the ```lithium\util\Validator::check()``` method, before ```if (!isset($values[$field]))``` (around line ```450```)
{{{
if ( strpos($field, '.') ) {
$xpath = '/' . str_replace('.', '/', $field) . '/.';
$val = Set::extract($values, $xpath);
if ( count($val) ) {
$values[$field] = $val[0];
}
}
}}}
This will allow adding validation rules on a group/subset of fields, like in the following (silly) example:
{{{
public $_schema = array(
'_id' => array('type' => 'id'),
'target' => array('type' => 'object'),
'target.age' => array('type' => 'object'),
'target.age.min' => array('type' => 'integer'),
'target.age.max' => array('type' => 'integer'),
);
public $validates = array(
'target.age' => array(
array('ageMaxGtMin', 'message' => 'Max age should be greater than min')
)
);
/**
* "ageMaxGtMin" being a a closure passed to Validator::add(),
* which will receive something like
* array(
* 'min' => 21,
* 'max' => 32,
* )
* as the $value parameter
*/
}}}
##
Then, in order to convert names like ```details[age]``` or ```details[gender][m]``` to ```details.age``` and ```details.gender.m```, so ```Entity::data()``` and ```Entity::errors()``` could understand them, add
{{{
$name = str_replace(array('][', '[', ']'), array('.', '.', ''), $name);
}}}
to ```lithium\template\helper\Form::error()```, before
{{{
if (!$this->_binding || !$content = $this->_binding->errors($name)) {
return null;
}
}}}
and to ```lithium\template\helper\Form::_defaults()```, before
{{{
$hasValue = (
(!isset($options['value']) || $options['value'] === null) &&
$name && $this->_binding && $value = $this->_binding->data($name)
);
}}}
This could also be handled at ```lithium\data\Entity``` level (inside ```errors()``` and ```data()```), but I'm not sure if that would be appropriate.
This ticket is at least partially invalid. The correct syntax for form fields is: {{{ <?=$this->form->field('details.age'); ?> }}}It doesn't seem to matter whether you use the dot notation or the square brackets. In ```Form::_defaults()```, the dotted names get converted to their square brackets equivalents and the result is passed to other methods, like ```Form::error()```. Then, ```Form::error()``` searches for ```$this->_binding->errors("some[field]")``` and none is found. Also, ```Form::_defaults()``` makes a call to ```$this->_binding->data($name)``` to determine whether a field has a value and, since ```$name``` was converted from something like ```some.field``` to ```some[field]```, ```_binding->data()``` won't return any value...One step closer: {{{ --- a/libraries/lithium/data/entity/Document.php +++ b/libraries/lithium/data/entity/Document.php @@ -226,7 +226,7 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { $length = count($path) - 1; foreach ($path as $i => $key) { - if (is_array($current)) { + if (is_array($current) || $current instanceof \lithium\data\collection\DocumentArray) { $current =& $current[$key]; } elseif (isset($current->{$key})) { $current =& $current->{$key}; }}}