v1.0 Initial commit of project

This commit is contained in:
2026-01-01 10:54:18 +01:00
commit 768cf78b57
990 changed files with 241213 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
<?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 ExtendedUninitializedProperty extends UninitializedProperty
{
}

View File

@@ -0,0 +1,81 @@
<?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 ?: [];
}
public function offsetExists($offset): bool
{
return \array_key_exists($offset, $this->array);
}
/**
* @param mixed $offset
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->array[$offset];
}
public function offsetSet($offset, $value): void
{
if (null === $offset) {
$this->array[] = $value;
} else {
$this->array[$offset] = $value;
}
}
public function offsetUnset($offset): void
{
unset($this->array[$offset]);
}
public function count(): int
{
return \count($this->array);
}
public function __serialize(): array
{
return $this->array;
}
public function serialize(): string
{
return serialize($this->__serialize());
}
public function __unserialize(array $data): void
{
$this->array = $data;
}
public function unserialize($serialized): void
{
$this->__unserialize((array) unserialize((string) $serialized));
}
}

View File

@@ -0,0 +1,36 @@
<?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)
{
}
public function setName($name): self
{
return 'This does not respect the return type on purpose.';
}
}

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 TestAdderRemoverInvalidArgumentLength
{
public function addFoo()
{
}
public function removeFoo($var1, $var2)
{
}
public function setBar($var1, $var2)
{
}
}

View File

@@ -0,0 +1,23 @@
<?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 TestAdderRemoverInvalidMethods
{
public function addFoo($foo)
{
}
public function removeBar($foo)
{
}
}

View File

@@ -0,0 +1,204 @@
<?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 $publicCanAccessor;
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->publicCanAccessor = $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 isPublicAccessor($param)
{
throw new \LogicException('This method should never have been called.');
}
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 setPublicCanAccessor($value)
{
$this->publicCanAccessor = $value;
}
public function canPublicCanAccessor()
{
return $this->publicCanAccessor;
}
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,39 @@
<?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(string $method, array $args)
{
if ('getMagicCallProperty' === $method) {
return $this->magicCallProperty;
}
if ('getConstantMagicCallProperty' === $method) {
return 'constant value';
}
if ('setMagicCallProperty' === $method) {
$this->magicCallProperty = reset($args);
}
return null;
}
}

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(string $property, $value)
{
if ('magicProperty' === $property) {
$this->magicProperty = $value;
}
}
public function __get(string $property)
{
if ('magicProperty' === $property) {
return $this->magicProperty;
}
if ('constantMagicProperty' === $property) {
return 'constant value';
}
}
}

View File

@@ -0,0 +1,36 @@
<?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;
}
private function setFoo()
{
}
}

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,17 @@
<?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 TestClassTypedProperty
{
public float $publicProperty;
}

View File

@@ -0,0 +1,21 @@
<?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;
#[\AllowDynamicProperties]
class TestPublicPropertyDynamicallyCreated
{
public function __construct(string $bar)
{
$this->foo = $bar;
}
}

View File

@@ -0,0 +1,18 @@
<?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 TestPublicPropertyGetterOnObject
{
public $a = 'A';
private $b = 'B';
}

View File

@@ -0,0 +1,25 @@
<?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 TestPublicPropertyGetterOnObjectMagicGet
{
public $a = 'A';
private $b = 'B';
public function __get($property)
{
if ('b' === $property) {
return $this->b;
}
}
}

View File

@@ -0,0 +1,50 @@
<?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;
/**
* Notice we don't have getter/setter for emails
* because we count on adder/remover.
*/
class TestSingularAndPluralProps
{
/** @var string|null */
private $email;
/** @var array */
private $emails = [];
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email)
{
$this->email = $email;
}
public function getEmails(): array
{
return $this->emails;
}
public function addEmail(string $email)
{
$this->emails[] = $email;
}
public function removeEmail(string $email)
{
$this->emails = array_diff($this->emails, [$email]);
}
}

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(string $property, $value)
{
$this->$property = $value;
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyAccess\Tests\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 ?: [];
}
public function offsetExists($offset): bool
{
return \array_key_exists($offset, $this->array);
}
/**
* @param mixed $offset
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->array[$offset];
}
public function offsetSet($offset, $value): void
{
if (null === $offset) {
$this->array[] = $value;
} else {
$this->array[$offset] = $value;
}
}
public function offsetUnset($offset): void
{
unset($this->array[$offset]);
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->array);
}
public function count(): int
{
return \count($this->array);
}
public function __serialize(): array
{
return $this->array;
}
public function serialize(): string
{
return serialize($this->__serialize());
}
public function __unserialize(array $data): void
{
$this->array = $data;
}
public function unserialize($serialized)
{
$this->__unserialize((array) unserialize((string) $serialized));
}
}

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\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;
}
public function getCountable(): \Countable
{
return $this->countable;
}
public function setCountable(\Countable $countable)
{
$this->countable = $countable;
}
}

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\Fixtures;
class UninitializedPrivateProperty
{
private $uninitialized;
public function getUninitialized(): array
{
return $this->uninitialized;
}
}

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 UninitializedProperty
{
public string $uninitialized;
private string $privateUninitialized;
public function getPrivateUninitialized(): string
{
return $this->privateUninitialized;
}
public function setPrivateUninitialized(string $privateUninitialized): void
{
$this->privateUninitialized = $privateUninitialized;
}
}

View File

@@ -0,0 +1,102 @@
<?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\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
abstract class PropertyAccessorArrayAccessTestCase extends TestCase
{
/**
* @var PropertyAccessor
*/
protected $propertyAccessor;
protected function setUp(): void
{
$this->propertyAccessor = new PropertyAccessor();
}
abstract protected static function getContainer(array $array);
public static function getValidPropertyPaths(): array
{
return [
[static::getContainer(['firstName' => 'Bernhard']), '[firstName]', 'Bernhard'],
[static::getContainer(['person' => static::getContainer(['firstName' => 'Bernhard'])]), '[person][firstName]', 'Bernhard'],
];
}
public static function getInvalidPropertyPaths(): array
{
return [
[static::getContainer(['firstName' => 'Bernhard']), 'firstName', 'Bernhard'],
[static::getContainer(['person' => static::getContainer(['firstName' => 'Bernhard'])]), 'person.firstName', 'Bernhard'],
];
}
/**
* @dataProvider getValidPropertyPaths
*/
public function testGetValue($collection, $path, $value)
{
$this->assertSame($value, $this->propertyAccessor->getValue($collection, $path));
}
public function testGetValueFailsIfNoSuchIndex()
{
$this->expectException(NoSuchIndexException::class);
$this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
->enableExceptionOnInvalidIndex()
->getPropertyAccessor();
$object = static::getContainer(['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));
}
/**
* @dataProvider getInvalidPropertyPaths
*/
public function testIsNotWritable($collection, $path)
{
$this->assertFalse($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 PropertyAccessorCollectionTestCase
{
protected static 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 PropertyAccessorCollectionTestCase
{
protected static function getContainer(array $array)
{
return $array;
}
}

View File

@@ -0,0 +1,128 @@
<?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;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
class PropertyAccessorBuilderTest extends TestCase
{
/**
* @var PropertyAccessorBuilder
*/
protected $builder;
protected function setUp(): void
{
$this->builder = new PropertyAccessorBuilder();
}
protected function tearDown(): void
{
$this->builder = null;
}
public function testEnableMagicGet()
{
$this->assertSame($this->builder, $this->builder->enableMagicGet());
$this->assertTrue($this->builder->isMagicGetEnabled());
}
public function testDisableMagicGet()
{
$this->assertSame($this->builder, $this->builder->disableMagicGet());
$this->assertFalse($this->builder->disableMagicGet()->isMagicGetEnabled());
}
public function testEnableMagicSet()
{
$this->assertSame($this->builder, $this->builder->enableMagicSet());
$this->assertTrue($this->builder->isMagicSetEnabled());
}
public function testDisableMagicSet()
{
$this->assertSame($this->builder, $this->builder->disableMagicSet());
$this->assertFalse($this->builder->disableMagicSet()->isMagicSetEnabled());
}
public function testEnableMagicCall()
{
$this->assertSame($this->builder, $this->builder->enableMagicCall());
$this->assertTrue($this->builder->isMagicCallEnabled());
}
public function testDisableMagicCall()
{
$this->assertSame($this->builder, $this->builder->disableMagicCall());
$this->assertFalse($this->builder->isMagicCallEnabled());
}
public function testTogglingMagicGet()
{
$this->assertTrue($this->builder->isMagicGetEnabled());
$this->assertFalse($this->builder->disableMagicGet()->isMagicGetEnabled());
$this->assertTrue($this->builder->enableMagicGet()->isMagicGetEnabled());
}
public function testTogglingMagicSet()
{
$this->assertTrue($this->builder->isMagicSetEnabled());
$this->assertFalse($this->builder->disableMagicSet()->isMagicSetEnabled());
$this->assertTrue($this->builder->enableMagicSet()->isMagicSetEnabled());
}
public function testTogglingMagicCall()
{
$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());
}
public function testUseReadInfoExtractor()
{
$readInfoExtractor = $this->createMock(PropertyReadInfoExtractorInterface::class);
$this->builder->setReadInfoExtractor($readInfoExtractor);
$this->assertSame($readInfoExtractor, $this->builder->getReadInfoExtractor());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
}
public function testUseWriteInfoExtractor()
{
$writeInfoExtractor = $this->createMock(PropertyWriteInfoExtractorInterface::class);
$this->builder->setWriteInfoExtractor($writeInfoExtractor);
$this->assertSame($writeInfoExtractor, $this->builder->getWriteInfoExtractor());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
}
}

View File

@@ -0,0 +1,204 @@
<?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\Exception\NoSuchPropertyException;
class PropertyAccessorCollectionTestCase_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 PropertyAccessorCollectionTestCase_CarOnlyAdder
{
public function addAxis($axis)
{
}
public function getAxes()
{
}
}
class PropertyAccessorCollectionTestCase_CarOnlyRemover
{
public function removeAxis($axis)
{
}
public function getAxes()
{
}
}
class PropertyAccessorCollectionTestCase_CarNoAdderAndRemover
{
public function getAxes()
{
}
}
class PropertyAccessorCollectionTestCase_CompositeCar
{
public function getStructure()
{
}
public function setStructure($structure)
{
}
}
class PropertyAccessorCollectionTestCase_CarStructure
{
public function addAxis($axis)
{
}
public function removeAxis($axis)
{
}
public function getAxes()
{
}
}
abstract class PropertyAccessorCollectionTestCase extends PropertyAccessorArrayAccessTestCase
{
public function testSetValueCallsAdderAndRemoverForCollections()
{
$axesBefore = $this->getContainer([1 => 'second', 3 => 'fourth', 4 => 'fifth']);
$axesMerged = $this->getContainer([1 => 'first', 2 => 'second', 3 => 'third']);
$axesAfter = $this->getContainer([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 PropertyAccessorCollectionTestCase_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->createMock(__CLASS__.'_CompositeCar');
$structure = $this->createMock(__CLASS__.'_CarStructure');
$axesBefore = $this->getContainer([1 => 'second', 3 => 'fourth']);
$axesAfter = $this->getContainer([0 => 'first', 1 => 'second', 2 => 'third']);
$car->expects($this->any())
->method('getStructure')
->willReturn($structure);
$structure->expects($this->once())
->method('getAxes')
->willReturn($axesBefore);
$structure->expects($this->once())
->method('removeAxis')
->with('fourth');
$structure->expects($this->exactly(2))
->method('addAxis')
->willReturnCallback(function (string $axis) {
static $series = [
'first',
'third',
];
$this->assertSame(array_shift($series), $axis);
})
;
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
}
public function testSetValueFailsIfNoAdderNorRemoverFound()
{
$this->expectException(NoSuchPropertyException::class);
$this->expectExceptionMessageMatches('/Could not determine access type for property "axes" in class "Mock_PropertyAccessorCollectionTestCase_CarNoAdderAndRemover_[^"]*"./');
$car = $this->createMock(__CLASS__.'_CarNoAdderAndRemover');
$axesBefore = $this->getContainer([1 => 'second', 3 => 'fourth']);
$axesAfter = $this->getContainer([0 => 'first', 1 => 'second', 2 => 'third']);
$car->expects($this->any())
->method('getAxes')
->willReturn($axesBefore);
$this->propertyAccessor->setValue($car, 'axes', $axesAfter);
}
public function testIsWritableReturnsTrueIfAdderAndRemoverExists()
{
$car = new PropertyAccessorCollectionTestCase_Car();
$this->assertTrue($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfOnlyAdderExists()
{
$car = new PropertyAccessorCollectionTestCase_CarOnlyAdder();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfOnlyRemoverExists()
{
$car = new PropertyAccessorCollectionTestCase_CarOnlyRemover();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists()
{
$car = new PropertyAccessorCollectionTestCase_CarNoAdderAndRemover();
$this->assertFalse($this->propertyAccessor->isWritable($car, 'axes'));
}
public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable()
{
$this->expectException(NoSuchPropertyException::class);
$this->expectExceptionMessageMatches('/The property "axes" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\PropertyAccessorCollectionTestCase_Car" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\\Traversable\./');
$car = new PropertyAccessorCollectionTestCase_Car();
$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 PropertyAccessorArrayAccessTestCase
{
protected static function getContainer(array $array)
{
return new NonTraversableArrayObject($array);
}
}

File diff suppressed because it is too large Load Diff

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 PropertyAccessorCollectionTestCase
{
protected static function getContainer(array $array)
{
return new TraversableArrayObject($array);
}
}

View File

@@ -0,0 +1,297 @@
<?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
{
private const PREFIX = 'old1[old2].old3[old4][old5].old6';
/**
* @var PropertyPathBuilder
*/
private $builder;
protected function setUp(): void
{
$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());
}
public function testReplaceByIndexDoesNotAllowInvalidOffsets()
{
$this->expectException(\OutOfBoundsException::class);
$this->builder->replaceByIndex(6, 'new1');
}
public function testReplaceByIndexDoesNotAllowNegativeOffsets()
{
$this->expectException(\OutOfBoundsException::class);
$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());
}
public function testReplaceByPropertyDoesNotAllowInvalidOffsets()
{
$this->expectException(\OutOfBoundsException::class);
$this->builder->replaceByProperty(6, 'new1');
}
public function testReplaceByPropertyDoesNotAllowNegativeOffsets()
{
$this->expectException(\OutOfBoundsException::class);
$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
*/
public function testReplaceDoesNotAllowInvalidOffsets($offset)
{
$this->expectException(\OutOfBoundsException::class);
$this->builder->replace($offset, 1, new PropertyPath('new1[new2].new3'));
}
public static function provideInvalidOffsets()
{
return [
[6],
[-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());
}
public function testRemoveDoesNotAllowInvalidOffsets()
{
$this->expectException(\OutOfBoundsException::class);
$this->builder->remove(6);
}
public function testRemoveDoesNotAllowNegativeOffsets()
{
$this->expectException(\OutOfBoundsException::class);
$this->builder->remove(-1);
}
public function testRemoveAndAppendAtTheEnd()
{
$this->builder->remove($this->builder->getLength() - 1);
$path = new PropertyPath('old1[old2].old3[old4][old5]');
$this->assertEquals($path, $this->builder->getPropertyPath());
$this->builder->appendProperty('old7');
$path = new PropertyPath('old1[old2].old3[old4][old5].old7');
$this->assertEquals($path, $this->builder->getPropertyPath());
$this->builder->remove($this->builder->getLength() - 1);
$path = new PropertyPath('old1[old2].old3[old4][old5]');
$this->assertEquals($path, $this->builder->getPropertyPath());
}
}

View File

@@ -0,0 +1,186 @@
<?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\Exception\InvalidArgumentException;
use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException;
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());
}
public function testDotIsRequiredBeforeProperty()
{
$this->expectException(InvalidPropertyPathException::class);
new PropertyPath('[index]property');
}
public function testDotCannotBePresentAtTheBeginning()
{
$this->expectException(InvalidPropertyPathException::class);
new PropertyPath('.property');
}
public static function providePathsContainingUnexpectedCharacters()
{
return [
['property.'],
['property.['],
['property..'],
['property['],
['property[['],
['property[.'],
['property[]'],
];
}
/**
* @dataProvider providePathsContainingUnexpectedCharacters
*/
public function testUnexpectedCharacters($path)
{
$this->expectException(InvalidPropertyPathException::class);
new PropertyPath($path);
}
public function testPathCannotBeEmpty()
{
$this->expectException(InvalidPropertyPathException::class);
new PropertyPath('');
}
public function testPathCannotBeNull()
{
$this->expectException(InvalidArgumentException::class);
new PropertyPath(null);
}
public function testPathCannotBeFalse()
{
$this->expectException(InvalidArgumentException::class);
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));
}
public function testGetElementDoesNotAcceptInvalidIndices()
{
$this->expectException(\OutOfBoundsException::class);
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->getElement(3);
}
public function testGetElementDoesNotAcceptNegativeIndices()
{
$this->expectException(\OutOfBoundsException::class);
$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));
}
public function testIsPropertyDoesNotAcceptInvalidIndices()
{
$this->expectException(\OutOfBoundsException::class);
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isProperty(3);
}
public function testIsPropertyDoesNotAcceptNegativeIndices()
{
$this->expectException(\OutOfBoundsException::class);
$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));
}
public function testIsIndexDoesNotAcceptInvalidIndices()
{
$this->expectException(\OutOfBoundsException::class);
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isIndex(3);
}
public function testIsIndexDoesNotAcceptNegativeIndices()
{
$this->expectException(\OutOfBoundsException::class);
$propertyPath = new PropertyPath('grandpa.parent[child]');
$propertyPath->isIndex(-1);
}
}

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;
class TestPluralAdderRemoverAndSetter
{
private $emails = [];
public function getEmails()
{
return $this->emails;
}
public function setEmails(array $emails)
{
$this->emails = ['foo@email.com'];
}
public function addEmail($email)
{
$this->emails[] = $email;
}
public function removeEmail($email)
{
$this->emails = array_diff($this->emails, [$email]);
}
}

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;
class TestPluralAdderRemoverAndSetterSameSingularAndPlural
{
private $aircraft = [];
public function getAircraft()
{
return $this->aircraft;
}
public function setAircraft(array $aircraft)
{
$this->aircraft = ['plane'];
}
public function addAircraft($aircraft)
{
$this->aircraft[] = $aircraft;
}
public function removeAircraft($aircraft)
{
$this->aircraft = array_diff($this->aircraft, [$aircraft]);
}
}