Ticket Details

Access Nested Object Data in Form Helper

ENHANCEMENT Ticket (pending)

###What happened:
When using nested documents with MongoDB, you can create fields using the form helper to save data in a nested document.  However, when editing, the form helper doesn't pull the data out of the nested document correctly.

Example:
{{{
<?=$this->form->field('loc[lat]', array('label'=>'Latitude'));?>
}}}

###What was expected:
The field should display the value inside the nested document... in this case $obj->loc->lat;

###Example Fix:
This code is a bit ugly, but it works even for multiple objects nested multiple times.

Modify lithium\template\helper\Form.php

Change:
{{{
           $hasValue = (
                (!isset($options['value']) || $options['value'] === null) &&
                $name && $this->_binding && $value = $this->_binding->data($name)
            );
}}}
To:
{{{
        if(stristr($name,']') != FALSE && (!isset($options['value']) || $options['value'] === null) && $this->_binding)
        {
            $nameParts = explode(".",str_replace(array('[',']'),array('.',''), $name));
            $value = null;
            foreach($nameParts as $namePart)
            {
                if(is_null($value)){
                    $hasValue = $value = $this->_binding->data($namePart);
                }
                else
                {
                   $hasValue = $value = $value[$namePart];
                }
            }

        }
        else
        {
            $hasValue = (
                (!isset($options['value']) || $options['value'] === null) &&
                $name && $this->_binding && $value = $this->_binding->data($name)
            );
        }

}}}

on 11.28.10 reported by: SeyelentEco owned by: nate

Updates

(fixed) on 03.03.11 by nate
  • owner was changed to nate
  • status was changed to closed
  • resolution was changed to fixed
This has been implementing using the dot syntax for fields. See the `testNestedFieldAccess()` method of the `Form` helper test for an example.
(reopened) on 05.12.11 by edmunc
  • status was changed to pending
  • resolution was removed
Hi!

The case SeyelentEco describes is actually not covered by the test case. The test case tests form::text() but not form::field(), which fails with dot syntax. 

Here is a failing test:

{{{
$doc = new Document(array('data' => array('foo' => array('bar' => 'value'))));
$this->form->create($doc);

$result = $this->form->field('foo.bar');
$this->assertTags($result, array(
   'div' => array(),
   'label' => array('for' => 'FooBar'), 'Foo[bar]', '/label',
   'input' => array('type' => 'text', 'name' => 'foo[bar]', 'id' => 'FooBar', 'value' => 'value'),
));
}}}
(fixed) on 05.12.11 by nate
  • status was changed to closed
  • resolution was changed to fixed
Fixed in [b8492fa0f3ca41f4e971d1d567512ded69ca8ba4]. Thanks for the follow-up.
(reopened) on 05.17.11 by dgalien
  • status was changed to pending
  • resolution was removed
Hi.

Since this update we got errors with nested documents:

###What happened:
We have Customers and Attachments (fs.file) as Models.
We store/mirroring some of the attachments model data inside users data. eg: customer.attachment.bill_amount

and

customer.attachment.data_file

###Description

{{{
Catchable fatal error: Object of class lithium\data\entity\Document could not be converted to string in /mnt/nfs/cored-li3/libraries/lithium/template/Helper.php on line 196 
}}}

i am currently not able to build a test case but this code excerpts might give a hint:

template excerpt:
{{{
<?=$this->form->field('attachment.'.$fieldId, array(
				'type' => 'file',
				'label' => $fieldLabel)
			);
		?>
}}}

var_dump inside the form helper like so:

{{{
	/**
	 * Convert a set of options to HTML attributes
	 *
	 * @param array $params
	 * @param string $method
	 * @param array $options
	 * @return string
	 */
	protected function _attributes($params, $method = null, array $options = array()) {
		if (!is_array($params)) {
			return !$params ? '' : ' ' . $params;
		}
		$defaults = array('escape' => true, 'prepend' => ' ', 'append' => '');
		$options += $defaults;
		$result = array();

		var_dump($params);
		foreach ($params as $key => $value) {
			$result[] = $this->_attribute($key, $value, $options);
		}
		return $result ? $options['prepend'] . implode(' ', $result) . $options['append'] : '';
	}
}}}

