Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Bug fix for Zend\Form\Element\Collection::extract() #5220

Closed
wants to merge 1 commit into from

3 participants

@dhensen

When a Traversable is encountered it is converted to an array.
By default this is done recursively, so that the Traversable becomes an array representation.
The result of that is saved in $this->object.
In the Collection::extract method when iterating over $this->object there are two branches:

  1. If there is a hydrator, its extract method is called which expects $value as an object.
  2. If $value is an instance of the targetElement, eventualy setObject($value) is called on a fieldset, which also expects an object. Also the targetElement object is set to $value.

In both cases an object is expected, but because of recursive iteratorToArray call it will never be an object.

The fix is to not do it recursively, this way it still gives a key => value array representation of the traversable,
but still gives model objects that can be used by a hydrator or set as targetElement object.

@Maks3w
Collaborator

Two things:

  • Add a test case
  • That method could be replaced with iterator_to_array native function for this case
@dhensen dhensen Bug fix:
When a Traversable is encountered it is converted to an array.
By default this is done recursively, so that the Traversable becomes an array representation.
The result of that is saved in $this->object.
In the Colletion::extract method when iterating over $this->object there are two branches:
1. If there is a hydrator, its extract method is called which expects $value as an object.
2. If $value is an instance of the targetElement, eventualy setObject($value) is called on a fieldset, which also expects an object. Also the targetElement object is set to $value.

In both cases an object is expected, but because of recursive iteratorToArray call it will never be an object.

The fix is to not do it recursively, this way it still gives a key => value array representation of the traversable,
but still gives model objects that can be used by a hydrator or set as targetElement object.

The above problem occurs when an extended Traversable that implements the toArray method is being used in the ArrayUtils::iteratorToArray method.
Because it is recursive per default, it calls the toArray method on the traversable and in case this is implemented in a way that also calls toArray on all child elements, the whole traversable is converted to a scalar-only-array.
So now that there are no object, the Zend\Form\Element\Collection::extract method can not use the hydrator extract method on an array. Also the targetElement hydrator can not do it.
So the extract method fails even though there is probably an array containing a lot of nice values.

The solution chosen by me is to not recursively transform. That works as shown in the two added testcases.
Another fix would be to rewrite the way the extract method works. Or to better dictate how the traversable should be constrained to be transformed in a more controlled way.
2e848e8
@dhensen

Okay I amended the commit and force pushed. Now contains a two testcases and some stuff to support fixing the bug by non-recursive iteratorToArray transformation. If it is not OK please let me know.

