feature #16838 [PropertyAccess] Add PSR-6 cache (dunglas)
This PR was squashed before being merged into the 3.2-dev branch (closes #16838).
Discussion
----------
[PropertyAccess] Add PSR-6 cache
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | n/a
| License | MIT
| Doc PR | n/a
Follow #16294
Commits
-------
4ccabcd
[PropertyAccess] Add PSR-6 cache
This commit is contained in:
commit
ce28a869cc
@ -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',
|
||||
|
@ -8,6 +8,7 @@
|
||||
<service id="property_accessor" class="Symfony\Component\PropertyAccess\PropertyAccessor" >
|
||||
<argument /> <!-- magicCall, set by the extension -->
|
||||
<argument /> <!-- throwExceptionOnInvalidIndex, set by the extension -->
|
||||
<argument type="service" id="cache.property_access" on-invalid="ignore" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -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 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,11 +531,19 @@ 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 {
|
||||
return $this->readPropertyCache[$key];
|
||||
}
|
||||
|
||||
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);
|
||||
@ -560,10 +594,11 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||
);
|
||||
}
|
||||
|
||||
$this->readPropertyCache[$key] = $access;
|
||||
if (isset($item)) {
|
||||
$this->cacheItemPool->save($item->set($access));
|
||||
}
|
||||
|
||||
return $access;
|
||||
return $this->readPropertyCache[$key] = $access;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -673,11 +708,19 @@ 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 {
|
||||
return $this->writePropertyCache[$key];
|
||||
}
|
||||
|
||||
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);
|
||||
@ -731,10 +774,11 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||
}
|
||||
}
|
||||
|
||||
$this->writePropertyCache[$key] = $access;
|
||||
if (isset($item)) {
|
||||
$this->cacheItemPool->save($item->set($access));
|
||||
}
|
||||
|
||||
return $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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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": [
|
||||
|
Reference in New Issue
Block a user