1
0

first commit

This commit is contained in:
2020-08-06 14:58:51 +08:00
commit 17096657dc
780 changed files with 92857 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,31 @@
<?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;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ReturnTyped
{
public function getFoos(): array
{
return 'It doesn\'t respect the return type on purpose';
}
public function addFoo(\DateTime $dateTime)
{
}
public function removeFoo(\DateTime $dateTime)
{
}
}

View File

@@ -0,0 +1,187 @@
<?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;
class TestClass
{
public $publicProperty;
protected $protectedProperty;
private $privateProperty;
private $publicAccessor;
private $publicMethodAccessor;
private $publicGetSetter;
private $publicAccessorWithDefaultValue;
private $publicAccessorWithRequiredAndDefaultValue;
private $publicAccessorWithMoreRequiredParameters;
private $publicIsAccessor;
private $publicHasAccessor;
private $publicGetter;
private $date;
public function __construct($value)
{
$this->publicProperty = $value;
$this->publicAccessor = $value;
$this->publicMethodAccessor = $value;
$this->publicGetSetter = $value;
$this->publicAccessorWithDefaultValue = $value;
$this->publicAccessorWithRequiredAndDefaultValue = $value;
$this->publicAccessorWithMoreRequiredParameters = $value;
$this->publicIsAccessor = $value;
$this->publicHasAccessor = $value;
$this->publicGetter = $value;
}
public function setPublicAccessor($value)
{
$this->publicAccessor = $value;
}
public function setPublicAccessorWithDefaultValue($value = null)
{
$this->publicAccessorWithDefaultValue = $value;
}
public function setPublicAccessorWithRequiredAndDefaultValue($value, $optional = null)
{
$this->publicAccessorWithRequiredAndDefaultValue = $value;
}
public function setPublicAccessorWithMoreRequiredParameters($value, $needed)
{
$this->publicAccessorWithMoreRequiredParameters = $value;
}
public function getPublicAccessor()
{
return $this->publicAccessor;
}
public function getPublicAccessorWithDefaultValue()
{
return $this->publicAccessorWithDefaultValue;
}
public function getPublicAccessorWithRequiredAndDefaultValue()
{
return $this->publicAccessorWithRequiredAndDefaultValue;
}
public function getPublicAccessorWithMoreRequiredParameters()
{
return $this->publicAccessorWithMoreRequiredParameters;
}
public function setPublicIsAccessor($value)
{
$this->publicIsAccessor = $value;
}
public function isPublicIsAccessor()
{
return $this->publicIsAccessor;
}
public function setPublicHasAccessor($value)
{
$this->publicHasAccessor = $value;
}
public function hasPublicHasAccessor()
{
return $this->publicHasAccessor;
}
public function publicGetSetter($value = null)
{
if (null !== $value) {
$this->publicGetSetter = $value;
}
return $this->publicGetSetter;
}
public function getPublicMethodMutator()
{
return $this->publicGetSetter;
}
protected function setProtectedAccessor($value)
{
}
protected function getProtectedAccessor()
{
return 'foobar';
}
protected function setProtectedIsAccessor($value)
{
}
protected function isProtectedIsAccessor()
{
return 'foobar';
}
protected function setProtectedHasAccessor($value)
{
}
protected function hasProtectedHasAccessor()
{
return 'foobar';
}
private function setPrivateAccessor($value)
{
}
private function getPrivateAccessor()
{
return 'foobar';
}
private function setPrivateIsAccessor($value)
{
}
private function isPrivateIsAccessor()
{
return 'foobar';
}
private function setPrivateHasAccessor($value)
{
}
private function hasPrivateHasAccessor()
{
return 'foobar';
}
public function getPublicGetter()
{
return $this->publicGetter;
}
public function setDate(\DateTimeInterface $date)
{
$this->date = $date;
}
public function getDate()
{
return $this->date;
}
}

View File

@@ -0,0 +1,27 @@
<?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;
class TestClassIsWritable
{
protected $value;
public function getValue()
{
return $this->value;
}
public function __construct($value)
{
$this->value = $value;
}
}

View File

@@ -0,0 +1,37 @@
<?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;
class TestClassMagicCall
{
private $magicCallProperty;
public function __construct($value)
{
$this->magicCallProperty = $value;
}
public function __call($method, array $args)
{
if ('getMagicCallProperty' === $method) {
return $this->magicCallProperty;
}
if ('getConstantMagicCallProperty' === $method) {
return 'constant value';
}
if ('setMagicCallProperty' === $method) {
$this->magicCallProperty = reset($args);
}
}
}

View File

