* @author Nicolas Grekas
+ * @author Luis Ramón López
*/
class PropertyAccessor implements PropertyAccessorInterface
{
@@ -118,6 +121,11 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
const CACHE_PREFIX_PROPERTY_PATH = 'p';
+ /**
+ * @internal
+ */
+ const CACHE_PREFIX_METADATA = 'm';
+
/**
* @var bool
*/
@@ -129,6 +137,14 @@ class PropertyAccessor implements PropertyAccessorInterface
*/
private $cacheItemPool;
+ /**
+ * @var MetadataFactoryInterface
+ */
+ private $classMetadataFactory;
+
+ /**
+ * @var array
+ */
private $readPropertyCache = array();
private $writePropertyCache = array();
private $propertyPathCache = array();
@@ -141,15 +157,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|null
+ * @param MetadataFactoryInterface $classMetadataFactory|null
*/
- 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;
}
/**
@@ -520,18 +538,20 @@ private function getReadAccessInfo($class, $property)
}
if ($this->cacheItemPool) {
- $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.str_replace('\\', '.', $key));
+ $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.$this->encodeCacheKey($key));
if ($item->isHit()) {
return $this->readPropertyCache[$key] = $item->get();
}
}
+ $metadata = $this->getPropertyMetadata($class, $property);
+
$access = array();
$reflClass = new \ReflectionClass($class);
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
$camelProp = $this->camelize($property);
- $getter = 'get'.$camelProp;
+ $getter = ($metadata && $metadata->getGetter()) ? $metadata->getGetter() : 'get'.$camelProp;
$getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
$isser = 'is'.$camelProp;
$hasser = 'has'.$camelProp;
@@ -699,12 +719,14 @@ private function getWriteAccessInfo($class, $property, $value)
}
if ($this->cacheItemPool) {
- $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.str_replace('\\', '.', $key));
+ $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.$this->encodeCacheKey($key));
if ($item->isHit()) {
return $this->writePropertyCache[$key] = $item->get();
}
}
+ $metadata = $this->getPropertyMetadata($class, $property);
+
$access = array();
$reflClass = new \ReflectionClass($class);
@@ -713,7 +735,7 @@ private function getWriteAccessInfo($class, $property, $value)
$singulars = (array) Inflector::singularize($camelized);
if (is_array($value) || $value instanceof \Traversable) {
- $methods = $this->findAdderAndRemover($reflClass, $singulars);
+ $methods = $this->findAdderAndRemover($reflClass, $singulars, $metadata);
if (null !== $methods) {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
@@ -723,7 +745,8 @@ private function getWriteAccessInfo($class, $property, $value)
}
if (!isset($access[self::ACCESS_TYPE])) {
- $setter = 'set'.$camelized;
+ $setter = ($metadata && $metadata->getSetter()) ? $metadata->getSetter() : 'set'.$camelized;
+
$getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
if ($this->isMethodAccessible($reflClass, $setter, 1)) {
@@ -742,7 +765,7 @@ private function getWriteAccessInfo($class, $property, $value)
// 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)) {
+ } elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars, $metadata)) {
$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 '.
@@ -819,11 +842,17 @@ private function camelize($string)
*
* @return array|null An array containing the adder and remover when found, null otherwise
*/
- private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars)
+ private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars, PropertyMetadata $metadata = null)
{
+ $fixedAdder = ($metadata && $metadata->getAdder()) ? $metadata->getAdder() : null;
+ $fixedRemover = ($metadata && $metadata->getRemover()) ? $metadata->getRemover() : null;
+ if ($fixedAdder && $fixedRemover) {
+ return array($fixedAdder, $fixedRemover);
+ }
+
foreach ($singulars as $singular) {
- $addMethod = 'add'.$singular;
- $removeMethod = 'remove'.$singular;
+ $addMethod = $fixedAdder ?: 'add'.$singular;
+ $removeMethod = $fixedRemover ?: 'remove'.$singular;
$addMethodFound = $this->isMethodAccessible($reflClass, $addMethod, 1);
$removeMethodFound = $this->isMethodAccessible($reflClass, $removeMethod, 1);
@@ -832,6 +861,8 @@ private function findAdderAndRemover(\ReflectionClass $reflClass, array $singula
return array($addMethod, $removeMethod);
}
}
+
+ return null;
}
/**
@@ -923,4 +954,42 @@ public static function createCache($namespace, $defaultLifetime, $version, Logge
return $apcu;
}
+
+ /**
+ * Returns metadata associated with the property if it exists.
+ *
+ * @param $class
+ * @param $property
+ *
+ * @return null|PropertyMetadata
+ */
+ private function getPropertyMetadata($class, $property)
+ {
+ if ($this->cacheItemPool) {
+ $key = (false !== strpos($class, '@') ? rawurlencode($class) : $class).'..'.$property;
+ $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_METADATA.$this->encodeCacheKey($key));
+ if ($item->isHit()) {
+ return $item->get();
+ }
+ }
+
+ $metadata = null;
+ if ($this->classMetadataFactory && $classMetadata = $this->classMetadataFactory->getMetadataFor($class)) {
+ $metadata = $classMetadata->getMetadataForProperty($property);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * Escapes the key so it does not contains invalid characters.
+ *
+ * @param string $key
+ *
+ * @return string
+ */
+ private function encodeCacheKey($key)
+ {
+ return str_replace('\\', '.', $key);
+ }
}
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
index 1db6a1dba23e..83923538b398 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\PropertyAccess;
use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\PropertyAccess\Mapping\Factory\MetadataFactoryInterface;
/**
* A configurable builder to create a PropertyAccessor.
@@ -28,6 +29,11 @@ class PropertyAccessorBuilder
*/
private $cacheItemPool;
+ /**
+ * @var MetadataFactoryInterface|null
+ */
+ private $metadataFactory;
+
/**
* Enables the use of "__call" by the PropertyAccessor.
*
@@ -121,6 +127,30 @@ public function getCacheItemPool()
return $this->cacheItemPool;
}
+ /**
+ * Sets a metadata loader.
+ *
+ * @param MetadataFactoryInterface|null $metadataFactory
+ *
+ * @return PropertyAccessorBuilder The builder object
+ */
+ public function setMetadataFactory(MetadataFactoryInterface $metadataFactory = null)
+ {
+ $this->metadataFactory = $metadataFactory;
+
+ return $this;
+ }
+
+ /**
+ * Gets the used metadata loader.
+ *
+ * @return MetadataFactoryInterface|null
+ */
+ public function getMetadataFactory()
+ {
+ return $this->metadataFactory;
+ }
+
/**
* Builds and returns a new PropertyAccessor object.
*
@@ -128,6 +158,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->metadataFactory);
}
}
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 000000000000..fe71ccfa2151
--- /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\PropertyAccessor;
+use Symfony\Component\PropertyAccess\Annotation\GetterAccessor;
+
+/**
+ * Fixtures for testing metadata.
+ */
+class Dummy extends DummyParent
+{
+ /**
+ * @PropertyAccessor(getter="getter1", setter="setter1", adder="adder1", remover="remover1")
+ */
+ protected $foo;
+
+ /**
+ * @PropertyAccessor(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;
+ }
+
+ /**
+ * @GetterAccessor(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 000000000000..64e2dce9071b
--- /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\GetterAccessor;
+
+/**
+ * Fixtures for testing metadata.
+ */
+class DummyParent
+{
+ /**
+ * @GetterAccessor(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 e63af3a8bac5..5d6de1fab52e 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\PropertyAccessor;
+use Symfony\Component\PropertyAccess\Annotation\GetterAccessor;
+use Symfony\Component\PropertyAccess\Annotation\SetterAccessor;
+
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;
+
+ /**
+ * @PropertyAccessor(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;
+ }
+
+ /**
+ * @GetterAccessor(property="total")
+ */
+ public function getTotal()
+ {
+ return $this->quantity * $this->pricePerUnit;
+ }
+
+ /**
+ * @SetterAccessor(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 000000000000..e69de29bb2d1
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 000000000000..19102815663d
--- /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 000000000000..990b2ad9dfbc
--- /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 000000000000..4c78d1bc4be6
--- /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 000000000000..4b59949d6861
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/ClassMetadataTest.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 PHPUnit\Framework\TestCase;
+use Symfony\Component\PropertyAccess\Mapping\ClassMetadata;
+
+/**
+ * @author Kévin Dunglas
+ * @author Luis Ramón López
+ */
+class ClassMetadataTest extends 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->getMockBuilder('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata')->getMock();
+ $a1->method('getName')->willReturn('a1');
+
+ $a2 = $this->getMockBuilder('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata')->getMock();
+ $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->getMockBuilder('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata')->getMock();
+ $a1->method('getName')->willReturn('b1');
+ $a1->method('__sleep')->willReturn(array());
+
+ $a2 = $this->getMockBuilder('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata')->getMock();
+ $a2->method('getName')->willReturn('b2');
+ $a2->method('__sleep')->willReturn(array());
+
+ $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 000000000000..3cba200de34d
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php
@@ -0,0 +1,34 @@
+
+ *
+ * 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 PHPUnit\Framework\TestCase;
+use Symfony\Component\PropertyAccess\Mapping\Factory\BlackHoleMetadataFactory;
+
+class BlackHoleMetadataFactoryTest extends 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 000000000000..43520ea65781
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.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\Factory;
+
+use PHPUnit\Framework\TestCase;
+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 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());
+ }
+}
+
+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 000000000000..2a2277ffe0b3
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/AnnotationLoaderTest.php
@@ -0,0 +1,58 @@
+
+ *
+ * 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 PHPUnit\Framework\TestCase;
+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 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 000000000000..53f7d68f6a78
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/XmlFileLoaderTest.php
@@ -0,0 +1,56 @@
+
+ *
+ * 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 PHPUnit\Framework\TestCase;
+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 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 000000000000..ad484afb8730
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/Loader/YamlFileLoaderTest.php
@@ -0,0 +1,72 @@
+
+ *
+ * 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 PHPUnit\Framework\TestCase;
+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 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 000000000000..35b64f1359dd
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/Mapping/PropertyMetadataTest.php
@@ -0,0 +1,96 @@
+
+ *
+ * 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 PHPUnit\Framework\TestCase;
+use Symfony\Component\PropertyAccess\Mapping\PropertyMetadata;
+
+/**
+ * @author Kévin Dunglas
+ */
+class PropertyMetadataTest extends 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 000000000000..251259031872
--- /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 63bd64225039..83c399ca0680 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php
@@ -13,6 +13,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\PropertyAccess\Mapping\Factory\BlackHoleMetadataFactory;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
@@ -63,4 +64,12 @@ public function testUseCache()
$this->assertEquals($cacheItemPool, $this->builder->getCacheItemPool());
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
}
+
+ public function testUseMetadataFactory()
+ {
+ $metadataFactory = new BlackHoleMetadataFactory();
+ $this->builder->setMetadataFactory($metadataFactory);
+ $this->assertEquals($metadataFactory, $this->builder->getMetadataFactory());
+ $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 1f1026230524..542ca1f8e405 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php
@@ -11,13 +11,45 @@
namespace Symfony\Component\PropertyAccess\Tests;
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Symfony\Component\PropertyAccess\Annotation\AdderAccessor;
+use Symfony\Component\PropertyAccess\Annotation\GetterAccessor;
+use Symfony\Component\PropertyAccess\Annotation\RemoverAccessor;
+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\PropertyAccessor(adder="addAxisTest", remover="removeAxisTest")
+ */
+ private $customAxes;
+
+ // This property will only have its adder accessor overriden
+ /**
+ * @Symfony\Component\PropertyAccess\Annotation\PropertyAccessor(adder="addAxis2Test")
+ */
+ private $customAxes2;
+
+ // This property will only have its remover accessor overriden
+ /**
+ * @Symfony\Component\PropertyAccess\Annotation\PropertyAccessor(remover="removeAxis3Test")
+ */
+ private $customAxes3;
+
+ /**
+ * @param array|null $axes
+ */
public function __construct($axes = null)
{
$this->axes = $axes;
+ $this->customAxes = $axes;
+ $this->customAxes2 = $axes;
+ $this->customAxes3 = $axes;
}
// In the test, use a name that StringUtil can't uniquely singularify
@@ -26,6 +58,34 @@ public function addAxis($axis)
$this->axes[] = $axis;
}
+ // In the test, use a name that StringUtil can't uniquely singularify
+ /**
+ * @AdderAccessor(property="customVirtualAxes")
+ * @param $axis
+ */
+ public function addAxisTest($axis)
+ {
+ $this->customAxes[] = $axis;
+ }
+
+ // Only override adder accessor
+ /**
+ * @AdderAccessor(property="customVirtualAxes2")
+ * @param $axis
+ */
+ public function addAxis2Test($axis)
+ {
+ $this->customAxes2[] = $axis;
+ }
+
+ /**
+ * @param $axis
+ */
+ public function addCustomAxes3($axis)
+ {
+ $this->customAxes3[] = $axis;
+ }
+
public function removeAxis($axis)
{
foreach ($this->axes as $key => $value) {
@@ -37,10 +97,81 @@ public function removeAxis($axis)
}
}
+ /**
+ * @RemoverAccessor(property="customVirtualAxes")
+ * @param $axis
+ */
+ public function removeAxisTest($axis)
+ {
+ foreach ($this->customAxes as $key => $value) {
+ if ($value === $axis) {
+ unset($this->customAxes[$key]);
+
+ return;
+ }
+ }
+ }
+
+ // Default customAxes2 remover
+ /**
+ * @param $axis
+ */
+ public function removeCustomAxes2($axis)
+ {
+ foreach ($this->customAxes2 as $key => $value) {
+ if ($value === $axis) {
+ unset($this->customAxes2[$key]);
+
+ return;
+ }
+ }
+ }
+
+ // Only override remover accessor
+ /**
+ * @RemoverAccessor(property="customAxis3")
+ * @param $axis
+ */
+ public function removeAxis3Test($axis)
+ {
+ foreach ($this->customAxes3 as $key => $value) {
+ if ($value === $axis) {
+ unset($this->customAxes3[$key]);
+
+ return;
+ }
+ }
+ }
+
public function getAxes()
{
return $this->axes;
}
+
+ /**
+ * @GetterAccessor(property="customVirtualAxes")
+ * @return array|null
+ */
+ public function getCustomAxes()
+ {
+ return $this->customAxes;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getCustomAxes2()
+ {
+ return $this->customAxes2;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getCustomAxes3()
+ {
+ return $this->customAxes3;
+ }
}
class PropertyAccessorCollectionTest_CarOnlyAdder
@@ -146,6 +277,50 @@ public function testSetValueCallsAdderAndRemoverForNestedCollections()
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
}
+ /**
+ * @param $propertyPath string Property path to test
+ */
+ private function baseTestAdderAndRemoverPropertyPath($propertyPath, $getMethod)
+ {
+ $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, $propertyPath, $axesMerged);
+
+ $this->assertEquals($axesAfter, $car->$getMethod());
+
+ // The passed collection was not modified
+ $this->assertEquals($axesMergedCopy, $axesMerged);
+ }
+ public function testSetValueCallsCustomAdderAndRemoverForCollections()
+ {
+ $this->baseTestAdderAndRemoverPropertyPath('customAxes', 'getCustomAxes');
+ }
+
+ public function testSetValueCallsCustomAdderAndRemoverForCollectionsMethodAnnotation()
+ {
+ $this->baseTestAdderAndRemoverPropertyPath('customVirtualAxes', 'getCustomAxes');
+ }
+
+ public function testSetValueCallsCustomAdderButNotRemoverForCollectionsMethodAnnotation()
+ {
+ $this->baseTestAdderAndRemoverPropertyPath('customAxes2', 'getCustomAxes2');
+ }
+
+ public function testSetValueCallsCustomRemoverButNotAdderForCollectionsMethodAnnotation()
+ {
+ $this->baseTestAdderAndRemoverPropertyPath('customAxes3', 'getCustomAxes3');
+ }
+
/**
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
* @expectedExceptionMessageRegExp /Could not determine access type for property "axes" in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover_[^"]*"./
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index b8356500a588..554347840b7d 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -11,9 +11,12 @@
namespace Symfony\Component\PropertyAccess\Tests;
+use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
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\ReturnTyped;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
@@ -201,6 +204,18 @@ public function testGetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $p
$this->propertyAccessor->getValue($objectOrArray, $path);
}
+ public function testGetWithCustomGetter()
+ {
+ $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()
+ {
+ $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
*/
@@ -301,6 +316,28 @@ public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $p
$this->propertyAccessor->setValue($objectOrArray, $path, 'value');
}
+ public function testSetValueWithCustomSetter()
+ {
+ $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()
+ {
+ $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 d6e7afb69c48..c30722bb1499 100644
--- a/src/Symfony/Component/PropertyAccess/composer.json
+++ b/src/Symfony/Component/PropertyAccess/composer.json
@@ -18,13 +18,18 @@
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-php70": "~1.0",
- "symfony/inflector": "~3.1|~4.0"
+ "symfony/inflector": "~3.4|~4.0"
},
"require-dev": {
- "symfony/cache": "~3.1|~4.0"
+ "doctrine/annotations": "~1.2",
+ "symfony/cache": "~3.4",
+ "symfony/config": "~3.4",
+ "symfony/yaml": "~3.4"
},
"suggest": {
- "psr/cache-implementation": "To cache access methods."
+ "psr/cache-implementation": "To cache access methods.",
+ "doctrine/annotations": "To define custom accessors using annotations.",
+ "symfony/yaml": "To define custom accessors using YAML config files."
},
"autoload": {
"psr-4": { "Symfony\\Component\\PropertyAccess\\": "" },