Ticket Details

Add the option to specify validation rules for array fields. Eg. for input name="details[age]"

ENHANCEMENT Ticket (pending)

###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.

on 03.24.11 reported by: Ionut.Botizan

Updates

on 03.28.11 by Ionut.Botizan
  • description was changed
on 03.28.11 by Ionut.Botizan
  • description was changed
on 03.28.11 by nate
This ticket is at least partially invalid. The correct syntax for form fields is:

{{{
<?=$this->form->field('details.age'); ?>
}}}
on 03.28.11 by Ionut.Botizan
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...
on 03.28.11 by Ionut.Botizan
For what it's worth, I've spent countless hours debugging the code (literally) line by line before posting this ticket...
I've tried both with dots and square brackets and the result was the same (the field didn't get populated with the value from ```_binding->data()```).
on 03.29.11 by Ionut.Botizan
  • description was changed
on 03.31.11 by alkemann
I believe it is http://lithify.me/docs/lithium/data/entity/Document::_getNested() having a flawed logic that results in form fields not being populated. Specifically it is doing an is_array where it probably shouldn't..
on 03.31.11 by alkemann
  • id was changed to 496
  • number was changed to 314
  • type was changed to enhancement
  • priority was changed to normal
  • title was changed to Add the option to specify validation rules for array fields. Eg. for input name="details[age]"
  • description was changed
  • tags was changed to mongodb, schema, form, Validator
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};
}}}
on 04.08.11 by alkemann
Push the suggested changes to a fork, along with a [test case](http://dev.lithify.me/forks/alkemann/lithium/commits/view/2144c53ce3a8c4717148306fe6a29c8b5ac83ed4).

Did not include my DocumentArray thing, but potential issues with this may be seen in [these tests](http://dev.lithify.me/forks/alkemann/lithium/commits/view/c0e87800090880b598fa944215aff576ec78f9fc).
on 04.28.11 by dgalien
  • id was changed to 496
  • number was changed to 314
  • type was changed to enhancement
  • priority was changed to normal
this would solve a lot of workarounds! please merge this solution into master!