@@ -0,0 +1,42 @@
<?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;
class TestClassMagicGet
{
private $magicProperty;
public $publicProperty;
public function __construct($value)
{
$this->magicProperty = $value;
}
public function __set($property, $value)
{
if ('magicProperty' === $property) {
$this->magicProperty = $value;
}
}
public function __get($property)
{
if ('magicProperty' === $property) {
return $this->magicProperty;
}
if ('constantMagicProperty' === $property) {
return 'constant value';
}
}
}

View File

@@ -0,0 +1,32 @@
<?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;
class TestClassSetValue
{
protected $value;
public function getValue()
{
return $this->value;
}
public function setValue($value)
{
$this->value = $value;
}
public function __construct($value)
{
$this->value = $value;
}
}

View File

@@ -0,0 +1,28 @@
<?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;
class TestClassTypeErrorInsideCall
{
public function expectsDateTime(\DateTime $date)
{
}
public function getProperty()
{
}
public function setProperty($property)
{
$this->expectsDateTime(null); // throws TypeError
}
}

View File

@@ -0,0 +1,31 @@
<?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;
class Ticket5775Object
{
private $property;
public function getProperty()
{
return $this->property;
}
private function setProperty()
{
}
public function __set($property, $value)
{
$this->$property = $value;
}
}

View File

@@ -0,0 +1,70 @@
<?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 TraversableArrayObject implements \ArrayAccess, \IteratorAggregate, \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 getIterator()
{
return new \ArrayIterator($this->array);
}
public function count()
{
return count($this->array);
}
public function serialize()
{
return serialize($this->array);
}
public function unserialize($serialized)
{
$this->array = (array) unserialize((string) $serialized);
}
}

View File

@@ -0,0 +1,51 @@
<?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;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class TypeHinted
{
private $date;
/**
* @var \Countable
*/
private $countable;
public function setDate(\DateTime $date)
{
$this->date = $date;
}
public function getDate()
{
return $this->date;
}
/**
* @return \Countable
*/
public function getCountable()
{
return $this->countable;
}
/**
* @param \Countable $countable
*/
public function setCountable(\Countable $countable)
{
$this->countable = $countable;
}
}

View File

@@ -0,0 +1,87 @@
<?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 PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
abstract class PropertyAccessorArrayAccessTest extends 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));
}
}

View File

@@ -0,0 +1,20 @@
<?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;
class PropertyAccessorArrayObjectTest extends PropertyAccessorCollectionTest
{
protected function getContainer(array $array)
{
return new \ArrayObject($array);
}
}

View File

@@ -0,0 +1,20 @@
<?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;
class PropertyAccessorArrayTest extends PropertyAccessorCollectionTest
{
protected function getContainer(array $array)
{
return $array;
}
}

View File

@@ -0,0 +1,66 @@
<?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 PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
class PropertyAccessorBuilderTest extends TestCase
{
/**
* @var PropertyAccessorBuilder
*/
protected $builder;
protected function setUp()
{
$this->builder = new PropertyAccessorBuilder();
}
protected function tearDown()
{
$this->builder = null;
}
public function testEnableMagicCall()
{
$this->assertSame($this->builder, $this->builder->enableMagicCall());
}
public function testDisableMagicCall()
{
$this->assertSame($this->builder, $this->builder->disableMagicCall());
}
public function testIsMagicCallEnable()
{
$this->assertFalse($this->builder->isMagicCallEnabled());
$this->assertTrue($this->builder->enableMagicCall()->isMagicCallEnabled());
$this->assertFalse($this->builder->disableMagicCall()->isMagicCallEnabled());
}
public function testGetPropertyAccessor()
{
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->enableMagicCall()->getPropertyAccessor());
}
public function testUseCache()
{
$cacheItemPool = new ArrayAdapter();
$this->builder->setCacheItemPool($cacheItemPool);
$this->assertEquals($cacheItemPool, $this->builder->getCacheItemPool());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
}
}

View File