@weierophinney weierophinney referenced this pull request from a commit
@weierophinney weierophinney [#5220] CS fixes
- EOF ending
f5e4caa
@dhensen

Thanks for merging into dev. I hope all is good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 5, 2013
  1. @dhensen

    Bug fix:

    dhensen authored
    When a Traversable is encountered it is converted to an array.
    By default this is done recursively, so that the Traversable becomes an array representation.
    The result of that is saved in $this->object.
    In the Colletion::extract method when iterating over $this->object there are two branches:
    1. If there is a hydrator, its extract method is called which expects $value as an object.
    2. If $value is an instance of the targetElement, eventualy setObject($value) is called on a fieldset, which also expects an object. Also the targetElement object is set to $value.
    
    In both cases an object is expected, but because of recursive iteratorToArray call it will never be an object.
    
    The fix is to not do it recursively, this way it still gives a key => value array representation of the traversable,
    but still gives model objects that can be used by a hydrator or set as targetElement object.
    
    The above problem occurs when an extended Traversable that implements the toArray method is being used in the ArrayUtils::iteratorToArray method.
    Because it is recursive per default, it calls the toArray method on the traversable and in case this is implemented in a way that also calls toArray on all child elements, the whole traversable is converted to a scalar-only-array.
    So now that there are no object, the Zend\Form\Element\Collection::extract method can not use the hydrator extract method on an array. Also the targetElement hydrator can not do it.
    So the extract method fails even though there is probably an array containing a lot of nice values.
    
    The solution chosen by me is to not recursively transform. That works as shown in the two added testcases.
    Another fix would be to rewrite the way the extract method works. Or to better dictate how the traversable should be constrained to be transformed in a more controlled way.
This page is out of date. Refresh to see the latest.
View
2  library/Zend/Form/Element/Collection.php
@@ -493,7 +493,7 @@ public function extract()
{
if ($this->object instanceof Traversable) {
- $this->object = ArrayUtils::iteratorToArray($this->object);
+ $this->object = ArrayUtils::iteratorToArray($this->object, false);
}
if (!is_array($this->object)) {
View
53 tests/ZendTest/Form/Element/CollectionTest.php
@@ -18,6 +18,9 @@
use Zend\Form\Form;
use Zend\Stdlib\Hydrator\ObjectProperty as ObjectPropertyHydrator;
use ZendTest\Form\TestAsset\Entity\Product;
+use Zend\Stdlib\Hydrator\ArraySerializable;
+use ZendTest\Form\TestAsset\CustomCollection;
+use ZendTest\Form\TestAsset\ArrayModel;
class CollectionTest extends TestCase
{
@@ -488,4 +491,54 @@ protected function prepareForExtract($collection)
'obj3' => $obj3,
));
}
+
+ public function testExtractFromTraversableImplementingToArrayThroughCollectionHydrator()
+ {
+ $collection = $this->form->get('fieldsets');
+
+ // this test is using a hydrator set on the collection
+ $collection->setHydrator(new ArraySerializable());
+
+ $this->prepareForExtractWithCustomTraversable($collection);
+
+ $expected = array(
+ array('foo' => 'foo_value_1', 'bar' => 'bar_value_1', 'foobar' => 'foobar_value_1'),
+ array('foo' => 'foo_value_2', 'bar' => 'bar_value_2', 'foobar' => 'foobar_value_2'),
+ );
+
+ $this->assertEquals($expected, $collection->extract());
+ }
+
+ public function testExtractFromTraversableImplementingToArrayThroughTargetElementHydrator()
+ {
+ $collection = $this->form->get('fieldsets');
+
+ // this test is using a hydrator set on the target element of the collection
+ $targetElement = $collection->getTargetElement();
+ $targetElement->setHydrator(new ArraySerializable());
+ $obj1 = new ArrayModel();
+ $targetElement->setObject($obj1);
+
+ $this->prepareForExtractWithCustomTraversable($collection);
+
+ $expected = array(
+ array('foo' => 'foo_value_1', 'bar' => 'bar_value_1', 'foobar' => 'foobar_value_1'),
+ array('foo' => 'foo_value_2', 'bar' => 'bar_value_2', 'foobar' => 'foobar_value_2'),
+ );
+
+ $this->assertEquals($expected, $collection->extract());
+ }
+
+ protected function prepareForExtractWithCustomTraversable($collection)
+ {
+ $obj2 = new ArrayModel();
+ $obj2->exchangeArray(array('foo' => 'foo_value_1', 'bar' => 'bar_value_1', 'foobar' => 'foobar_value_1'));
+ $obj3 = new ArrayModel();
+ $obj3->exchangeArray(array('foo' => 'foo_value_2', 'bar' => 'bar_value_2', 'foobar' => 'foobar_value_2'));
+
+ $traversable = new CustomCollection();
+ $traversable->append($obj2);
+ $traversable->append($obj3);
+ $collection->setObject($traversable);
+ }
}
View
18 tests/ZendTest/Form/TestAsset/ArrayModel.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace ZendTest\Form\TestAsset;
+
+class ArrayModel extends Model
+{
+ public function toArray()
+ {
+ return $this->getArrayCopy();
+ }
+}
View
26 tests/ZendTest/Form/TestAsset/CustomCollection.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Zend Framework (http://framework.zend.com/)
+ *
+ * @link http://github.com/zendframework/zf2 for the canonical source repository
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+namespace ZendTest\Form\TestAsset;
+
+use Zend\Stdlib\ArrayObject;
+
+class CustomCollection extends ArrayObject
+{
+ public function toArray()
+ {
+ $ret = array();
+
+ foreach ($this as $key => $obj) {
+ $ret[$key] = $obj->toArray();
+ }
+
+ return $ret;
+ }
+}
Something went wrong with that request. Please try again.