diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index f10755f0f6..4eb4f5279e 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -231,7 +231,22 @@ class PropertyAccessor implements PropertyAccessorInterface // Create missing nested arrays on demand if ($isIndex && $isArrayAccess && !isset($objectOrArray[$property])) { if (!$ignoreInvalidIndices) { - throw new NoSuchIndexException(sprintf('Cannot read property "%s". Available properties are "%s"', $property, print_r(array_keys($objectOrArray), true))); + if (!is_array($objectOrArray)) { + if (!$objectOrArray instanceof \Traversable) { + throw new NoSuchIndexException(sprintf( + 'Cannot read property "%s".', + $property + )); + } + + $objectOrArray = iterator_to_array($objectOrArray); + } + + throw new NoSuchIndexException(sprintf( + 'Cannot read property "%s". Available properties are "%s"', + $property, + print_r(array_keys($objectOrArray), true) + )); } $objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null; diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php new file mode 100644 index 0000000000..fd00a730b8 --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests\Fixtures; + +/** + * This class is a hand written simplified version of PHP native `ArrayObject` + * class, to show that it behaves differently than the PHP native implementation. + */ +class NonTraversableArrayObject implements \ArrayAccess, \Countable, \Serializable +{ + private $array; + + public function __construct(array $array = null) + { + $this->array = $array ?: array(); + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->array); + } + + public function offsetGet($offset) + { + return $this->array[$offset]; + } + + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->array[] = $value; + } else { + $this->array[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->array[$offset]); + } + + public function count() + { + return count($this->array); + } + + public function serialize() + { + return serialize($this->array); + } + + public function unserialize($serialized) + { + $this->array = (array) unserialize((string) $serialized); + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/CustomArrayObject.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php similarity index 93% rename from src/Symfony/Component/PropertyAccess/Tests/Fixtures/CustomArrayObject.php rename to src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php index cd23f7033f..3bd9795e6b 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/CustomArrayObject.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php @@ -15,7 +15,7 @@ namespace Symfony\Component\PropertyAccess\Tests\Fixtures; * This class is a hand written simplified version of PHP native `ArrayObject` * class, to show that it behaves differently than the PHP native implementation. */ -class CustomArrayObject implements \ArrayAccess, \IteratorAggregate, \Countable, \Serializable +class TraversableArrayObject implements \ArrayAccess, \IteratorAggregate, \Countable, \Serializable { private $array; diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayAccessTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayAccessTest.php new file mode 100644 index 0000000000..a253d4030f --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayAccessTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests; + +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessor; + +abstract class PropertyAccessorArrayAccessTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PropertyAccessor + */ + protected $propertyAccessor; + + protected function setUp() + { + $this->propertyAccessor = new PropertyAccessor(); + } + + abstract protected function getContainer(array $array); + + public function getValidPropertyPaths() + { + return array( + array($this->getContainer(array('firstName' => 'Bernhard')), '[firstName]', 'Bernhard'), + array($this->getContainer(array('person' => $this->getContainer(array('firstName' => 'Bernhard')))), '[person][firstName]', 'Bernhard'), + ); + } + + /** + * @dataProvider getValidPropertyPaths + */ + public function testGetValue($collection, $path, $value) + { + $this->assertSame($value, $this->propertyAccessor->getValue($collection, $path)); + } + + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException + */ + public function testGetValueFailsIfNoSuchIndex() + { + $this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() + ->enableExceptionOnInvalidIndex() + ->getPropertyAccessor(); + + $object = $this->getContainer(array('firstName' => 'Bernhard')); + + $this->propertyAccessor->getValue($object, '[lastName]'); + } + + /** + * @dataProvider getValidPropertyPaths + */ + public function testSetValue($collection, $path) + { + $this->propertyAccessor->setValue($collection, $path, 'Updated'); + + $this->assertSame('Updated', $this->propertyAccessor->getValue($collection, $path)); + } + + /** + * @dataProvider getValidPropertyPaths + */ + public function testIsReadable($collection, $path) + { + $this->assertTrue($this->propertyAccessor->isReadable($collection, $path)); + } + + /** + * @dataProvider getValidPropertyPaths + */ + public function testIsWritable($collection, $path) + { + $this->assertTrue($this->propertyAccessor->isWritable($collection, $path, 'Updated')); + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayObjectTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayObjectTest.php index aaa86b3c25..fb0b383789 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayObjectTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayObjectTest.php @@ -13,7 +13,7 @@ namespace Symfony\Component\PropertyAccess\Tests; class PropertyAccessorArrayObjectTest extends PropertyAccessorCollectionTest { - protected function getCollection(array $array) + protected function getContainer(array $array) { return new \ArrayObject($array); } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayTest.php index 5ab63c67cb..c982826344 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorArrayTest.php @@ -13,7 +13,7 @@ namespace Symfony\Component\PropertyAccess\Tests; class PropertyAccessorArrayTest extends PropertyAccessorCollectionTest { - protected function getCollection(array $array) + protected function getContainer(array $array) { return $array; } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php index 60bd9dead8..2312ac34fd 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php @@ -11,8 +11,6 @@ namespace Symfony\Component\PropertyAccess\Tests; -use Symfony\Component\PropertyAccess\PropertyAccessor; - class PropertyAccessorCollectionTest_Car { private $axes; @@ -80,55 +78,13 @@ class PropertyAccessorCollectionTest_CarStructure public function getAxes() {} } -abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCase +abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAccessTest { - /** - * @var PropertyAccessor - */ - private $propertyAccessor; - - protected function setUp() - { - $this->propertyAccessor = new PropertyAccessor(); - } - - abstract protected function getCollection(array $array); - - public function getValidPropertyPaths() - { - return array( - array(array('firstName' => 'Bernhard'), '[firstName]', 'Bernhard'), - array(array('person' => array('firstName' => 'Bernhard')), '[person][firstName]', 'Bernhard'), - ); - } - - /** - * @dataProvider getValidPropertyPaths - */ - public function testGetValue(array $array, $path, $value) - { - $collection = $this->getCollection($array); - - $this->assertSame($value, $this->propertyAccessor->getValue($collection, $path)); - } - - /** - * @dataProvider getValidPropertyPaths - */ - public function testSetValue(array $array, $path) - { - $collection = $this->getCollection($array); - - $this->propertyAccessor->setValue($collection, $path, 'Updated'); - - $this->assertSame('Updated', $this->propertyAccessor->getValue($collection, $path)); - } - public function testSetValueCallsAdderAndRemoverForCollections() { - $axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth', 4 => 'fifth')); - $axesMerged = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third')); - $axesAfter = $this->getCollection(array(1 => 'second', 5 => 'first', 6 => 'third')); + $axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth', 4 => 'fifth')); + $axesMerged = $this->getContainer(array(1 => 'first', 2 => 'second', 3 => 'third')); + $axesAfter = $this->getContainer(array(1 => 'second', 5 => 'first', 6 => 'third')); $axesMergedCopy = is_object($axesMerged) ? clone $axesMerged : $axesMerged; // Don't use a mock in order to test whether the collections are @@ -147,8 +103,8 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas { $car = $this->getMock(__CLASS__.'_CompositeCar'); $structure = $this->getMock(__CLASS__.'_CarStructure'); - $axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth')); - $axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third')); + $axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth')); + $axesAfter = $this->getContainer(array(0 => 'first', 1 => 'second', 2 => 'third')); $car->expects($this->any()) ->method('getStructure') @@ -177,35 +133,20 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas public function testSetValueFailsIfNoAdderNorRemoverFound() { $car = $this->getMock(__CLASS__.'_CarNoAdderAndRemover'); - $axes = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third')); + $axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth')); + $axesAfter = $this->getContainer(array(0 => 'first', 1 => 'second', 2 => 'third')); - $this->propertyAccessor->setValue($car, 'axes', $axes); - } + $car->expects($this->any()) + ->method('getAxes') + ->will($this->returnValue($axesBefore)); - /** - * @dataProvider getValidPropertyPaths - */ - public function testIsReadable(array $array, $path) - { - $collection = $this->getCollection($array); - - $this->assertTrue($this->propertyAccessor->isReadable($collection, $path)); - } - - /** - * @dataProvider getValidPropertyPaths - */ - public function testIsWritable(array $array, $path) - { - $collection = $this->getCollection($array); - - $this->assertTrue($this->propertyAccessor->isWritable($collection, $path, 'Updated')); + $this->propertyAccessor->setValue($car, 'axes', $axesAfter); } public function testIsWritableReturnsTrueIfAdderAndRemoverExists() { $car = $this->getMock(__CLASS__.'_Car'); - $axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third')); + $axes = $this->getContainer(array(1 => 'first', 2 => 'second', 3 => 'third')); $this->assertTrue($this->propertyAccessor->isWritable($car, 'axes', $axes)); } @@ -213,7 +154,7 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas public function testIsWritableReturnsFalseIfOnlyAdderExists() { $car = $this->getMock(__CLASS__.'_CarOnlyAdder'); - $axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third')); + $axes = $this->getContainer(array(1 => 'first', 2 => 'second', 3 => 'third')); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes', $axes)); } @@ -221,7 +162,7 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas public function testIsWritableReturnsFalseIfOnlyRemoverExists() { $car = $this->getMock(__CLASS__.'_CarOnlyRemover'); - $axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third')); + $axes = $this->getContainer(array(1 => 'first', 2 => 'second', 3 => 'third')); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes', $axes)); } @@ -229,7 +170,7 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists() { $car = $this->getMock(__CLASS__.'_CarNoAdderAndRemover'); - $axes = $this->getCollection(array(1 => 'first', 2 => 'second', 3 => 'third')); + $axes = $this->getContainer(array(1 => 'first', 2 => 'second', 3 => 'third')); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes', $axes)); } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorNonTraversableArrayObjectTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorNonTraversableArrayObjectTest.php new file mode 100644 index 0000000000..6910d8be70 --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorNonTraversableArrayObjectTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests; + +use Symfony\Component\PropertyAccess\Tests\Fixtures\NonTraversableArrayObject; + +class PropertyAccessorNonTraversableArrayObjectTest extends PropertyAccessorArrayAccessTest +{ + protected function getContainer(array $array) + { + return new NonTraversableArrayObject($array); + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCustomArrayObjectTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTraversableArrayObjectTest.php similarity index 53% rename from src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCustomArrayObjectTest.php rename to src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTraversableArrayObjectTest.php index 7340df720f..4e45001176 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCustomArrayObjectTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTraversableArrayObjectTest.php @@ -11,12 +11,12 @@ namespace Symfony\Component\PropertyAccess\Tests; -use Symfony\Component\PropertyAccess\Tests\Fixtures\CustomArrayObject; +use Symfony\Component\PropertyAccess\Tests\Fixtures\TraversableArrayObject; -class PropertyAccessorCustomArrayObjectTest extends PropertyAccessorCollectionTest +class PropertyAccessorTraversableArrayObjectTest extends PropertyAccessorCollectionTest { - protected function getCollection(array $array) + protected function getContainer(array $array) { - return new CustomArrayObject($array); + return new TraversableArrayObject($array); } }