[Form] Made PropertyPath deterministic: "[prop]" always refers to indices (array or ArrayAccess), "prop" always refers to properties
This commit is contained in:
parent
29963400e8
commit
c2a243f926
@ -21,25 +21,28 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$array = array('firstName' => 'Bernhard');
|
$array = array('firstName' => 'Bernhard');
|
||||||
|
|
||||||
$path = new PropertyPath('firstName');
|
$path = new PropertyPath('[firstName]');
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetValueIgnoresSingular()
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyException
|
||||||
|
*/
|
||||||
|
public function testGetValueThrowsExceptionIfIndexNotationExpected()
|
||||||
{
|
{
|
||||||
$array = array('children' => 'Many');
|
$array = array('firstName' => 'Bernhard');
|
||||||
|
|
||||||
$path = new PropertyPath('children|child');
|
$path = new PropertyPath('firstName');
|
||||||
|
|
||||||
$this->assertEquals('Many', $path->getValue($array));
|
$path->getValue($array);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetValueReadsZeroIndex()
|
public function testGetValueReadsZeroIndex()
|
||||||
{
|
{
|
||||||
$array = array('Bernhard');
|
$array = array('Bernhard');
|
||||||
|
|
||||||
$path = new PropertyPath('0');
|
$path = new PropertyPath('[0]');
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||||
}
|
}
|
||||||
@ -53,20 +56,11 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetValueReadsElementWithSpecialCharsExceptDot()
|
|
||||||
{
|
|
||||||
$array = array('%!@$§' => 'Bernhard');
|
|
||||||
|
|
||||||
$path = new PropertyPath('%!@$§');
|
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetValueReadsNestedIndexWithSpecialChars()
|
public function testGetValueReadsNestedIndexWithSpecialChars()
|
||||||
{
|
{
|
||||||
$array = array('root' => array('%!@$§.' => 'Bernhard'));
|
$array = array('root' => array('%!@$§.' => 'Bernhard'));
|
||||||
|
|
||||||
$path = new PropertyPath('root[%!@$§.]');
|
$path = new PropertyPath('[root][%!@$§.]');
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||||
}
|
}
|
||||||
@ -75,7 +69,7 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$array = array('child' => array('index' => array('firstName' => 'Bernhard')));
|
$array = array('child' => array('index' => array('firstName' => 'Bernhard')));
|
||||||
|
|
||||||
$path = new PropertyPath('child[index].firstName');
|
$path = new PropertyPath('[child][index][firstName]');
|
||||||
|
|
||||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||||
}
|
}
|
||||||
@ -84,7 +78,7 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$array = array('child' => array('index' => array()));
|
$array = array('child' => array('index' => array()));
|
||||||
|
|
||||||
$path = new PropertyPath('child[index].firstName');
|
$path = new PropertyPath('[child][index][firstName]');
|
||||||
|
|
||||||
$this->assertNull($path->getValue($array));
|
$this->assertNull($path->getValue($array));
|
||||||
}
|
}
|
||||||
@ -99,6 +93,24 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('Bernhard', $path->getValue($object));
|
$this->assertEquals('Bernhard', $path->getValue($object));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetValueIgnoresSingular()
|
||||||
|
{
|
||||||
|
$object = (object) array('children' => 'Many');
|
||||||
|
|
||||||
|
$path = new PropertyPath('children|child');
|
||||||
|
|
||||||
|
$this->assertEquals('Many', $path->getValue($object));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetValueReadsPropertyWithSpecialCharsExceptDot()
|
||||||
|
{
|
||||||
|
$array = (object) array('%!@$§' => 'Bernhard');
|
||||||
|
|
||||||
|
$path = new PropertyPath('%!@$§');
|
||||||
|
|
||||||
|
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||||
|
}
|
||||||
|
|
||||||
public function testGetValueReadsPropertyWithCustomPropertyPath()
|
public function testGetValueReadsPropertyWithCustomPropertyPath()
|
||||||
{
|
{
|
||||||
$object = new Author();
|
$object = new Author();
|
||||||
@ -121,21 +133,23 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('Bernhard', $path->getValue($object));
|
$this->assertEquals('Bernhard', $path->getValue($object));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfArrayAccessExpected()
|
public function testGetValueThrowsExceptionIfArrayAccessExpected()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('[firstName]');
|
$path = new PropertyPath('[firstName]');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidPropertyException');
|
|
||||||
|
|
||||||
$path->getValue(new Author());
|
$path->getValue(new Author());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfPropertyIsNotPublic()
|
public function testGetValueThrowsExceptionIfPropertyIsNotPublic()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('privateProperty');
|
$path = new PropertyPath('privateProperty');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\PropertyAccessDeniedException');
|
|
||||||
|
|
||||||
$path->getValue(new Author());
|
$path->getValue(new Author());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,12 +173,13 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('Schussek', $path->getValue($object));
|
$this->assertEquals('Schussek', $path->getValue($object));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfGetterIsNotPublic()
|
public function testGetValueThrowsExceptionIfGetterIsNotPublic()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('privateGetter');
|
$path = new PropertyPath('privateGetter');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\PropertyAccessDeniedException');
|
|
||||||
|
|
||||||
$path->getValue(new Author());
|
$path->getValue(new Author());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,48 +213,53 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertSame('foobar', $path->getValue($object));
|
$this->assertSame('foobar', $path->getValue($object));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfIsserIsNotPublic()
|
public function testGetValueThrowsExceptionIfIsserIsNotPublic()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('privateIsser');
|
$path = new PropertyPath('privateIsser');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\PropertyAccessDeniedException');
|
|
||||||
|
|
||||||
$path->getValue(new Author());
|
$path->getValue(new Author());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfPropertyDoesNotExist()
|
public function testGetValueThrowsExceptionIfPropertyDoesNotExist()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('foobar');
|
$path = new PropertyPath('foobar');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidPropertyException');
|
|
||||||
|
|
||||||
$path->getValue(new Author());
|
$path->getValue(new Author());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfNotObjectOrArray()
|
public function testGetValueThrowsExceptionIfNotObjectOrArray()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('foobar');
|
$path = new PropertyPath('foobar');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
|
|
||||||
|
|
||||||
$path->getValue('baz');
|
$path->getValue('baz');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfNull()
|
public function testGetValueThrowsExceptionIfNull()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('foobar');
|
$path = new PropertyPath('foobar');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
|
|
||||||
|
|
||||||
$path->getValue(null);
|
$path->getValue(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||||
|
*/
|
||||||
public function testGetValueThrowsExceptionIfEmpty()
|
public function testGetValueThrowsExceptionIfEmpty()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('foobar');
|
$path = new PropertyPath('foobar');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
|
|
||||||
|
|
||||||
$path->getValue('');
|
$path->getValue('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,17 +267,28 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$array = array();
|
$array = array();
|
||||||
|
|
||||||
$path = new PropertyPath('firstName');
|
$path = new PropertyPath('[firstName]');
|
||||||
$path->setValue($array, 'Bernhard');
|
$path->setValue($array, 'Bernhard');
|
||||||
|
|
||||||
$this->assertEquals(array('firstName' => 'Bernhard'), $array);
|
$this->assertEquals(array('firstName' => 'Bernhard'), $array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyException
|
||||||
|
*/
|
||||||
|
public function testSetValueThrowsExceptionIfIndexNotationExpected()
|
||||||
|
{
|
||||||
|
$array = array();
|
||||||
|
|
||||||
|
$path = new PropertyPath('firstName');
|
||||||
|
$path->setValue($array, 'Bernhard');
|
||||||
|
}
|
||||||
|
|
||||||
public function testSetValueUpdatesArraysWithCustomPropertyPath()
|
public function testSetValueUpdatesArraysWithCustomPropertyPath()
|
||||||
{
|
{
|
||||||
$array = array();
|
$array = array();
|
||||||
|
|
||||||
$path = new PropertyPath('child[index].firstName');
|
$path = new PropertyPath('[child][index][firstName]');
|
||||||
$path->setValue($array, 'Bernhard');
|
$path->setValue($array, 'Bernhard');
|
||||||
|
|
||||||
$this->assertEquals(array('child' => array('index' => array('firstName' => 'Bernhard'))), $array);
|
$this->assertEquals(array('child' => array('index' => array('firstName' => 'Bernhard'))), $array);
|
||||||
@ -305,12 +336,13 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('foobar', $object->__get('magicProperty'));
|
$this->assertEquals('foobar', $object->__get('magicProperty'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyException
|
||||||
|
*/
|
||||||
public function testSetValueThrowsExceptionIfArrayAccessExpected()
|
public function testSetValueThrowsExceptionIfArrayAccessExpected()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('[firstName]');
|
$path = new PropertyPath('[firstName]');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidPropertyException');
|
|
||||||
|
|
||||||
$path->setValue(new Author(), 'Bernhard');
|
$path->setValue(new Author(), 'Bernhard');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,42 +366,46 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('Schussek', $object->getLastName());
|
$this->assertEquals('Schussek', $object->getLastName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||||
|
*/
|
||||||
public function testSetValueThrowsExceptionIfGetterIsNotPublic()
|
public function testSetValueThrowsExceptionIfGetterIsNotPublic()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('privateSetter');
|
$path = new PropertyPath('privateSetter');
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\PropertyAccessDeniedException');
|
|
||||||
|
|
||||||
$path->setValue(new Author(), 'foobar');
|
$path->setValue(new Author(), 'foobar');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||||
|
*/
|
||||||
public function testSetValueThrowsExceptionIfNotObjectOrArray()
|
public function testSetValueThrowsExceptionIfNotObjectOrArray()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('foobar');
|
$path = new PropertyPath('foobar');
|
||||||
$value = 'baz';
|
$value = 'baz';
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
|
|
||||||
|
|
||||||
$path->setValue($value, 'bam');
|
$path->setValue($value, 'bam');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||||
|
*/
|
||||||
public function testSetValueThrowsExceptionIfNull()
|
public function testSetValueThrowsExceptionIfNull()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('foobar');
|
$path = new PropertyPath('foobar');
|
||||||
$value = null;
|
$value = null;
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
|
|
||||||
|
|
||||||
$path->setValue($value, 'bam');
|
$path->setValue($value, 'bam');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||||
|
*/
|
||||||
public function testSetValueThrowsExceptionIfEmpty()
|
public function testSetValueThrowsExceptionIfEmpty()
|
||||||
{
|
{
|
||||||
$path = new PropertyPath('foobar');
|
$path = new PropertyPath('foobar');
|
||||||
$value = '';
|
$value = '';
|
||||||
|
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
|
|
||||||
|
|
||||||
$path->setValue($value, 'bam');
|
$path->setValue($value, 'bam');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,31 +416,35 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('reference.traversable[index].property', $path->__toString());
|
$this->assertEquals('reference.traversable[index].property', $path->__toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||||
|
*/
|
||||||
public function testInvalidPropertyPath_noDotBeforeProperty()
|
public function testInvalidPropertyPath_noDotBeforeProperty()
|
||||||
{
|
{
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidPropertyPathException');
|
|
||||||
|
|
||||||
new PropertyPath('[index]property');
|
new PropertyPath('[index]property');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||||
|
*/
|
||||||
public function testInvalidPropertyPath_dotAtTheBeginning()
|
public function testInvalidPropertyPath_dotAtTheBeginning()
|
||||||
{
|
{
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidPropertyPathException');
|
|
||||||
|
|
||||||
new PropertyPath('.property');
|
new PropertyPath('.property');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||||
|
*/
|
||||||
public function testInvalidPropertyPath_unexpectedCharacters()
|
public function testInvalidPropertyPath_unexpectedCharacters()
|
||||||
{
|
{
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidPropertyPathException');
|
|
||||||
|
|
||||||
new PropertyPath('property.$form');
|
new PropertyPath('property.$form');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||||
|
*/
|
||||||
public function testInvalidPropertyPath_null()
|
public function testInvalidPropertyPath_null()
|
||||||
{
|
{
|
||||||
$this->setExpectedException('Symfony\Component\Form\Exception\InvalidPropertyPathException');
|
|
||||||
|
|
||||||
new PropertyPath(null);
|
new PropertyPath(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +69,11 @@ class PropertyPath implements \IteratorAggregate
|
|||||||
private $positions;
|
private $positions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the given property path
|
* Constructs a property path from a string.
|
||||||
*
|
*
|
||||||
* @param string $propertyPath
|
* @param string $propertyPath The property path as string.
|
||||||
|
*
|
||||||
|
* @throws InvalidPropertyPathException If the syntax of the property path is not valid.
|
||||||
*/
|
*/
|
||||||
public function __construct($propertyPath)
|
public function __construct($propertyPath)
|
||||||
{
|
{
|
||||||
@ -258,25 +260,7 @@ class PropertyPath implements \IteratorAggregate
|
|||||||
*/
|
*/
|
||||||
public function getValue($objectOrArray)
|
public function getValue($objectOrArray)
|
||||||
{
|
{
|
||||||
for ($i = 0; $i < $this->length; ++$i) {
|
return $this->readPropertyAt($objectOrArray, $this->length - 1);
|
||||||
if (is_object($objectOrArray)) {
|
|
||||||
$value = $this->readProperty($objectOrArray, $i);
|
|
||||||
// arrays need to be treated separately (due to PHP bug?)
|
|
||||||
// http://bugs.php.net/bug.php?id=52133
|
|
||||||
} elseif (is_array($objectOrArray)) {
|
|
||||||
$property = $this->elements[$i];
|
|
||||||
if (!array_key_exists($property, $objectOrArray)) {
|
|
||||||
$objectOrArray[$property] = $i + 1 < $this->length ? array() : null;
|
|
||||||
}
|
|
||||||
$value =& $objectOrArray[$property];
|
|
||||||
} else {
|
|
||||||
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
|
||||||
}
|
|
||||||
|
|
||||||
$objectOrArray =& $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -299,63 +283,89 @@ class PropertyPath implements \IteratorAggregate
|
|||||||
*
|
*
|
||||||
* If neither is found, an exception is thrown.
|
* If neither is found, an exception is thrown.
|
||||||
*
|
*
|
||||||
* @param object|array $objectOrArray The object or array to traverse
|
* @param object|array $objectOrArray The object or array to modify.
|
||||||
* @param mixed $value The value at the end of the property path
|
* @param mixed $value The value to set at the end of the property path.
|
||||||
*
|
*
|
||||||
* @throws InvalidPropertyException If the property/setter does not exist
|
* @throws InvalidPropertyException If a property does not exist.
|
||||||
* @throws PropertyAccessDeniedException If the property/setter exists but is not public
|
* @throws PropertyAccessDeniedException If a property cannot be accessed due to
|
||||||
|
* access restrictions (private or protected).
|
||||||
|
* @throws UnexpectedTypeException If a value within the path is neither object
|
||||||
|
* nor array.
|
||||||
*/
|
*/
|
||||||
public function setValue(&$objectOrArray, $value)
|
public function setValue(&$objectOrArray, $value)
|
||||||
{
|
{
|
||||||
for ($i = 0, $l = $this->length - 1; $i < $l; ++$i) {
|
$objectOrArray =& $this->readPropertyAt($objectOrArray, $this->length - 2);
|
||||||
|
|
||||||
if (is_object($objectOrArray)) {
|
|
||||||
$nestedObject = $this->readProperty($objectOrArray, $i);
|
|
||||||
// arrays need to be treated separately (due to PHP bug?)
|
|
||||||
// http://bugs.php.net/bug.php?id=52133
|
|
||||||
} elseif (is_array($objectOrArray)) {
|
|
||||||
$property = $this->elements[$i];
|
|
||||||
if (!array_key_exists($property, $objectOrArray)) {
|
|
||||||
$objectOrArray[$property] = array();
|
|
||||||
}
|
|
||||||
$nestedObject =& $objectOrArray[$property];
|
|
||||||
} else {
|
|
||||||
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
|
||||||
}
|
|
||||||
|
|
||||||
$objectOrArray =& $nestedObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||||
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->writeProperty($objectOrArray, $i, $value);
|
$property = $this->elements[$this->length - 1];
|
||||||
|
$singular = $this->singulars[$this->length - 1];
|
||||||
|
$isIndex = $this->isIndex[$this->length - 1];
|
||||||
|
|
||||||
|
$this->writeProperty($objectOrArray, $property, $singular, $isIndex, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the value of the property at the given index in the path
|
* Reads the path from an object up to a given path index.
|
||||||
*
|
*
|
||||||
* @param object $object The object to read from
|
* @param object|array $objectOrArray The object or array to read from.
|
||||||
* @param integer $currentIndex The index of the read property in the path
|
* @param integer $index The integer up to which should be read.
|
||||||
*
|
*
|
||||||
* @return mixed The value of the property
|
* @return mixed The value read at the end of the path.
|
||||||
|
*
|
||||||
|
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
||||||
*/
|
*/
|
||||||
protected function readProperty($object, $currentIndex)
|
protected function &readPropertyAt(&$objectOrArray, $index)
|
||||||
{
|
{
|
||||||
$property = $this->elements[$currentIndex];
|
for ($i = 0; $i <= $index; ++$i) {
|
||||||
|
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||||
if ($this->isIndex[$currentIndex]) {
|
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
||||||
if (!$object instanceof \ArrayAccess) {
|
|
||||||
throw new InvalidPropertyException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($object)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($object[$property])) {
|
// Create missing nested arrays on demand
|
||||||
return $object[$property];
|
if (is_array($objectOrArray) && !array_key_exists($this->elements[$i], $objectOrArray)) {
|
||||||
|
$objectOrArray[$this->elements[$i]] = $i + 1 < $this->length ? array() : null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
$property = $this->elements[$i];
|
||||||
|
$isIndex = $this->isIndex[$i];
|
||||||
|
|
||||||
|
$objectOrArray =& $this->readProperty($objectOrArray, $property, $isIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $objectOrArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the a property from an object or array.
|
||||||
|
*
|
||||||
|
* @param object|array $objectOrArray The object or array to read from.
|
||||||
|
* @param string $property The property to read.
|
||||||
|
* @param integer $isIndex Whether to interpret the property as index.
|
||||||
|
*
|
||||||
|
* @return mixed The value of the read property
|
||||||
|
*
|
||||||
|
* @throws InvalidPropertyException If the property does not exist.
|
||||||
|
* @throws PropertyAccessDeniedException If the property cannot be accessed due to
|
||||||
|
* access restrictions (private or protected).
|
||||||
|
*/
|
||||||
|
protected function &readProperty(&$objectOrArray, $property, $isIndex)
|
||||||
|
{
|
||||||
|
$result = null;
|
||||||
|
|
||||||
|
if ($isIndex) {
|
||||||
|
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
||||||
|
throw new InvalidPropertyException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($objectOrArray)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($objectOrArray[$property])) {
|
||||||
|
$result =& $objectOrArray[$property];
|
||||||
|
}
|
||||||
|
} elseif (is_object($objectOrArray)) {
|
||||||
$camelProp = $this->camelize($property);
|
$camelProp = $this->camelize($property);
|
||||||
$reflClass = new ReflectionClass($object);
|
$reflClass = new ReflectionClass($objectOrArray);
|
||||||
$getter = 'get'.$camelProp;
|
$getter = 'get'.$camelProp;
|
||||||
$isser = 'is'.$camelProp;
|
$isser = 'is'.$camelProp;
|
||||||
$hasser = 'has'.$camelProp;
|
$hasser = 'has'.$camelProp;
|
||||||
@ -365,50 +375,58 @@ class PropertyPath implements \IteratorAggregate
|
|||||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $getter, $reflClass->getName()));
|
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $getter, $reflClass->getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object->$getter();
|
$result = $objectOrArray->$getter();
|
||||||
} elseif ($reflClass->hasMethod($isser)) {
|
} elseif ($reflClass->hasMethod($isser)) {
|
||||||
if (!$reflClass->getMethod($isser)->isPublic()) {
|
if (!$reflClass->getMethod($isser)->isPublic()) {
|
||||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $isser, $reflClass->getName()));
|
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $isser, $reflClass->getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object->$isser();
|
$result = $objectOrArray->$isser();
|
||||||
} elseif ($reflClass->hasMethod($hasser)) {
|
} elseif ($reflClass->hasMethod($hasser)) {
|
||||||
if (!$reflClass->getMethod($hasser)->isPublic()) {
|
if (!$reflClass->getMethod($hasser)->isPublic()) {
|
||||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $hasser, $reflClass->getName()));
|
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $hasser, $reflClass->getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object->$hasser();
|
$result = $objectOrArray->$hasser();
|
||||||
} elseif ($reflClass->hasMethod('__get')) {
|
} elseif ($reflClass->hasMethod('__get')) {
|
||||||
// needed to support magic method __get
|
// needed to support magic method __get
|
||||||
return $object->$property;
|
$result =& $objectOrArray->$property;
|
||||||
} elseif ($reflClass->hasProperty($property)) {
|
} elseif ($reflClass->hasProperty($property)) {
|
||||||
if (!$reflClass->getProperty($property)->isPublic()) {
|
if (!$reflClass->getProperty($property)->isPublic()) {
|
||||||
throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "%s()" or "%s()"?', $property, $reflClass->getName(), $getter, $isser));
|
throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "%s()" or "%s()"?', $property, $reflClass->getName(), $getter, $isser));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $object->$property;
|
$result =& $objectOrArray->$property;
|
||||||
} elseif (property_exists($object, $property)) {
|
} elseif (property_exists($objectOrArray, $property)) {
|
||||||
// needed to support \stdClass instances
|
// needed to support \stdClass instances
|
||||||
return $object->$property;
|
$result =& $objectOrArray->$property;
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidPropertyException(sprintf('Neither property "%s" nor method "%s()" nor method "%s()" exists in class "%s"', $property, $getter, $isser, $reflClass->getName()));
|
throw new InvalidPropertyException(sprintf('Neither property "%s" nor method "%s()" nor method "%s()" exists in class "%s"', $property, $getter, $isser, $reflClass->getName()));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new InvalidPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of the property at the given index in the path
|
* Sets the value of the property at the given index in the path
|
||||||
*
|
*
|
||||||
* @param object $objectOrArray The object or array to traverse
|
* @param object|array $objectOrArray The object or array to write to.
|
||||||
* @param integer $currentIndex The index of the modified property in the path
|
* @param string $property The property to write.
|
||||||
* @param mixed $value The value to set
|
* @param string $singular The singular form of the property name or null.
|
||||||
|
* @param integer $isIndex Whether to interpret the property as index.
|
||||||
|
* @param mixed $value The value to write.
|
||||||
|
*
|
||||||
|
* @throws InvalidPropertyException If the property does not exist.
|
||||||
|
* @throws PropertyAccessDeniedException If the property cannot be accessed due to
|
||||||
|
* access restrictions (private or protected).
|
||||||
*/
|
*/
|
||||||
protected function writeProperty(&$objectOrArray, $currentIndex, $value)
|
protected function writeProperty(&$objectOrArray, $property, $singular, $isIndex, $value)
|
||||||
{
|
{
|
||||||
$property = $this->elements[$currentIndex];
|
if ($isIndex) {
|
||||||
|
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
||||||
if (is_object($objectOrArray) && $this->isIndex[$currentIndex]) {
|
|
||||||
if (!$objectOrArray instanceof \ArrayAccess) {
|
|
||||||
throw new InvalidPropertyException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($objectOrArray)));
|
throw new InvalidPropertyException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($objectOrArray)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +440,6 @@ class PropertyPath implements \IteratorAggregate
|
|||||||
|
|
||||||
// Check if the parent has matching methods to add/remove items
|
// Check if the parent has matching methods to add/remove items
|
||||||
if (is_array($value) || $value instanceof Traversable) {
|
if (is_array($value) || $value instanceof Traversable) {
|
||||||
$singular = $this->singulars[$currentIndex];
|
|
||||||
if (null !== $singular) {
|
if (null !== $singular) {
|
||||||
$addMethod = 'add' . ucfirst($singular);
|
$addMethod = 'add' . ucfirst($singular);
|
||||||
$removeMethod = 'remove' . ucfirst($singular);
|
$removeMethod = 'remove' . ucfirst($singular);
|
||||||
@ -490,7 +507,7 @@ class PropertyPath implements \IteratorAggregate
|
|||||||
if ($addMethod && $removeMethod) {
|
if ($addMethod && $removeMethod) {
|
||||||
$itemsToAdd = is_object($value) ? clone $value : $value;
|
$itemsToAdd = is_object($value) ? clone $value : $value;
|
||||||
$itemToRemove = array();
|
$itemToRemove = array();
|
||||||
$previousValue = $this->readProperty($objectOrArray, $currentIndex);
|
$previousValue = $this->readProperty($objectOrArray, $property, $isIndex);
|
||||||
|
|
||||||
if (is_array($previousValue) || $previousValue instanceof Traversable) {
|
if (is_array($previousValue) || $previousValue instanceof Traversable) {
|
||||||
foreach ($previousValue as $previousItem) {
|
foreach ($previousValue as $previousItem) {
|
||||||
@ -538,21 +555,38 @@ class PropertyPath implements \IteratorAggregate
|
|||||||
throw new InvalidPropertyException(sprintf('Neither element "%s" nor method "%s()" exists in class "%s"', $property, $setter, $reflClass->getName()));
|
throw new InvalidPropertyException(sprintf('Neither element "%s" nor method "%s()" exists in class "%s"', $property, $setter, $reflClass->getName()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$objectOrArray[$property] = $value;
|
throw new InvalidPropertyException(sprintf('Cannot write property "%s" in an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function camelize($property)
|
/**
|
||||||
|
* Camelizes a given string.
|
||||||
|
*
|
||||||
|
* @param string $string Some string.
|
||||||
|
*
|
||||||
|
* @return string The camelized version of the string.
|
||||||
|
*/
|
||||||
|
protected function camelize($string)
|
||||||
{
|
{
|
||||||
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $property);
|
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isAccessible(ReflectionClass $reflClass, $methodName, $numberOfRequiredParameters)
|
/**
|
||||||
|
* Returns whether a method is public and has a specific number of required parameters.
|
||||||
|
*
|
||||||
|
* @param \ReflectionClass $class The class of the method.
|
||||||
|
* @param string $methodName The method name.
|
||||||
|
* @param integer $parameters The number of parameters.
|
||||||
|
*
|
||||||
|
* @return Boolean Whether the method is public and has $parameters
|
||||||
|
* required parameters.
|
||||||
|
*/
|
||||||
|
private function isAccessible(ReflectionClass $class, $methodName, $parameters)
|
||||||
{
|
{
|
||||||
if ($reflClass->hasMethod($methodName)) {
|
if ($class->hasMethod($methodName)) {
|
||||||
$method = $reflClass->getMethod($methodName);
|
$method = $class->getMethod($methodName);
|
||||||
|
|
||||||
if ($method->isPublic() && $method->getNumberOfRequiredParameters() === $numberOfRequiredParameters) {
|
if ($method->isPublic() && $method->getNumberOfRequiredParameters() === $parameters) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user