var_dump:
{{{

array(1) {
  ["class"]=>
  string(0) ""
}
array(2) {
  ["id"]=>
  string(29) "CustomerAttachmentBillAmounts"
  ["value"]=>
  object(lithium\data\entity\Document)#78 (17) {
    ["_pathKey":protected]=>
    string(23) "attachment.bill_amounts"
    ["_relations":protected]=>
    array(0) {
    }
    ["_removed":protected]=>
    array(0) {
    }
    ["_stats":protected]=>
    array(0) {
    }
    ["_valid":protected]=>
    bool(false)
    ["_model":protected]=>
    string(19) "app\models\Customer"
    ["_data":protected]=>
    array(5) {
      ["name"]=>
      string(0) ""
      ["type"]=>
      string(0) ""
      ["tmp_name"]=>
      string(0) ""
      ["error"]=>
      int(4)
      ["size"]=>
      int(0)
    }
    ["_relationships":protected]=>
    array(0) {
    }
    ["_parent":protected]=>
    NULL
    ["_handle":protected]=>
    NULL
    ["_errors":protected]=>
    array(0) {
    }
    ["_updated":protected]=>
    array(0) {
    }
    ["_increment":protected]=>
    array(0) {
    }
    ["_exists":protected]=>
    bool(true)
    ["_schema":protected]=>
    array(0) {
    }
    ["_config":protected]=>
    array(7) {
      ["model"]=>
      string(19) "app\models\Customer"
      ["data"]=>
      array(5) {
        ["name"]=>
        string(0) ""
        ["type"]=>
        string(0) ""
        ["tmp_name"]=>
        string(0) ""
        ["error"]=>
        int(4)
        ["size"]=>
        int(0)
      }
      ["pathKey"]=>
      string(23) "attachment.bill_amounts"
      ["first"]=>
      bool(true)
      ["handlers"]=>
      array(8) {
        ["id"]=>
        object(Closure)#17 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
        ["date"]=>
        object(Closure)#18 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
        ["regex"]=>
        object(Closure)#19 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
        ["integer"]=>
        object(Closure)#20 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
        ["float"]=>
        object(Closure)#21 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
        ["boolean"]=>
        object(Closure)#22 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
        ["code"]=>
        object(Closure)#23 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
        ["binary"]=>
        object(Closure)#24 (1) {
          ["parameter"]=>
          array(1) {
            ["$v"]=>
            string(10) "<required>"
          }
        }
      }
      ["arrays"]=>
      bool(true)
      ["init"]=>
      bool(true)
    }
    ["_methodFilters":protected]=>
    array(0) {
    }
  }
}
}}}
on 05.17.11 by DeKay
I think this is the same problem i'm having here:
http://dev.lithify.me/lithium/tickets/view/362
(invalid) on 05.17.11 by nate
  • id was changed to 368
  • number was changed to 218
  • type was changed to enhancement
  • status was changed to closed
  • resolution was changed to invalid
  • title was changed to Access Nested Object Data in Form Helper
  • description was changed
  • tags was changed to nested, data, field, mongodb, helper, form
You're using a Document object as the value of a field. This is incorrect. Either use a reference to a field that contains a scalar value, or set the value manually.
on 06.23.11 by tehnorm
Also getting a similar error. Not sure if this is directly related.

{{{
Catchable fatal error: Object of class lithium\data\entity\Document could not be converted to string in /Users/norm/Projects/taggs.co/site/libraries/lithium/template/Helper.php on line 198 
}}}

The combination of using a ```$form->field('upload', array('type' => 'file'))``` with mongo causes this error when form validation fails for another field. 

### The solution for me:
{{{
$upload = Tagg::create($this->request->data);
// Make sure our form validates
if(!$upload->validates()){
        Session::write('error', 'Correct the errors below.');
        unset($upload->upload); // Remove the uploaded file field - stops error 
        return compact('upload');
}
}}}
(reopened) on 06.24.11 by nate
  • status was changed to pending
  • resolution was removed