* @author Nicolas Grekas
+ * @author Luis Ramón López
*/
class PropertyAccessor implements PropertyAccessorInterface
{
@@ -141,10 +144,16 @@ class PropertyAccessor implements PropertyAccessorInterface
* @var array
*/
private $writePropertyCache = array();
+
private static $previousErrorHandler = false;
private static $errorHandler = array(__CLASS__, 'handleError');
private static $resultProto = array(self::VALUE => null);
+ /**
+ * @var ClassMetadataFactoryInterface
+ */
+ private $classMetadataFactory;
+
/**
* @var array
*/
@@ -154,15 +163,17 @@ class PropertyAccessor implements PropertyAccessorInterface
* Should not be used by application code. Use
* {@link PropertyAccess::createPropertyAccessor()} instead.
*
- * @param bool $magicCall
- * @param bool $throwExceptionOnInvalidIndex
- * @param CacheItemPoolInterface $cacheItemPool
+ * @param bool $magicCall
+ * @param bool $throwExceptionOnInvalidIndex
+ * @param CacheItemPoolInterface $cacheItemPool
+ * @param ClassMetadataFactoryInterface $classMetadataFactory
*/
- public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null)
+ public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null, MetadataFactoryInterface $classMetadataFactory = null)
{
$this->magicCall = $magicCall;
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
$this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
+ $this->classMetadataFactory = $classMetadataFactory;
}
/**
@@ -544,17 +555,29 @@ private function getReadAccessInfo($class, $property)
}
}
+ /** @var $metadata */
+ $metadata = null;
$access = array();
$reflClass = new \ReflectionClass($class);
- $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
+ $hasProperty = $reflClass->hasProperty($property);
+ $access[self::ACCESS_HAS_PROPERTY] = $hasProperty;
+
+ if ($this->classMetadataFactory) {
+ $metadata = $this->classMetadataFactory->getMetadataFor($class)->getPropertyMetadataCollection();
+ $metadata = isset($metadata[$property]) ? $metadata[$property] : null;
+ }
+
$camelProp = $this->camelize($property);
$getter = 'get'.$camelProp;
$getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
$isser = 'is'.$camelProp;
$hasser = 'has'.$camelProp;
- if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) {
+ if ($metadata && $metadata->getGetter()) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
+ $access[self::ACCESS_NAME] = $metadata->getGetter();
+ } elseif ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
$access[self::ACCESS_NAME] = $getter;
} elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) {
@@ -721,67 +744,93 @@ private function getWriteAccessInfo($class, $property, $value)
}
}
+ $metadata = null;
$access = array();
$reflClass = new \ReflectionClass($class);
- $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
- $camelized = $this->camelize($property);
- $singulars = (array) Inflector::singularize($camelized);
+ $hasProperty = $reflClass->hasProperty($property);
+ $access[self::ACCESS_HAS_PROPERTY] = $hasProperty;
+
+ $traversable = is_array($value) || $value instanceof \Traversable;
+ $done = false;
+
+ if ($this->classMetadataFactory) {
+ $metadata = $this->classMetadataFactory->getMetadataFor($class)->getPropertyMetadataCollection();
+ $metadata = isset($metadata[$property]) ? $metadata[$property] : null;
+
+ if ($metadata) {
+ if ($traversable && $metadata->getAdder() && $metadata->getRemover()) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
+ $access[self::ACCESS_ADDER] = $metadata->getAdder();
+ $access[self::ACCESS_REMOVER] = $metadata->getRemover();
+ $done = true;
+ } elseif ($metadata->getSetter()) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
+ $access[self::ACCESS_NAME] = $metadata->getSetter();
+ $done = true;
+ }
+ }
+ }
- if (is_array($value) || $value instanceof \Traversable) {
- $methods = $this->findAdderAndRemover($reflClass, $singulars);
+ if (!$done) {
+ $camelized = $this->camelize($property);
+ $singulars = (array)Inflector::singularize($camelized);
- if (null !== $methods) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
- $access[self::ACCESS_ADDER] = $methods[0];
- $access[self::ACCESS_REMOVER] = $methods[1];
+ if ($traversable) {
+ $methods = $this->findAdderAndRemover($reflClass, $singulars);
+
+ if (null !== $methods) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
+ $access[self::ACCESS_ADDER] = $methods[0];
+ $access[self::ACCESS_REMOVER] = $methods[1];
+ }
}
- }
- if (!isset($access[self::ACCESS_TYPE])) {
- $setter = 'set'.$camelized;
- $getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
-
- if ($this->isMethodAccessible($reflClass, $setter, 1)) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
- $access[self::ACCESS_NAME] = $setter;
- } elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
- $access[self::ACCESS_NAME] = $getsetter;
- } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
- $access[self::ACCESS_NAME] = $property;
- } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
- $access[self::ACCESS_NAME] = $property;
- } elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
- // we call the getter and hope the __call do the job
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
- $access[self::ACCESS_NAME] = $setter;
- } elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars)) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
- $access[self::ACCESS_NAME] = sprintf(
- 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
- 'the new value must be an array or an instance of \Traversable, '.
- '"%s" given.',
- $property,
- $reflClass->name,
- implode('()", "', $methods),
- is_object($value) ? get_class($value) : gettype($value)
- );
- } else {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
- $access[self::ACCESS_NAME] = sprintf(
- 'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '.
- '"__set()" or "__call()" exist and have public access in class "%s".',
- $property,
- implode('', array_map(function ($singular) {
- return '"add'.$singular.'()"/"remove'.$singular.'()", ';
- }, $singulars)),
- $setter,
- $getsetter,
- $reflClass->name
- );
+ if (!isset($access[self::ACCESS_TYPE])) {
+ $setter = 'set' . $camelized;
+ $getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
+
+ if ($this->isMethodAccessible($reflClass, $setter, 1)) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
+ $access[self::ACCESS_NAME] = $setter;
+ } elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
+ $access[self::ACCESS_NAME] = $getsetter;
+ } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
+ $access[self::ACCESS_NAME] = $property;
+ } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
+ $access[self::ACCESS_NAME] = $property;
+ } elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
+ // we call the getter and hope the __call do the job
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
+ $access[self::ACCESS_NAME] = $setter;
+ } elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars)) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
+ $access[self::ACCESS_NAME] = sprintf(
+ 'The property "%s" in class "%s" can be defined with the methods "%s()" but ' .
+ 'the new value must be an array or an instance of \Traversable, ' .
+ '"%s" given.',
+ $property,
+ $reflClass->name,
+ implode('()", "', $methods),
+ is_object($value) ? get_class($value) : gettype($value)
+ );
+ } else {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
+ $access[self::ACCESS_NAME] = sprintf(
+ 'Neither the property "%s" nor one of the methods %s"%s()", "%s()", ' .
+ '"__set()" or "__call()" exist and have public access in class "%s".',
+ $property,
+ implode('', array_map(function ($singular) {
+ return '"add' . $singular . '()"/"remove' . $singular . '()", ';
+ }, $singulars)),
+ $setter,
+ $getsetter,
+ $reflClass->name
+ );
+ }
}
}
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
index 3225cf9bc6b40..f34f34a947d6a 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
@@ -12,11 +12,13 @@
namespace Symfony\Component\PropertyAccess;
use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\PropertyAccess\Mapping\Factory\MetadataFactoryInterface;
/**
* A configurable builder to create a PropertyAccessor.
*
* @author Jérémie Augustin
+ * @author Luis Ramón López
*/
class PropertyAccessorBuilder
{
@@ -35,6 +37,11 @@ class PropertyAccessorBuilder
*/
private $cacheItemPool;
+ /**
+ * @var MetadataFactoryInterface
+ */
+ private $metadataFactoryInterface = null;
+
/**
* Enables the use of "__call" by the PropertyAccessor.
*
@@ -128,6 +135,29 @@ public function getCacheItemPool()
return $this->cacheItemPool;
}
+ /**
+ * Allows to take into account metadata in order to override getter/setter/adder and remover method
+ * calls to properties.
+ *
+ * @param MetadataFactoryInterface|null $metadataFactoryInterface
+ *
+ * @return PropertyAccessorBuilder The builder object
+ */
+ public function setMetadataFactory(MetadataFactoryInterface $metadataFactoryInterface = null)
+ {
+ $this->metadataFactoryInterface = $metadataFactoryInterface;
+
+ return $this;
+ }
+
+ /**
+ * @return MetadataFactoryInterface|null the current object that retrieves metadata or null if not used
+ */
+ public function getMetadataFactory()
+ {
+ return $this->metadataFactoryInterface;
+ }
+
/**
* Builds and returns a new PropertyAccessor object.
*
@@ -135,6 +165,6 @@ public function getCacheItemPool()
*/
public function getPropertyAccessor()
{
- return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool);
+ return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool, $this->metadataFactoryInterface);
}
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/Dummy.php
new file mode 100644
index 0000000000000..dc1ce60818ba5
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/Dummy.php
@@ -0,0 +1,71 @@
+
+ *
+ * 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;
+
+use Symfony\Component\PropertyAccess\Annotation\Property;
+use Symfony\Component\PropertyAccess\Annotation\PropertyGetter;
+
+/**
+ * Fixtures for testing metadata.
+ */
+class Dummy extends DummyParent
+{
+ /**
+ * @Property(getter="getter1", setter="setter1", adder="adder1", remover="remover1")
+ */
+ protected $foo;
+
+ /**
+ * @Property(getter="getter2")
+ */
+ protected $bar;
+
+ /**
+ * @return mixed
+ */
+ public function getter1()
+ {
+ return $this->foo;
+ }
+
+ /**
+ * @param mixed $foo
+ */
+ public function setter1($foo)
+ {
+ $this->foo = $foo;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getter2()
+ {
+ return $this->bar;
+ }
+
+ /**
+ * @param mixed $bar
+ */
+ public function setBar($bar)
+ {
+ $this->bar = $bar;
+ }
+
+ /**
+ * @PropertyGetter(property="test")
+ */
+ public function testChild()
+ {
+ return 'child';
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/DummyParent.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/DummyParent.php
new file mode 100644
index 0000000000000..2475d14c96f82
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/DummyParent.php
@@ -0,0 +1,28 @@
+
+ *
+ * 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;
+
+use Symfony\Component\PropertyAccess\Annotation\PropertyGetter;
+
+/**
+ * Fixtures for testing metadata.
+ */
+class DummyParent
+{
+ /**
+ * @PropertyGetter(property="test")
+ */
+ public function testParent()
+ {
+ return 'parent';
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php
index e63af3a8bac5d..2276e03c4ffb8 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php
@@ -11,11 +11,14 @@
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
+use Symfony\Component\PropertyAccess\Annotation\Property;
+use Symfony\Component\PropertyAccess\Annotation\PropertyGetter;
+use Symfony\Component\PropertyAccess\Annotation\PropertySetter;
+
class TestClass
{
public $publicProperty;
protected $protectedProperty;
- private $privateProperty;
private $publicAccessor;
private $publicMethodAccessor;
@@ -28,7 +31,14 @@ class TestClass
private $publicGetter;
private $date;
- public function __construct($value)
+ private $quantity;
+
+ /**
+ * @Property(getter="customGetterTest", setter="customSetterTest")
+ */
+ private $customGetterSetter;
+
+ public function __construct($value, $quantity = 2, $pricePerUnit = 10)
{
$this->publicProperty = $value;
$this->publicAccessor = $value;
@@ -40,6 +50,9 @@ public function __construct($value)
$this->publicIsAccessor = $value;
$this->publicHasAccessor = $value;
$this->publicGetter = $value;
+ $this->customGetterSetter = $value;
+ $this->quantity = $quantity;
+ $this->pricePerUnit = $pricePerUnit;
}
public function setPublicAccessor($value)
@@ -184,4 +197,40 @@ public function getDate()
{
return $this->date;
}
+
+ public function customGetterTest()
+ {
+ return $this->customGetterSetter;
+ }
+
+ public function customSetterTest($value)
+ {
+ $this->customGetterSetter = $value;
+ }
+
+ /**
+ * @return int
+ */
+ public function getQuantity()
+ {
+ return $this->quantity;
+ }
+
+ /**
+ * @PropertyGetter(property="total")
+ */
+ public function getTotal()
+ {
+ return $this->quantity * $this->pricePerUnit;
+ }
+
+ /**
+ * @PropertySetter(property="total")
+ *
+ * @param mixed $total
+ */
+ public function setTotal($total)
+ {
+ $this->quantity = $total / $this->pricePerUnit;
+ }
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/empty-mapping.yml b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/empty-mapping.yml
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/invalid-mapping.yml b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/invalid-mapping.yml
new file mode 100644
index 0000000000000..19102815663d2
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/invalid-mapping.yml
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/property-access.xml b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/property-access.xml
new file mode 100644
index 0000000000000..990b2ad9dfbc5
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/property-access.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/property-access.yml b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/property-access.yml
new file mode 100644
index 0000000000000..4c78d1bc4be62
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/property-access.yml
@@ -0,0 +1,9 @@
+'Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy':
+ properties:
+ foo:
+ getter: getter1
+ setter: setter1
+ adder: adder1
+ remover: remover1
+ bar:
+ getter: getter2
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/ClassMetadataTest.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/ClassMetadataTest.php
new file mode 100644
index 0000000000000..e54a392befa76
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/ClassMetadataTest.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping;
+
+use Symfony\Component\PropertyAccess\Mapping\ClassMetadata;
+
+/**
+ * @author Kévin Dunglas
+ */
+class ClassMetadataTest extends \PHPUnit_Framework_TestCase
+{
+ public function testInterface()
+ {
+ $classMetadata = new ClassMetadata('name');
+ $this->assertInstanceOf('Symfony\Component\PropertyAccess\Mapping\ClassMetadata', $classMetadata);
+ }
+
+ public function testAttributeMetadata()
+ {
+ $classMetadata = new ClassMetadata('c');
+
+ $a1 = $this->getMock('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata');
+ $a1->method('getName')->willReturn('a1');
+
+ $a2 = $this->getMock('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata');
+ $a2->method('getName')->willReturn('a2');
+
+ $classMetadata->addPropertyMetadata($a1);
+ $classMetadata->addPropertyMetadata($a2);
+
+ $this->assertEquals(array('a1' => $a1, 'a2' => $a2), $classMetadata->getPropertyMetadataCollection());
+ }
+
+ public function testSerialize()
+ {
+ $classMetadata = new ClassMetadata('a');
+
+ $a1 = $this->getMock('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata');
+ $a1->method('getName')->willReturn('b1');
+ $a1->method('__sleep')->willReturn([]);
+
+ $a2 = $this->getMock('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata');
+ $a2->method('getName')->willReturn('b2');
+ $a2->method('__sleep')->willReturn([]);
+
+ $classMetadata->addPropertyMetadata($a1);
+ $classMetadata->addPropertyMetadata($a2);
+
+ $serialized = serialize($classMetadata);
+ $this->assertEquals($classMetadata, unserialize($serialized));
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php
new file mode 100644
index 0000000000000..d49b37eae8496
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping\Factory;
+
+use Symfony\Component\PropertyAccess\Mapping\Factory\BlackHoleMetadataFactory;
+
+class BlackHoleMetadataFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @expectedException \LogicException
+ */
+ public function testGetMetadataForThrowsALogicException()
+ {
+ $metadataFactory = new BlackHoleMetadataFactory();
+ $metadataFactory->getMetadataFor('foo');
+ }
+
+ public function testHasMetadataForReturnsFalse()
+ {
+ $metadataFactory = new BlackHoleMetadataFactory();
+
+ $this->assertFalse($metadataFactory->hasMetadataFor('foo'));
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
new file mode 100644
index 0000000000000..7549b652c0bad
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping\Factory;
+
+use Symfony\Component\PropertyAccess\Mapping\ClassMetadata;
+use Symfony\Component\PropertyAccess\Mapping\Factory\LazyLoadingMetadataFactory;
+use Symfony\Component\PropertyAccess\Mapping\Loader\LoaderInterface;
+use Symfony\Component\PropertyAccess\Mapping\PropertyMetadata;
+
+class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ const CLASSNAME = 'Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy';
+ const PARENTCLASS = 'Symfony\Component\PropertyAccess\Tests\Fixtures\DummyParent';
+
+ public function testLoadClassMetadata()
+ {
+ $factory = new LazyLoadingMetadataFactory(new TestLoader());
+ $metadata = $factory->getMetadataFor(self::PARENTCLASS);
+
+ $properties = array(
+ self::PARENTCLASS => new PropertyMetadata(self::PARENTCLASS),
+ );
+
+ $this->assertEquals($properties, $metadata->getPropertyMetadataCollection());
+ }
+
+ public function testMergeParentMetadata()
+ {
+ $factory = new LazyLoadingMetadataFactory(new TestLoader());
+ $metadata = $factory->getMetadataFor(self::CLASSNAME);
+
+ $properties = array(
+ self::PARENTCLASS => new PropertyMetadata(self::PARENTCLASS),
+ self::CLASSNAME => new PropertyMetadata(self::CLASSNAME),
+ );
+
+ $this->assertEquals($properties, $metadata->getPropertyMetadataCollection());
+ }
+
+ public function testWriteMetadataToCache()
+ {
+ $cache = $this->getMock('Psr\Cache\CacheItemPoolInterface');
+ $factory = new LazyLoadingMetadataFactory(new TestLoader(), $cache);
+
+ $properties = array(
+ self::PARENTCLASS => new PropertyMetadata(self::PARENTCLASS),
+ );
+
+ $cacheItem = $this->getMock('Psr\Cache\CacheItemInterface');
+
+ $cache->expects($this->once())
+ ->method('getItem')
+ ->with($this->equalTo($this->escapeClassName(self::PARENTCLASS)))
+ ->will($this->returnValue($cacheItem));
+
+ $cacheItem->expects($this->once())
+ ->method('isHit')
+ ->will($this->returnValue(false));
+
+ $cacheItem->expects($this->once())
+ ->method('set')
+ ->will($this->returnCallback(function ($metadata) use ($properties) {
+ $this->assertEquals($properties, $metadata->getPropertyMetadataCollection());
+ }));
+
+ $cache->expects($this->once())
+ ->method('save')
+ ->with($this->equalTo($cacheItem))
+ ->will($this->returnValue(true));
+
+ $metadata = $factory->getMetadataFor(self::PARENTCLASS);
+
+ $this->assertEquals(self::PARENTCLASS, $metadata->getName());
+ $this->assertEquals($properties, $metadata->getPropertyMetadataCollection());
+ }
+
+ public function testReadMetadataFromCache()
+ {
+ $loader = $this->getMock('Symfony\Component\PropertyAccess\Mapping\Loader\LoaderInterface');
+ $cache = $this->getMock('Psr\Cache\CacheItemPoolInterface');
+ $factory = new LazyLoadingMetadataFactory($loader, $cache);
+
+ $metadata = new ClassMetadata(self::PARENTCLASS);
+ $metadata->addPropertyMetadata(new PropertyMetadata());
+
+ $loader->expects($this->never())
+ ->method('loadClassMetadata');
+
+ $cacheItem = $this->getMock('Psr\Cache\CacheItemInterface');
+
+ $cache->expects($this->once())
+ ->method('getItem')
+ ->with($this->equalTo($this->escapeClassName(self::PARENTCLASS)))
+ ->will($this->returnValue($cacheItem));
+
+ $cacheItem->expects($this->once())
+ ->method('isHit')
+ ->will($this->returnValue(true));
+
+ $cacheItem->expects($this->once())
+ ->method('get')
+ ->will($this->returnValue($metadata));
+
+ $cacheItem->expects($this->never())
+ ->method('set');
+
+ $cache->expects($this->never())
+ ->method('save');
+
+ $this->assertEquals($metadata, $factory->getMetadataFor(self::PARENTCLASS));
+ }
+
+ /**
+ * Replaces backslashes by dots in a class name.
+ *
+ * @param string $class
+ *
+ * @return string
+ */
+ private function escapeClassName($class)
+ {
+ return str_replace('\\', '.', $class);
+ }
+}
+
+class TestLoader implements LoaderInterface
+{
+ public function loadClassMetadata(ClassMetadata $metadata)
+ {
+ $metadata->addPropertyMetadata(new PropertyMetadata($metadata->getName()));
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/AnnotationLoaderTest.php
new file mode 100644
index 0000000000000..0cfa2c1fbaf89
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/AnnotationLoaderTest.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping\Loader;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Symfony\Component\PropertyAccess\Mapping\ClassMetadata;
+use Symfony\Component\PropertyAccess\Mapping\Loader\AnnotationLoader;
+use Symfony\Component\PropertyAccess\Tests\Mapping\TestClassMetadataFactory;
+
+/**
+ * @author Kévin Dunglas
+ * @author Luis Ramón López
+ */
+class AnnotationLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var AnnotationLoader
+ */
+ private $loader;
+
+ protected function setUp()
+ {
+ $this->loader = new AnnotationLoader(new AnnotationReader());
+ }
+
+ public function testInterface()
+ {
+ $this->assertInstanceOf('Symfony\Component\PropertyAccess\Mapping\Loader\LoaderInterface', $this->loader);
+ }
+
+ public function testLoadClassMetadataReturnsTrueIfSuccessful()
+ {
+ $classMetadata = new ClassMetadata('Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy');
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../../../..');
+ $this->assertTrue($this->loader->loadClassMetadata($classMetadata));
+ }
+
+ public function testLoadMetadata()
+ {
+ $classMetadata = new ClassMetadata('Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy');
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../../../..');
+ $this->loader->loadClassMetadata($classMetadata);
+
+ $this->assertEquals(TestClassMetadataFactory::createClassMetadata(), $classMetadata);
+ }
+
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/XmlFileLoaderTest.php
new file mode 100644
index 0000000000000..4162dec218808
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/XmlFileLoaderTest.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping\Loader;
+
+use Symfony\Component\PropertyAccess\Mapping\Loader\XmlFileLoader;
+use Symfony\Component\PropertyAccess\Mapping\ClassMetadata;
+use Symfony\Component\PropertyAccess\Tests\Mapping\TestClassMetadataFactory;
+
+/**
+ * @author Kévin Dunglas
+ * @author Luis Ramón López
+ */
+class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var XmlFileLoader
+ */
+ private $loader;
+ /**
+ * @var ClassMetadata
+ */
+ private $metadata;
+
+ protected function setUp()
+ {
+ $this->loader = new XmlFileLoader(__DIR__.'/../../Fixtures/property-access.xml');
+ $this->metadata = new ClassMetadata('Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy');
+ }
+
+ public function testInterface()
+ {
+ $this->assertInstanceOf('Symfony\Component\PropertyAccess\Mapping\Loader\LoaderInterface', $this->loader);
+ }
+
+ public function testLoadClassMetadataReturnsTrueIfSuccessful()
+ {
+ $this->assertTrue($this->loader->loadClassMetadata($this->metadata));
+ }
+
+ public function testLoadClassMetadata()
+ {
+ $this->loader->loadClassMetadata($this->metadata);
+
+ $this->assertEquals(TestClassMetadataFactory::createXmlClassMetadata(), $this->metadata);
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/YamlFileLoaderTest.php
new file mode 100644
index 0000000000000..63e299b2de76d
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/YamlFileLoaderTest.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping\Loader;
+
+use Symfony\Component\PropertyAccess\Mapping\Loader\YamlFileLoader;
+use Symfony\Component\PropertyAccess\Mapping\ClassMetadata;
+use Symfony\Component\PropertyAccess\Tests\Mapping\TestClassMetadataFactory;
+
+/**
+ * @author Kévin Dunglas
+ * @author Luis Ramón López
+ */
+class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var YamlFileLoader
+ */
+ private $loader;
+ /**
+ * @var ClassMetadata
+ */
+ private $metadata;
+
+ protected function setUp()
+ {
+ $this->loader = new YamlFileLoader(__DIR__.'/../../Fixtures/property-access.yml');
+ $this->metadata = new ClassMetadata('Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy');
+ }
+
+ public function testInterface()
+ {
+ $this->assertInstanceOf('Symfony\Component\PropertyAccess\Mapping\Loader\LoaderInterface', $this->loader);
+ }
+
+ public function testLoadClassMetadataReturnsTrueIfSuccessful()
+ {
+ $this->assertTrue($this->loader->loadClassMetadata($this->metadata));
+ }
+
+ public function testLoadClassMetadataReturnsFalseWhenEmpty()
+ {
+ $loader = new YamlFileLoader(__DIR__.'/../../Fixtures/empty-mapping.yml');
+ $this->assertFalse($loader->loadClassMetadata($this->metadata));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\PropertyAccess\Exception\MappingException
+ */
+ public function testLoadClassMetadataReturnsThrowsInvalidMapping()
+ {
+ $loader = new YamlFileLoader(__DIR__.'/../../Fixtures/invalid-mapping.yml');
+ $loader->loadClassMetadata($this->metadata);
+ }
+
+ public function testLoadClassMetadata()
+ {
+ $this->loader->loadClassMetadata($this->metadata);
+
+ $this->assertEquals(TestClassMetadataFactory::createXmlClassMetadata(), $this->metadata);
+ }
+
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/PropertyMetadataTest.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/PropertyMetadataTest.php
new file mode 100644
index 0000000000000..827d7c57169e1
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/PropertyMetadataTest.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping;
+
+use Symfony\Component\PropertyAccess\Mapping\PropertyMetadata;
+
+/**
+ * @author Kévin Dunglas
+ */
+class PropertyMetadataTest extends \PHPUnit_Framework_TestCase
+{
+ public function testInterface()
+ {
+ $propertyMetadata = new PropertyMetadata('name');
+ $this->assertInstanceOf('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata', $propertyMetadata);
+ }
+
+ public function testGetName()
+ {
+ $propertyMetadata = new PropertyMetadata('name');
+ $this->assertEquals('name', $propertyMetadata->getName());
+ }
+
+ public function testGetter()
+ {
+ $propertyMetadata = new PropertyMetadata('name');
+ $propertyMetadata->setGetter('one');
+
+ $this->assertEquals('one', $propertyMetadata->getGetter());
+ }
+
+ public function testSetter()
+ {
+ $propertyMetadata = new PropertyMetadata('name');
+ $propertyMetadata->setSetter('one');
+
+ $this->assertEquals('one', $propertyMetadata->getSetter());
+ }
+
+ public function testAdder()
+ {
+ $propertyMetadata = new PropertyMetadata('name');
+ $propertyMetadata->setAdder('one');
+
+ $this->assertEquals('one', $propertyMetadata->getAdder());
+ }
+
+ public function testRemover()
+ {
+ $propertyMetadata = new PropertyMetadata('name');
+ $propertyMetadata->setRemover('one');
+
+ $this->assertEquals('one', $propertyMetadata->getRemover());
+ }
+
+ public function testMerge()
+ {
+ $propertyMetadata1 = new PropertyMetadata('a1');
+ $propertyMetadata1->setGetter('a');
+ $propertyMetadata1->setSetter('b');
+
+ $propertyMetadata2 = new PropertyMetadata('a2');
+ $propertyMetadata2->setGetter('c');
+ $propertyMetadata2->setAdder('d');
+ $propertyMetadata2->setRemover('e');
+
+ $propertyMetadata1->merge($propertyMetadata2);
+
+ $this->assertEquals('a', $propertyMetadata1->getGetter());
+ $this->assertEquals('b', $propertyMetadata1->getSetter());
+ $this->assertEquals('d', $propertyMetadata1->getAdder());
+ $this->assertEquals('e', $propertyMetadata1->getRemover());
+ }
+
+ public function testSerialize()
+ {
+ $propertyMetadata = new PropertyMetadata('attribute');
+ $propertyMetadata->setGetter('a');
+ $propertyMetadata->setSetter('b');
+ $propertyMetadata->setAdder('c');
+ $propertyMetadata->setRemover('d');
+
+ $serialized = serialize($propertyMetadata);
+ $this->assertEquals($propertyMetadata, unserialize($serialized));
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Mapping/TestClassMetadataFactory.php b/src/Symfony/Component/PropertyAccess/Tests/Mapping/TestClassMetadataFactory.php
new file mode 100644
index 0000000000000..251259031872c
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/TestClassMetadataFactory.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests\Mapping;
+
+use Symfony\Component\PropertyAccess\Mapping\PropertyMetadata;
+use Symfony\Component\PropertyAccess\Mapping\ClassMetadata;
+
+/**
+ * @author Kévin Dunglas
+ */
+class TestClassMetadataFactory
+{
+ public static function createClassMetadata()
+ {
+ $expected = new ClassMetadata('Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy');
+
+ $expected->getReflectionClass();
+
+ $foo = new PropertyMetadata('foo');
+ $foo->setGetter('getter1');
+ $foo->setSetter('setter1');
+ $foo->setAdder('adder1');
+ $foo->setRemover('remover1');
+ $expected->addPropertyMetadata($foo);
+
+ $bar = new PropertyMetadata('bar');
+ $bar->setGetter('getter2');
+ $expected->addPropertyMetadata($bar);
+
+ $test = new PropertyMetadata('test');
+ $test->setGetter('testChild');
+ $expected->addPropertyMetadata($test);
+
+ return $expected;
+ }
+
+ public static function createXMLClassMetadata()
+ {
+ $expected = new ClassMetadata('Symfony\Component\PropertyAccess\Tests\Fixtures\Dummy');
+
+ $foo = new PropertyMetadata('foo');
+ $foo->setGetter('getter1');
+ $foo->setSetter('setter1');
+ $foo->setAdder('adder1');
+ $foo->setRemover('remover1');
+ $expected->addPropertyMetadata($foo);
+
+ $bar = new PropertyMetadata('bar');
+ $bar->setGetter('getter2');
+ $expected->addPropertyMetadata($bar);
+
+ return $expected;
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
index 2c65e6adf6ddd..7f53fe9880987 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
@@ -49,6 +49,14 @@ public function testIsMagicCallEnable()
$this->assertFalse($this->builder->disableMagicCall()->isMagicCallEnabled());
}
+ public function testMetadataFactory()
+ {
+ $metadataFactory = $this->getMock('Symfony\Component\PropertyAccess\Mapping\Factory\MetadataFactoryInterface');
+ $this->assertNull($this->builder->getMetadataFactory());
+ $this->assertSame($metadataFactory, $this->builder->setMetadataFactory($metadataFactory)->getMetadataFactory());
+ $this->assertNull($this->builder->setMetadataFactory(null)->getMetadataFactory());
+ }
+
public function testGetPropertyAccessor()
{
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php
index 17518468ebad8..54f7f6642b912 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php
@@ -11,13 +11,28 @@
namespace Symfony\Component\PropertyAccess\Tests;
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Symfony\Component\PropertyAccess\Annotation\PropertyAdder;
+use Symfony\Component\PropertyAccess\Annotation\PropertyGetter;
+use Symfony\Component\PropertyAccess\Annotation\PropertyRemover;
+use Symfony\Component\PropertyAccess\Mapping\Factory\LazyLoadingMetadataFactory;
+use Symfony\Component\PropertyAccess\Mapping\Loader\AnnotationLoader;
+use Symfony\Component\PropertyAccess\PropertyAccessor;
+
class PropertyAccessorCollectionTest_Car
{
private $axes;
+ /**
+ * @Symfony\Component\PropertyAccess\Annotation\Property(adder="addAxisTest", remover="removeAxisTest")
+ */
+ private $customAxes;
+
public function __construct($axes = null)
{
$this->axes = $axes;
+ $this->customAxes = $axes;
}
// In the test, use a name that StringUtil can't uniquely singularify
@@ -26,6 +41,16 @@ public function addAxis($axis)
$this->axes[] = $axis;
}
+ // In the test, use a name that StringUtil can't uniquely singularify
+ /**
+ * @PropertyAdder(property="customVirtualAxes")
+ * @param $axis
+ */
+ public function addAxisTest($axis)
+ {
+ $this->customAxes[] = $axis;
+ }
+
public function removeAxis($axis)
{
foreach ($this->axes as $key => $value) {
@@ -37,10 +62,34 @@ public function removeAxis($axis)
}
}
+ /**
+ * @PropertyRemover(property="customVirtualAxes")
+ * @param $axis
+ */
+ public function removeAxisTest($axis)
+ {
+ foreach ($this->customAxes as $key => $value) {
+ if ($value === $axis) {
+ unset($this->customAxes[$key]);
+
+ return;
+ }
+ }
+ }
+
public function getAxes()
{
return $this->axes;
}
+
+ /**
+ * @PropertyGetter(property="customVirtualAxes")
+ * @return null
+ */
+ public function getCustomAxes()
+ {
+ return $this->customAxes;
+ }
}
class PropertyAccessorCollectionTest_CarOnlyAdder
@@ -146,6 +195,50 @@ public function testSetValueCallsAdderAndRemoverForNestedCollections()
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
}
+ public function testSetValueCallsCustomAdderAndRemoverForCollections()
+ {
+ $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);
+
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../..');
+ $this->propertyAccessor = new PropertyAccessor(false, false, null, new LazyLoadingMetadataFactory(new AnnotationLoader(new AnnotationReader())));
+
+ $this->propertyAccessor->setValue($car, 'customAxes', $axesMerged);
+
+ $this->assertEquals($axesAfter, $car->getCustomAxes());
+
+ // The passed collection was not modified
+ $this->assertEquals($axesMergedCopy, $axesMerged);
+ }
+
+ public function testSetValueCallsCustomAdderAndRemoverForCollectionsMethodAnnotation()
+ {
+ $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);
+
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../..');
+ $this->propertyAccessor = new PropertyAccessor(false, false, null, new LazyLoadingMetadataFactory(new AnnotationLoader(new AnnotationReader())));
+
+ $this->propertyAccessor->setValue($car, 'customVirtualAxes', $axesMerged);
+
+ $this->assertEquals($axesAfter, $car->getCustomAxes());
+
+ // The passed collection was not modified
+ $this->assertEquals($axesMergedCopy, $axesMerged);
+ }
+
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
* @expectedExceptionMessage Neither the property "axes" nor one of the methods "addAx()"/"removeAx()", "addAxe()"/"removeAxe()", "addAxis()"/"removeAxis()", "setAxes()", "axes()", "__set()" or "__call()" exist and have public access in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index a3a82b0b63cba..c96402b8c81ce 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -11,8 +11,12 @@
namespace Symfony\Component\PropertyAccess\Tests;
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
+use Symfony\Component\PropertyAccess\Mapping\Factory\LazyLoadingMetadataFactory;
+use Symfony\Component\PropertyAccess\Mapping\Loader\AnnotationLoader;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
@@ -198,6 +202,20 @@ public function testGetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $p
$this->propertyAccessor->getValue($objectOrArray, $path);
}
+ public function testGetWithCustomGetter()
+ {
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../..');
+ $this->propertyAccessor = new PropertyAccessor(false, false, null, new LazyLoadingMetadataFactory(new AnnotationLoader(new AnnotationReader())));
+ $this->assertSame('webmozart', $this->propertyAccessor->getValue(new TestClass('webmozart'), 'customGetterSetter'));
+ }
+
+ public function testGetWithCustomGetterMethodAnnotation()
+ {
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../..');
+ $this->propertyAccessor = new PropertyAccessor(false, false, null, new LazyLoadingMetadataFactory(new AnnotationLoader(new AnnotationReader())));
+ $this->assertSame(200, $this->propertyAccessor->getValue(new TestClass('webmozart', 10, 20), 'total'));
+ }
+
/**
* @dataProvider getValidPropertyPaths
*/
@@ -298,6 +316,30 @@ public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $p
$this->propertyAccessor->setValue($objectOrArray, $path, 'value');
}
+ public function testSetValueWithCustomSetter()
+ {
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../..');
+ $this->propertyAccessor = new PropertyAccessor(false, false, null, new LazyLoadingMetadataFactory(new AnnotationLoader(new AnnotationReader())));
+
+ $custom = new TestClass('webmozart');
+
+ $this->propertyAccessor->setValue($custom, 'customGetterSetter', 'it works!');
+
+ $this->assertEquals('it works!', $custom->customGetterTest());
+ }
+
+ public function testSetValueWithCustomSetterMethodAnnotation()
+ {
+ AnnotationRegistry::registerAutoloadNamespace('Symfony\Component\PropertyAccess\Annotation', __DIR__.'/../../../..');
+ $this->propertyAccessor = new PropertyAccessor(false, false, null, new LazyLoadingMetadataFactory(new AnnotationLoader(new AnnotationReader())));
+
+ $custom = new TestClass('webmozart', 10, 20);
+
+ $this->propertyAccessor->setValue($custom, 'total', 5);
+
+ $this->assertEquals(5, $custom->getTotal());
+ }
+
public function testGetValueWhenArrayValueIsNull()
{
$this->propertyAccessor = new PropertyAccessor(false, true);
diff --git a/src/Symfony/Component/PropertyAccess/composer.json b/src/Symfony/Component/PropertyAccess/composer.json
index e095cbe35fe91..6b594d2c65263 100644
--- a/src/Symfony/Component/PropertyAccess/composer.json
+++ b/src/Symfony/Component/PropertyAccess/composer.json
@@ -21,7 +21,11 @@
"symfony/inflector": "~3.1"
},
"require-dev": {
- "symfony/cache": "~3.1"
+ "doctrine/cache": "~1.0",
+ "doctrine/annotations": "~1.2",
+ "symfony/cache": "~3.1",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/yaml": "~2.8|~3.0"
},
"suggest": {
"psr/cache-implementation": "To cache access methods."