@@ -0,0 +1,200 @@
<?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;
class PropertyAccessorCollectionTest_Car
{
private $axes;
public function __construct($axes = null)
{
$this->axes = $axes;
}
// In the test, use a name that StringUtil can't uniquely singularify
public function addAxis($axis)
{
$this->axes[] = $axis;
}
public function removeAxis($axis)
{
foreach ($this->axes as $key => $value) {
if ($value === $axis) {
unset($this->axes[$key]);
return;
}
}
}
public function getAxes()
{
return $this->axes;
}
}
class PropertyAccessorCollectionTest_CarOnlyAdder
{
public function addAxis($axis)
{
}
public function getAxes()
{
}
}
class PropertyAccessorCollectionTest_CarOnlyRemover
{
public function removeAxis($axis)
{
}
public function getAxes()
{
}
}
class PropertyAccessorCollectionTest_CarNoAdderAndRemover
{
public function getAxes()
{
}
}
class PropertyAccessorCollectionTest_CompositeCar
{
public function getStructure()
{
}
public function setStructure($structure)
{
}
}
class PropertyAccessorCollectionTest_CarStructure
{
public function addAxis($axis)
{
}
public function removeAxis($axis)
{
}
public function getAxes()
{
}
}
abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAccessTest
{
public function testSetValueCallsAdderAndRemoverForCollections()
{
$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
// modified while iterating them
$car = new PropertyAccessorCollectionTest_Car($axesBefore);
$this->propertyAccessor->setValue($car, 'axes', $axesMerged);
$this->assertEquals($axesAfter, $car->getAxes());
// The passed collection was not modified
$this->assertEquals($axesMergedCopy, $axesMerged);
}
public function testSetValueCallsAdderAndRemoverForNestedCollections()
{
$car = $this->getMockBuilder(__CLASS__.'_CompositeCar')->getMock();
$structure = $this->getMockBuilder(__CLASS__.'_CarStructure')->getMock();
$axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth'));
$axesAfter = $this->getContainer(array(0 => 'first', 1 => 'second', 2 => 'third'));
$car->expects($this->any())
->method('getStructure')
->will($this->returnValue($structure));
$structure->expects($this->at(0))
->method('getAxes')
->will($this->returnValue($axesBefore));
$structure->expects($this->at(1))
->method('removeAxis')
->with('fourth');
$structure->expects($this->at(2))
->method('addAxis')
->with('first');
$structure->expects($this->at(3))
->method('addAxis')
->with('third');
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
* @expectedExceptionMessage Could not determine access type for property "axes".
*/
public function testSetValueFailsIfNoAdderNorRemoverFound()
{
$car = $this->getMockBuilder(__CLASS__.'_CarNoAdderAndRemover')->getMock();
$axesBefore = $this->getContainer(array(1 => 'second', 3 => 'fourth'));
$axesAfter = $this->getContainer(array(0 => 'first', 1 => 'second', 2 => 'third'));
$car->expects($this->any())
->method('getAxes')
->will($this->returnValue($axesBefore));
$this->propertyAccessor->setValue($car, 'axes', $axesAfter);
}
public function testIsWritableReturnsTrueIfAdderAndRemoverExists()
{
$car = $this->getMockBuilder(__CLASS__.'_Car')->getMock();
$this->assertTrue($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfOnlyAdderExists()
{
$car = $this->getMockBuilder(__CLASS__.'_CarOnlyAdder')->getMock();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfOnlyRemoverExists()
{
$car = $this->getMockBuilder(__CLASS__.'_CarOnlyRemover')->getMock();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists()
{
$car = $this->getMockBuilder(__CLASS__.'_CarNoAdderAndRemover')->getMock();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
* expectedExceptionMessageRegExp /The property "axes" in class "Mock_PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis()", "removeAxis()" but the new value must be an array or an instance of \Traversable, "string" given./
*/
public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable()
{
$car = $this->getMockBuilder(__CLASS__.'_Car')->getMock();
$this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable');
}
}

View File

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

View File

@@ -0,0 +1,666 @@
<?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 PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassTypeErrorInsideCall;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
class PropertyAccessorTest extends TestCase
{
/**
* @var PropertyAccessor
*/
private $propertyAccessor;
protected function setUp()
{
$this->propertyAccessor = new PropertyAccessor();
}
public function getPathsWithUnexpectedType()
{
return array(
array('', 'foobar'),
array('foo', 'foobar'),
array(null, 'foobar'),
array(123, 'foobar'),
array((object) array('prop' => null), 'prop.foobar'),
array((object) array('prop' => (object) array('subProp' => null)), 'prop.subProp.foobar'),
array(array('index' => null), '[index][foobar]'),
array(array('index' => array('subIndex' => null)), '[index][subIndex][foobar]'),
);
}
public function getPathsWithMissingProperty()
{
return array(
array((object) array('firstName' => 'Bernhard'), 'lastName'),
array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.lastName'),
array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].lastName'),
array(new TestClass('Bernhard'), 'protectedProperty'),
array(new TestClass('Bernhard'), 'privateProperty'),
array(new TestClass('Bernhard'), 'protectedAccessor'),
array(new TestClass('Bernhard'), 'protectedIsAccessor'),
array(new TestClass('Bernhard'), 'protectedHasAccessor'),
array(new TestClass('Bernhard'), 'privateAccessor'),
array(new TestClass('Bernhard'), 'privateIsAccessor'),
array(new TestClass('Bernhard'), 'privateHasAccessor'),
// Properties are not camelized
array(new TestClass('Bernhard'), 'public_property'),
);
}
public function getPathsWithMissingIndex()
{
return array(
array(array('firstName' => 'Bernhard'), '[lastName]'),
array(array(), '[index][lastName]'),
array(array('index' => array()), '[index][lastName]'),
array(array('index' => array('firstName' => 'Bernhard')), '[index][lastName]'),
array((object) array('property' => array('firstName' => 'Bernhard')), 'property[lastName]'),
);
}
/**
* @dataProvider getValidPropertyPaths
*/
public function testGetValue($objectOrArray, $path, $value)
{
$this->assertSame($value, $this->propertyAccessor->getValue($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingProperty
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testGetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
{
$this->propertyAccessor->getValue($objectOrArray, $path);
}
/**
* @dataProvider getPathsWithMissingIndex
*/
public function testGetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $path)
{
$this->assertNull($this->propertyAccessor->getValue($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingIndex
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
*/
public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
$this->propertyAccessor = new PropertyAccessor(false, true);
$this->propertyAccessor->getValue($objectOrArray, $path);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
*/
public function testGetValueThrowsExceptionIfNotArrayAccess()
{
$this->propertyAccessor->getValue(new \stdClass(), '[index]');
}
public function testGetValueReadsMagicGet()
{
$this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'magicProperty'));
}
public function testGetValueReadsArrayWithMissingIndexForCustomPropertyPath()
{
$object = new \ArrayObject();
$array = array('child' => array('index' => $object));
$this->assertNull($this->propertyAccessor->getValue($array, '[child][index][foo][bar]'));
$this->assertSame(array(), $object->getArrayCopy());
}
// https://github.com/symfony/symfony/pull/4450
public function testGetValueReadsMagicGetThatReturnsConstant()
{
$this->assertSame('constant value', $this->propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'constantMagicProperty'));
}
public function testGetValueNotModifyObject()
{
$object = new \stdClass();
$object->firstName = array('Bernhard');
$this->assertNull($this->propertyAccessor->getValue($object, 'firstName[1]'));
$this->assertSame(array('Bernhard'), $object->firstName);
}
public function testGetValueNotModifyObjectException()
{
$propertyAccessor = new PropertyAccessor(false, true);
$object = new \stdClass();
$object->firstName = array('Bernhard');
try {
$propertyAccessor->getValue($object, 'firstName[1]');
} catch (NoSuchIndexException $e) {
}
$this->assertSame(array('Bernhard'), $object->firstName);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testGetValueDoesNotReadMagicCallByDefault()
{
$this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty');
}
public function testGetValueReadsMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
$this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
// https://github.com/symfony/symfony/pull/4450
public function testGetValueReadsMagicCallThatReturnsConstant()
{
$this->propertyAccessor = new PropertyAccessor(true);
$this->assertSame('constant value', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'constantMagicCallProperty'));
}
/**
* @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
* @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/
public function testGetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{
$this->propertyAccessor->getValue($objectOrArray, $path);
}
/**
* @dataProvider getValidPropertyPaths
*/
public function testSetValue($objectOrArray, $path)
{
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingProperty
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testSetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
{
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
}
/**
* @dataProvider getPathsWithMissingIndex
*/
public function testSetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $path)
{
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingIndex
*/
public function testSetValueThrowsNoExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
$this->propertyAccessor = new PropertyAccessor(false, true);
$this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
$this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
*/
public function testSetValueThrowsExceptionIfNotArrayAccess()
{
$object = new \stdClass();
$this->propertyAccessor->setValue($object, '[index]', 'Updated');
}
public function testSetValueUpdatesMagicSet()
{
$author = new TestClassMagicGet('Bernhard');
$this->propertyAccessor->setValue($author, 'magicProperty', 'Updated');
$this->assertEquals('Updated', $author->__get('magicProperty'));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testSetValueThrowsExceptionIfThereAreMissingParameters()
{
$object = new TestClass('Bernhard');
$this->propertyAccessor->setValue($object, 'publicAccessorWithMoreRequiredParameters', 'Updated');
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
*/
public function testSetValueDoesNotUpdateMagicCallByDefault()
{
$author = new TestClassMagicCall('Bernhard');
$this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
}
public function testSetValueUpdatesMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
$author = new TestClassMagicCall('Bernhard');
$this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
$this->assertEquals('Updated', $author->__call('getMagicCallProperty', array()));
}
/**
* @dataProvider getPathsWithUnexpectedType
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
* @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
*/
public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
{
$this->propertyAccessor->setValue($objectOrArray, $path, 'value');
}
public function testGetValueWhenArrayValueIsNull()
{
$this->propertyAccessor = new PropertyAccessor(false, true);
$this->assertNull($this->propertyAccessor->getValue(array('index' => array('nullable' => null)), '[index][nullable]'));
}
/**
* @dataProvider getValidPropertyPaths
*/
public function testIsReadable($objectOrArray, $path)
{
$this->assertTrue($this->propertyAccessor->isReadable($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingProperty
*/
public function testIsReadableReturnsFalseIfPropertyNotFound($objectOrArray, $path)
{
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingIndex
*/
public function testIsReadableReturnsTrueIfIndexNotFound($objectOrArray, $path)
{
// Non-existing indices can be read. In this case, null is returned
$this->assertTrue($this->propertyAccessor->isReadable($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingIndex
*/
public function testIsReadableReturnsFalseIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
$this->propertyAccessor = new PropertyAccessor(false, true);
// When exceptions are enabled, non-existing indices cannot be read
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
}
public function testIsReadableRecognizesMagicGet()
{
$this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicGet('Bernhard'), 'magicProperty'));
}
public function testIsReadableDoesNotRecognizeMagicCallByDefault()
{
$this->assertFalse($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
public function testIsReadableRecognizesMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
$this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
/**
* @dataProvider getPathsWithUnexpectedType
*/
public function testIsReadableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
{
$this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
}
/**
* @dataProvider getValidPropertyPaths
*/
public function testIsWritable($objectOrArray, $path)
{
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingProperty
*/
public function testIsWritableReturnsFalseIfPropertyNotFound($objectOrArray, $path)
{
$this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingIndex
*/
public function testIsWritableReturnsTrueIfIndexNotFound($objectOrArray, $path)
{
// Non-existing indices can be written. Arrays are created on-demand.
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
}
/**
* @dataProvider getPathsWithMissingIndex
*/
public function testIsWritableReturnsTrueIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
{
$this->propertyAccessor = new PropertyAccessor(false, true);
// Non-existing indices can be written even if exceptions are enabled
$this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
}
public function testIsWritableRecognizesMagicSet()
{
$this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicGet('Bernhard'), 'magicProperty'));
}
public function testIsWritableDoesNotRecognizeMagicCallByDefault()
{
$this->assertFalse($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
public function testIsWritableRecognizesMagicCallIfEnabled()
{
$this->propertyAccessor = new PropertyAccessor(true);
$this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
}
/**
* @dataProvider getPathsWithUnexpectedType
*/
public function testIsWritableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
{
$this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path));
}
public function getValidPropertyPaths()
{
return array(
array(array('Bernhard', 'Schussek'), '[0]', 'Bernhard'),
array(array('Bernhard', 'Schussek'), '[1]', 'Schussek'),
array(array('firstName' => 'Bernhard'), '[firstName]', 'Bernhard'),
array(array('index' => array('firstName' => 'Bernhard')), '[index][firstName]', 'Bernhard'),
array((object) array('firstName' => 'Bernhard'), 'firstName', 'Bernhard'),
array((object) array('property' => array('firstName' => 'Bernhard')), 'property[firstName]', 'Bernhard'),
array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].firstName', 'Bernhard'),
array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.firstName', 'Bernhard'),
// Accessor methods
array(new TestClass('Bernhard'), 'publicProperty', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicAccessor', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicAccessorWithDefaultValue', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicAccessorWithRequiredAndDefaultValue', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicIsAccessor', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicHasAccessor', 'Bernhard'),
array(new TestClass('Bernhard'), 'publicGetSetter', 'Bernhard'),
// Methods are camelized
array(new TestClass('Bernhard'), 'public_accessor', 'Bernhard'),
array(new TestClass('Bernhard'), '_public_accessor', 'Bernhard'),
// Missing indices
array(array('index' => array()), '[index][firstName]', null),
array(array('root' => array('index' => array())), '[root][index][firstName]', null),
// Special chars
array(array('%!@$§.' => 'Bernhard'), '[%!@$§.]', 'Bernhard'),
array(array('index' => array('%!@$§.' => 'Bernhard')), '[index][%!@$§.]', 'Bernhard'),
array((object) array('%!@$§' => 'Bernhard'), '%!@$§', 'Bernhard'),
array((object) array('property' => (object) array('%!@$§' => 'Bernhard')), 'property.%!@$§', 'Bernhard'),
// nested objects and arrays
array(array('foo' => new TestClass('bar')), '[foo].publicGetSetter', 'bar'),
array(new TestClass(array('foo' => 'bar')), 'publicGetSetter[foo]', 'bar'),
array(new TestClass(new TestClass('bar')), 'publicGetter.publicGetSetter', 'bar'),
array(new TestClass(array('foo' => new TestClass('bar'))), 'publicGetter[foo].publicGetSetter', 'bar'),
array(new TestClass(new TestClass(new TestClass('bar'))), 'publicGetter.publicGetter.publicGetSetter', 'bar'),
array(new TestClass(array('foo' => array('baz' => new TestClass('bar')))), 'publicGetter[foo][baz].publicGetSetter', 'bar'),
);
}
public function testTicket5755()
{
$object = new Ticket5775Object();
$this->propertyAccessor->setValue($object, 'property', 'foobar');
$this->assertEquals('foobar', $object->getProperty());
}
public function testSetValueDeepWithMagicGetter()
{
$obj = new TestClassMagicGet('foo');
$obj->publicProperty = array('foo' => array('bar' => 'some_value'));
$this->propertyAccessor->setValue($obj, 'publicProperty[foo][bar]', 'Updated');
$this->assertSame('Updated', $obj->publicProperty['foo']['bar']);
}
public function getReferenceChainObjectsForSetValue()
{
return array(
array(array('a' => array('b' => array('c' => 'old-value'))), '[a][b][c]', 'new-value'),
array(new TestClassSetValue(new TestClassSetValue('old-value')), 'value.value', 'new-value'),
array(new TestClassSetValue(array('a' => array('b' => array('c' => new TestClassSetValue('old-value'))))), 'value[a][b][c].value', 'new-value'),
array(new TestClassSetValue(array('a' => array('b' => 'old-value'))), 'value[a][b]', 'new-value'),
array(new \ArrayIterator(array('a' => array('b' => array('c' => 'old-value')))), '[a][b][c]', 'new-value'),
);
}
/**
* @dataProvider getReferenceChainObjectsForSetValue
*/
public function testSetValueForReferenceChainIssue($object, $path, $value)
{
$this->propertyAccessor->setValue($object, $path, $value);
$this->assertEquals($value, $this->propertyAccessor->getValue($object, $path));
}
public function getReferenceChainObjectsForIsWritable()
{
return array(
array(new TestClassIsWritable(array('a' => array('b' => 'old-value'))), 'value[a][b]', false),
array(new TestClassIsWritable(new \ArrayIterator(array('a' => array('b' => 'old-value')))), 'value[a][b]', true),
array(new TestClassIsWritable(array('a' => array('b' => array('c' => new TestClassSetValue('old-value'))))), 'value[a][b][c].value', true),
);
}
/**
* @dataProvider getReferenceChainObjectsForIsWritable
*/
public function testIsWritableForReferenceChainIssue($object, $path, $value)
{
$this->assertEquals($value, $this->propertyAccessor->isWritable($object, $path));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
* @expectedExceptionMessage Expected argument of type "DateTime", "string" given
*/
public function testThrowTypeError()
{
$object = new TypeHinted();
$this->propertyAccessor->setValue($object, 'date', 'This is a string, \DateTime expected.');
}
public function testSetTypeHint()
{
$date = new \DateTime();
$object = new TypeHinted();
$this->propertyAccessor->setValue($object, 'date', $date);
$this->assertSame($date, $object->getDate());
}
public function testArrayNotBeeingOverwritten()
{
$value = array('value1' => 'foo', 'value2' => 'bar');
$object = new TestClass($value);
$this->propertyAccessor->setValue($object, 'publicAccessor[value2]', 'baz');
$this->assertSame('baz', $this->propertyAccessor->getValue($object, 'publicAccessor[value2]'));
$this->assertSame(array('value1' => 'foo', 'value2' => 'baz'), $object->getPublicAccessor());
}
public function testCacheReadAccess()
{
$obj = new TestClass('foo');
$propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
$this->assertEquals('foo', $propertyAccessor->getValue($obj, 'publicGetSetter'));
$propertyAccessor->setValue($obj, 'publicGetSetter', 'bar');
$propertyAccessor->setValue($obj, 'publicGetSetter', 'baz');
$this->assertEquals('baz', $propertyAccessor->getValue($obj, 'publicGetSetter'));
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
* @expectedExceptionMessage Expected argument of type "Countable", "string" given
*/
public function testThrowTypeErrorWithInterface()
{
$object = new TypeHinted();
$this->propertyAccessor->setValue($object, 'countable', 'This is a string, \Countable expected.');
}
/**
* @requires PHP 7.0
*/
public function testAnonymousClassRead()
{
$value = 'bar';
$obj = $this->generateAnonymousClass($value);
$propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
}
/**
* @requires PHP 7.0
*/
public function testAnonymousClassWrite()
{
$value = 'bar';
$obj = $this->generateAnonymousClass('');
$propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
$propertyAccessor->setValue($obj, 'foo', $value);
$this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
}
private function generateAnonymousClass($value)
{
$obj = eval('return new class($value)
{
private $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
/**
* @return mixed
*/
public function getFoo()
{
return $this->foo;
}
/**
* @param mixed $foo
*/
public function setFoo($foo)
{
$this->foo = $foo;
}
};');
return $obj;
}
/**
* @requires PHP 7.0
* @expectedException \TypeError
*/
public function testThrowTypeErrorInsideSetterCall()
{
$object = new TestClassTypeErrorInsideCall();
$this->propertyAccessor->setValue($object, 'property', 'foo');
}
/**
* @requires PHP 7
*
* @expectedException \TypeError
*/
public function testDoNotDiscardReturnTypeError()
{
$object = new ReturnTyped();
$this->propertyAccessor->setValue($object, 'foos', array(new \DateTime()));
}
}

View File

@@ -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\TraversableArrayObject;
class PropertyAccessorTraversableArrayObjectTest extends PropertyAccessorCollectionTest
{
protected function getContainer(array $array)
{
return new TraversableArrayObject($array);
}
}

View File

@@ -0,0 +1,291 @@
<?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 PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyPathBuilder;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class PropertyPathBuilderTest extends TestCase
{
/**
* @var string
*/
const PREFIX = 'old1[old2].old3[old4][old5].old6';
/**
* @var PropertyPathBuilder
*/
private $builder;
protected function setUp()
{
$this->builder = new PropertyPathBuilder(new PropertyPath(self::PREFIX));
}
public function testCreateEmpty()
{
$builder = new PropertyPathBuilder();
$this->assertNull($builder->getPropertyPath());
}
public function testCreateCopyPath()
{
$this->assertEquals(new PropertyPath(self::PREFIX), $this->builder->getPropertyPath());
}
public function testAppendIndex()
{
$this->builder->appendIndex('new1');
$path = new PropertyPath(self::PREFIX.'[new1]');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testAppendProperty()
{
$this->builder->appendProperty('new1');
$path = new PropertyPath(self::PREFIX.'.new1');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testAppend()
{
$this->builder->append(new PropertyPath('new1[new2]'));
$path = new PropertyPath(self::PREFIX.'.new1[new2]');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testAppendUsingString()
{
$this->builder->append('new1[new2]');
$path = new PropertyPath(self::PREFIX.'.new1[new2]');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testAppendWithOffset()
{
$this->builder->append(new PropertyPath('new1[new2].new3'), 1);
$path = new PropertyPath(self::PREFIX.'[new2].new3');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testAppendWithOffsetAndLength()
{
$this->builder->append(new PropertyPath('new1[new2].new3'), 1, 1);
$path = new PropertyPath(self::PREFIX.'[new2]');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testReplaceByIndex()
{
$this->builder->replaceByIndex(1, 'new1');
$path = new PropertyPath('old1[new1].old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testReplaceByIndexWithoutName()
{
$this->builder->replaceByIndex(0);
$path = new PropertyPath('[old1][old2].old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByIndexDoesNotAllowInvalidOffsets()
{
$this->builder->replaceByIndex(6, 'new1');
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByIndexDoesNotAllowNegativeOffsets()
{
$this->builder->replaceByIndex(-1, 'new1');
}
public function testReplaceByProperty()
{
$this->builder->replaceByProperty(1, 'new1');
$path = new PropertyPath('old1.new1.old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testReplaceByPropertyWithoutName()
{
$this->builder->replaceByProperty(1);
$path = new PropertyPath('old1.old2.old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByPropertyDoesNotAllowInvalidOffsets()
{
$this->builder->replaceByProperty(6, 'new1');
}
/**
* @expectedException \OutOfBoundsException
*/
public function testReplaceByPropertyDoesNotAllowNegativeOffsets()
{
$this->builder->replaceByProperty(-1, 'new1');
}
public function testReplace()
{
$this->builder->replace(1, 1, new PropertyPath('new1[new2].new3'));
$path = new PropertyPath('old1.new1[new2].new3.old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testReplaceUsingString()
{
$this->builder->replace(1, 1, 'new1[new2].new3');
$path = new PropertyPath('old1.new1[new2].new3.old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testReplaceNegative()
{
$this->builder->replace(-1, 1, new PropertyPath('new1[new2].new3'));
$path = new PropertyPath('old1[old2].old3[old4][old5].new1[new2].new3');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
/**
* @dataProvider provideInvalidOffsets
* @expectedException \OutOfBoundsException
*/
public function testReplaceDoesNotAllowInvalidOffsets($offset)
{
$this->builder->replace($offset, 1, new PropertyPath('new1[new2].new3'));
}
public function provideInvalidOffsets()
{
return array(
array(6),
array(-7),
);
}
public function testReplaceWithLengthGreaterOne()
{
$this->builder->replace(0, 2, new PropertyPath('new1[new2].new3'));
$path = new PropertyPath('new1[new2].new3.old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testReplaceSubstring()
{
$this->builder->replace(1, 1, new PropertyPath('new1[new2].new3.new4[new5]'), 1, 3);
$path = new PropertyPath('old1[new2].new3.new4.old3[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
public function testReplaceSubstringWithLengthGreaterOne()
{
$this->builder->replace(1, 2, new PropertyPath('new1[new2].new3.new4[new5]'), 1, 3);
$path = new PropertyPath('old1[new2].new3.new4[old4][old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
// https://github.com/symfony/symfony/issues/5605
public function testReplaceWithLongerPath()
{
// error occurs when path contains at least two more elements
// than the builder
$path = new PropertyPath('new1.new2.new3');
$builder = new PropertyPathBuilder(new PropertyPath('old1'));
$builder->replace(0, 1, $path);
$this->assertEquals($path, $builder->getPropertyPath());
}
public function testReplaceWithLongerPathKeepsOrder()
{
$path = new PropertyPath('new1.new2.new3');
$expected = new PropertyPath('new1.new2.new3.old2');
$builder = new PropertyPathBuilder(new PropertyPath('old1.old2'));
$builder->replace(0, 1, $path);
$this->assertEquals($expected, $builder->getPropertyPath());
}
public function testRemove()
{
$this->builder->remove(3);
$path = new PropertyPath('old1[old2].old3[old5].old6');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
/**
* @expectedException \OutOfBoundsException
*/
public function testRemoveDoesNotAllowInvalidOffsets()
{
$this->builder->remove(6);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testRemoveDoesNotAllowNegativeOffsets()
{
$this->builder->remove(-1);
}
}

View File

@@ -0,0 +1,206 @@
<?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 PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyAccess\PropertyPath;
class PropertyPathTest extends TestCase
{
public function testToString()
{
$path = new PropertyPath('reference.traversable[index].property');
$this->assertEquals('reference.traversable[index].property', $path->__toString());
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testDotIsRequiredBeforeProperty()
{
new PropertyPath('[index]property');
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testDotCannotBePresentAtTheBeginning()
{
new PropertyPath('.property');
}
public function providePathsContainingUnexpectedCharacters()
{
return array(
array('property.'),
array('property.['),
array('property..'),
array('property['),
array('property[['),
array('property[.'),
array('property[]'),
);
}
/**
* @dataProvider providePathsContainingUnexpectedCharacters
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testUnexpectedCharacters($path)
{
new PropertyPath($path);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
*/
public function testPathCannotBeEmpty()
{
new PropertyPath('');
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
*/
public function testPathCannotBeNull()
{
new PropertyPath(null);
}
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
*/
public function testPathCannotBeFalse()
{
new PropertyPath(false);
}
public function testZeroIsValidPropertyPath()
{
$propertyPath = new PropertyPath('0');
$this->assertSame('0', (string) $propertyPath);
}
public function testGetParentWithDot()
{
$propertyPath = new PropertyPath('grandpa.parent.child');
$this->assertEquals(new PropertyPath('grandpa.parent'), $propertyPath->getParent());
}
public function testGetParentWithIndex()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$this->assertEquals(new PropertyPath('grandpa.parent'), $propertyPath->getParent());
}
public function testGetParentWhenThereIsNoParent()
{
$propertyPath = new PropertyPath('path');
$this->assertNull($propertyPath->getParent());
}
public function testCopyConstructor()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$copy = new PropertyPath($propertyPath);
$this->assertEquals($propertyPath, $copy);
}
public function testGetElement()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$this->assertEquals('child', $propertyPath->getElement(2));
}
/**
* @expectedException \OutOfBoundsException
*/
public function testGetElementDoesNotAcceptInvalidIndices()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->getElement(3);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testGetElementDoesNotAcceptNegativeIndices()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->getElement(-1);
}
public function testIsProperty()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$this->assertTrue($propertyPath->isProperty(1));
$this->assertFalse($propertyPath->isProperty(2));
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsPropertyDoesNotAcceptInvalidIndices()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isProperty(3);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsPropertyDoesNotAcceptNegativeIndices()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isProperty(-1);
}
public function testIsIndex()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$this->assertFalse($propertyPath->isIndex(1));
$this->assertTrue($propertyPath->isIndex(2));
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsIndexDoesNotAcceptInvalidIndices()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isIndex(3);
}
/**
* @expectedException \OutOfBoundsException
*/
public function testIsIndexDoesNotAcceptNegativeIndices()
{
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isIndex(-1);
}
}

View File

@@ -0,0 +1,45 @@
<?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 PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyAccess\StringUtil;
/**
* @group legacy
*/
class StringUtilTest extends TestCase
{
public function singularifyProvider()
{
// This is only a stub to make sure the BC layer works
// Actual tests are in the Symfony Inflector component
return array(
array('axes', array('ax', 'axe', 'axis')),
);
}
/**
* @dataProvider singularifyProvider
*/
public function testSingularify($plural, $singular)
{
$single = StringUtil::singularify($plural);
if (is_string($singular) && is_array($single)) {
$this->fail("--- Expected\n`string`: ".$singular."\n+++ Actual\n`array`: ".implode(', ', $single));
} elseif (is_array($singular) && is_string($single)) {
$this->fail("--- Expected\n`array`: ".implode(', ', $singular)."\n+++ Actual\n`string`: ".$single);
}
$this->assertEquals($singular, $single);
}
}