diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index c434cac057..b579e6a99d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -24,6 +25,7 @@ use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Config\FileLocator; +use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; use Symfony\Component\Serializer\Normalizer\DataUriNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; @@ -1062,6 +1064,15 @@ class FrameworkExtension extends Extension $container->setDefinition($name, $definition); } + if (method_exists(PropertyAccessor::class, 'createCache')) { + $propertyAccessDefinition = $container->register('cache.property_access', AdapterInterface::class); + $propertyAccessDefinition->setPublic(false); + $propertyAccessDefinition->setFactory(array(PropertyAccessor::class, 'createCache')); + $propertyAccessDefinition->setArguments(array(null, null, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))); + $propertyAccessDefinition->addTag('cache.pool', array('clearer' => 'cache.default_clearer')); + $propertyAccessDefinition->addTag('monolog.logger', array('channel' => 'cache')); + } + $this->addClassesToCompile(array( 'Psr\Cache\CacheItemInterface', 'Psr\Cache\CacheItemPoolInterface', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml index 1ebee06451..d9e381c480 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml @@ -8,6 +8,7 @@ + diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index dff8453ff3..c82d0814fa 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -11,6 +11,11 @@ namespace Symfony\Component\PropertyAccess; +use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Inflector\Inflector; use Symfony\Component\PropertyAccess\Exception\AccessException; use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; @@ -97,6 +102,21 @@ class PropertyAccessor implements PropertyAccessorInterface */ const ACCESS_TYPE_NOT_FOUND = 4; + /** + * @internal + */ + const CACHE_PREFIX_READ = 'r'; + + /** + * @internal + */ + const CACHE_PREFIX_WRITE = 'w'; + + /** + * @internal + */ + const CACHE_PREFIX_PROPERTY_PATH = 'p'; + /** * @var bool */ @@ -107,6 +127,11 @@ class PropertyAccessor implements PropertyAccessorInterface */ private $ignoreInvalidIndices; + /** + * @var CacheItemPoolInterface + */ + private $cacheItemPool; + /** * @var array */ @@ -120,17 +145,24 @@ class PropertyAccessor implements PropertyAccessorInterface private static $errorHandler = array(__CLASS__, 'handleError'); private static $resultProto = array(self::VALUE => null); + /** + * @var array + */ + private $propertyPathCache = array(); + /** * Should not be used by application code. Use * {@link PropertyAccess::createPropertyAccessor()} instead. * - * @param bool $magicCall - * @param bool $throwExceptionOnInvalidIndex + * @param bool $magicCall + * @param bool $throwExceptionOnInvalidIndex + * @param CacheItemPoolInterface $cacheItemPool */ - public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false) + public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false, CacheItemPoolInterface $cacheItemPool = null) { $this->magicCall = $magicCall; $this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex; + $this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value } /** @@ -138,9 +170,7 @@ class PropertyAccessor implements PropertyAccessorInterface */ public function getValue($objectOrArray, $propertyPath) { - if (!$propertyPath instanceof PropertyPathInterface) { - $propertyPath = new PropertyPath($propertyPath); - } + $propertyPath = $this->getPropertyPath($propertyPath); $zval = array( self::VALUE => $objectOrArray, @@ -155,9 +185,7 @@ class PropertyAccessor implements PropertyAccessorInterface */ public function setValue(&$objectOrArray, $propertyPath, $value) { - if (!$propertyPath instanceof PropertyPathInterface) { - $propertyPath = new PropertyPath($propertyPath); - } + $propertyPath = $this->getPropertyPath($propertyPath); $zval = array( self::VALUE => $objectOrArray, @@ -284,9 +312,7 @@ class PropertyAccessor implements PropertyAccessorInterface */ public function isWritable($objectOrArray, $propertyPath) { - if (!$propertyPath instanceof PropertyPathInterface) { - $propertyPath = new PropertyPath($propertyPath); - } + $propertyPath = $this->getPropertyPath($propertyPath); try { $zval = array( @@ -505,65 +531,74 @@ class PropertyAccessor implements PropertyAccessorInterface */ private function getReadAccessInfo($class, $property) { - $key = $class.'::'.$property; + $key = $class.'..'.$property; if (isset($this->readPropertyCache[$key])) { - $access = $this->readPropertyCache[$key]; - } else { - $access = array(); - - $reflClass = new \ReflectionClass($class); - $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property); - $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()) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; - $access[self::ACCESS_NAME] = $getter; - } elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; - $access[self::ACCESS_NAME] = $getsetter; - } elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; - $access[self::ACCESS_NAME] = $isser; - } elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; - $access[self::ACCESS_NAME] = $hasser; - } elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; - $access[self::ACCESS_NAME] = $property; - $access[self::ACCESS_REF] = false; - } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; - $access[self::ACCESS_NAME] = $property; - $access[self::ACCESS_REF] = true; - } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) { - // we call the getter and hope the __call do the job - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC; - $access[self::ACCESS_NAME] = $getter; - } else { - $methods = array($getter, $getsetter, $isser, $hasser, '__get'); - if ($this->magicCall) { - $methods[] = '__call'; - } - - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND; - $access[self::ACCESS_NAME] = sprintf( - 'Neither the property "%s" nor one of the methods "%s()" '. - 'exist and have public access in class "%s".', - $property, - implode('()", "', $methods), - $reflClass->name - ); - } - - $this->readPropertyCache[$key] = $access; + return $this->readPropertyCache[$key]; } - return $access; + if ($this->cacheItemPool) { + $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.str_replace('\\', '.', $key)); + if ($item->isHit()) { + return $this->readPropertyCache[$key] = $item->get(); + } + } + + $access = array(); + + $reflClass = new \ReflectionClass($class); + $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property); + $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()) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; + $access[self::ACCESS_NAME] = $getter; + } elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; + $access[self::ACCESS_NAME] = $getsetter; + } elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; + $access[self::ACCESS_NAME] = $isser; + } elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; + $access[self::ACCESS_NAME] = $hasser; + } elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; + $access[self::ACCESS_NAME] = $property; + $access[self::ACCESS_REF] = false; + } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; + $access[self::ACCESS_NAME] = $property; + $access[self::ACCESS_REF] = true; + } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) { + // we call the getter and hope the __call do the job + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC; + $access[self::ACCESS_NAME] = $getter; + } else { + $methods = array($getter, $getsetter, $isser, $hasser, '__get'); + if ($this->magicCall) { + $methods[] = '__call'; + } + + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND; + $access[self::ACCESS_NAME] = sprintf( + 'Neither the property "%s" nor one of the methods "%s()" '. + 'exist and have public access in class "%s".', + $property, + implode('()", "', $methods), + $reflClass->name + ); + } + + if (isset($item)) { + $this->cacheItemPool->save($item->set($access)); + } + + return $this->readPropertyCache[$key] = $access; } /** @@ -673,68 +708,77 @@ class PropertyAccessor implements PropertyAccessorInterface */ private function getWriteAccessInfo($class, $property, $value) { - $key = $class.'::'.$property; + $key = $class.'..'.$property; if (isset($this->writePropertyCache[$key])) { - $access = $this->writePropertyCache[$key]; - } else { - $access = array(); - - $reflClass = new \ReflectionClass($class); - $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property); - $camelized = $this->camelize($property); - $singulars = (array) Inflector::singularize($camelized); - - if (is_array($value) || $value instanceof \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; - } 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 - ); - } - } - - $this->writePropertyCache[$key] = $access; + return $this->writePropertyCache[$key]; } - return $access; + if ($this->cacheItemPool) { + $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.str_replace('\\', '.', $key)); + if ($item->isHit()) { + return $this->writePropertyCache[$key] = $item->get(); + } + } + + $access = array(); + + $reflClass = new \ReflectionClass($class); + $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property); + $camelized = $this->camelize($property); + $singulars = (array) Inflector::singularize($camelized); + + if (is_array($value) || $value instanceof \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; + } 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($item)) { + $this->cacheItemPool->save($item->set($access)); + } + + return $this->writePropertyCache[$key] = $access; } /** @@ -818,4 +862,68 @@ class PropertyAccessor implements PropertyAccessorInterface return false; } + + /** + * Gets a PropertyPath instance and caches it. + * + * @param string|PropertyPath $propertyPath + * + * @return PropertyPath + */ + private function getPropertyPath($propertyPath) + { + if ($propertyPath instanceof PropertyPathInterface) { + // Don't call the copy constructor has it is not needed here + return $propertyPath; + } + + if (isset($this->propertyPathCache[$propertyPath])) { + return $this->propertyPathCache[$propertyPath]; + } + + if ($this->cacheItemPool) { + $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH.$propertyPath); + if ($item->isHit()) { + return $this->propertyPathCache[$propertyPath] = $item->get(); + } + } + + $propertyPathInstance = new PropertyPath($propertyPath); + if (isset($item)) { + $item->set($propertyPathInstance); + $this->cacheItemPool->save($item); + } + + return $this->propertyPathCache[$propertyPath] = $propertyPathInstance; + } + + /** + * Creates the APCu adapter if applicable. + * + * @param string $namespace + * @param int $defaultLifetime + * @param string $version + * @param LoggerInterface|null $logger + * + * @return AdapterInterface + * + * @throws RuntimeException When the Cache Component isn't available + */ + public static function createCache($namespace, $defaultLifetime, $version, LoggerInterface $logger = null) + { + if (!class_exists('Symfony\Component\Cache\Adapter\ApcuAdapter')) { + throw new \RuntimeException(sprintf('The Symfony Cache component must be installed to use %s().', __METHOD__)); + } + + if (!ApcuAdapter::isSupported()) { + return new NullAdapter(); + } + + $apcu = new ApcuAdapter($namespace, $defaultLifetime / 5, $version); + if (null !== $logger) { + $apcu->setLogger($logger); + } + + return $apcu; + } } diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php index fc8ac4ed2d..3225cf9bc6 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessorBuilder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\PropertyAccess; +use Psr\Cache\CacheItemPoolInterface; + /** * A configurable builder to create a PropertyAccessor. * @@ -28,6 +30,11 @@ class PropertyAccessorBuilder */ private $throwExceptionOnInvalidIndex = false; + /** + * @var CacheItemPoolInterface|null + */ + private $cacheItemPool; + /** * Enables the use of "__call" by the PropertyAccessor. * @@ -97,6 +104,30 @@ class PropertyAccessorBuilder return $this->throwExceptionOnInvalidIndex; } + /** + * Sets a cache system. + * + * @param CacheItemPoolInterface|null $cacheItemPool + * + * @return PropertyAccessorBuilder The builder object + */ + public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool = null) + { + $this->cacheItemPool = $cacheItemPool; + + return $this; + } + + /** + * Gets the used cache system. + * + * @return CacheItemPoolInterface|null + */ + public function getCacheItemPool() + { + return $this->cacheItemPool; + } + /** * Builds and returns a new PropertyAccessor object. * @@ -104,6 +135,6 @@ class PropertyAccessorBuilder */ public function getPropertyAccessor() { - return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex); + return new PropertyAccessor($this->magicCall, $this->throwExceptionOnInvalidIndex, $this->cacheItemPool); } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php index 951c6802f9..2c65e6adf6 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorBuilderTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\PropertyAccess\Tests; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyAccess\PropertyAccessorBuilder; class PropertyAccessorBuilderTest extends \PHPUnit_Framework_TestCase @@ -49,7 +51,15 @@ class PropertyAccessorBuilderTest extends \PHPUnit_Framework_TestCase public function testGetPropertyAccessor() { - $this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->getPropertyAccessor()); - $this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->enableMagicCall()->getPropertyAccessor()); + $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()); } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index 3f1ef1c040..a3a82b0b63 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\PropertyAccess\Tests; +use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass; @@ -554,4 +555,15 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase $this->assertSame('baz', $this->propertyAccessor->getValue($object, 'publicAccessor[value2]')); $this->assertSame(array('value1' => 'foo', 'value2' => 'baz'), $object->getPublicAccessor()); } + + public function testCacheReadAccess() + { + $obj = new TestClass('foo'); + + $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter()); + $this->assertEquals('foo', $propertyAccessor->getValue($obj, 'publicGetSetter')); + $propertyAccessor->setValue($obj, 'publicGetSetter', 'bar'); + $propertyAccessor->setValue($obj, 'publicGetSetter', 'baz'); + $this->assertEquals('baz', $propertyAccessor->getValue($obj, 'publicGetSetter')); + } } diff --git a/src/Symfony/Component/PropertyAccess/composer.json b/src/Symfony/Component/PropertyAccess/composer.json index 7db8f835d8..e095cbe35f 100644 --- a/src/Symfony/Component/PropertyAccess/composer.json +++ b/src/Symfony/Component/PropertyAccess/composer.json @@ -20,6 +20,12 @@ "symfony/polyfill-php70": "~1.0", "symfony/inflector": "~3.1" }, + "require-dev": { + "symfony/cache": "~3.1" + }, + "suggest": { + "psr/cache-implementation": "To cache access methods." + }, "autoload": { "psr-4": { "Symfony\\Component\\PropertyAccess\\": "" }, "exclude-from-classmap": [