[PropertyAccess] Fixed getValue() when accessing non-existing indices of ArrayAccess implementations
This commit is contained in:
parent
b2855a55dd
commit
fef698e22f
@ -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;
|
||||
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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'));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user