Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[2.5][PropertyAccess] Fixed getValue() when accessing non-existing indices of ArrayAccess implementations #10946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 21, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,22 @@ private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $pr
// 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you sould keep it on 1 line, like previously

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@webmozart why use print_r? It prints way more than the available keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tobion Good question, actually. I kept the previous code, which was like this.

@stof I prefer to be able to actually read the line. Overall, we use both.

));
}

$objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* 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;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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'));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class PropertyAccessorArrayObjectTest extends PropertyAccessorCollectionTest
{
protected function getCollection(array $array)
protected function getContainer(array $array)
{
return new \ArrayObject($array);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class PropertyAccessorArrayTest extends PropertyAccessorCollectionTest
{
protected function getCollection(array $array)
protected function getContainer(array $array)
{
return $array;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

namespace Symfony\Component\PropertyAccess\Tests;

use Symfony\Component\PropertyAccess\PropertyAccessor;

class PropertyAccessorCollectionTest_Car
{
private $axes;
Expand Down Expand Up @@ -80,55 +78,13 @@ public function removeAxis($axis) {}
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
Expand All @@ -147,8 +103,8 @@ public function testSetValueCallsAdderAndRemoverForNestedCollections()
{
$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')
Expand Down Expand Up @@ -177,59 +133,44 @@ public function testSetValueCallsAdderAndRemoverForNestedCollections()
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);
}

/**
* @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);
$car->expects($this->any())
->method('getAxes')
->will($this->returnValue($axesBefore));

$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));
}

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));
}

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));
}

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));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}