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;
|
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
|
||||||
|
|
||||||
use Doctrine\Common\Annotations\Reader;
|
use Doctrine\Common\Annotations\Reader;
|
||||||
|
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
@ -24,6 +25,7 @@ use Symfony\Component\Config\Resource\DirectoryResource;
|
|||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
||||||
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
|
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
|
||||||
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||||
@ -1062,6 +1064,15 @@ class FrameworkExtension extends Extension
|
|||||||
$container->setDefinition($name, $definition);
|
$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(
|
$this->addClassesToCompile(array(
|
||||||
'Psr\Cache\CacheItemInterface',
|
'Psr\Cache\CacheItemInterface',
|
||||||
'Psr\Cache\CacheItemPoolInterface',
|
'Psr\Cache\CacheItemPoolInterface',
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<service id="property_accessor" class="Symfony\Component\PropertyAccess\PropertyAccessor" >
|
<service id="property_accessor" class="Symfony\Component\PropertyAccess\PropertyAccessor" >
|
||||||
<argument /> <!-- magicCall, set by the extension -->
|
<argument /> <!-- magicCall, set by the extension -->
|
||||||
<argument /> <!-- throwExceptionOnInvalidIndex, set by the extension -->
|
<argument /> <!-- throwExceptionOnInvalidIndex, set by the extension -->
|
||||||
|
<argument type="service" id="cache.property_access" on-invalid="ignore" />
|
||||||
</service>
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -11,6 +11,11 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\PropertyAccess;
|
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\Inflector\Inflector;
|
||||||
use Symfony\Component\PropertyAccess\Exception\AccessException;
|
use Symfony\Component\PropertyAccess\Exception\AccessException;
|
||||||
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
|
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
|
||||||
@ -97,6 +102,21 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
*/
|
*/
|
||||||
const ACCESS_TYPE_NOT_FOUND = 4;
|
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
|
* @var bool
|
||||||
*/
|
*/
|
||||||
@ -107,6 +127,11 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
*/
|
*/
|
||||||
private $ignoreInvalidIndices;
|
private $ignoreInvalidIndices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CacheItemPoolInterface
|
||||||
|
*/
|
||||||
|
private $cacheItemPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -120,17 +145,24 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
private static $errorHandler = array(__CLASS__, 'handleError');
|
private static $errorHandler = array(__CLASS__, 'handleError');
|
||||||
private static $resultProto = array(self::VALUE => null);
|
private static $resultProto = array(self::VALUE => null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $propertyPathCache = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should not be used by application code. Use
|
* Should not be used by application code. Use
|
||||||
* {@link PropertyAccess::createPropertyAccessor()} instead.
|
* {@link PropertyAccess::createPropertyAccessor()} instead.
|
||||||
*
|
*
|
||||||
* @param bool $magicCall
|
* @param bool $magicCall
|
||||||
* @param bool $throwExceptionOnInvalidIndex
|
* @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->magicCall = $magicCall;
|
||||||
$this->ignoreInvalidIndices = !$throwExceptionOnInvalidIndex;
|
$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)
|
public function getValue($objectOrArray, $propertyPath)
|
||||||
{
|
{
|
||||||
if (!$propertyPath instanceof PropertyPathInterface) {
|
$propertyPath = $this->getPropertyPath($propertyPath);
|
||||||
$propertyPath = new PropertyPath($propertyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
$zval = array(
|
$zval = array(
|
||||||
self::VALUE => $objectOrArray,
|
self::VALUE => $objectOrArray,
|
||||||
@ -155,9 +185,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
*/
|
*/
|
||||||
public function setValue(&$objectOrArray, $propertyPath, $value)
|
public function setValue(&$objectOrArray, $propertyPath, $value)
|
||||||
{
|
{
|
||||||
if (!$propertyPath instanceof PropertyPathInterface) {
|
$propertyPath = $this->getPropertyPath($propertyPath);
|
||||||
$propertyPath = new PropertyPath($propertyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
$zval = array(
|
$zval = array(
|
||||||
self::VALUE => $objectOrArray,
|
self::VALUE => $objectOrArray,
|
||||||
@ -284,9 +312,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
*/
|
*/
|
||||||
public function isWritable($objectOrArray, $propertyPath)
|
public function isWritable($objectOrArray, $propertyPath)
|
||||||
{
|
{
|
||||||
if (!$propertyPath instanceof PropertyPathInterface) {
|
$propertyPath = $this->getPropertyPath($propertyPath);
|
||||||
$propertyPath = new PropertyPath($propertyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$zval = array(
|
$zval = array(
|
||||||
@ -505,65 +531,74 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
*/
|
*/
|
||||||
private function getReadAccessInfo($class, $property)
|
private function getReadAccessInfo($class, $property)
|
||||||
{
|
{
|
||||||
$key = $class.'::'.$property;
|
$key = $class.'..'.$property;
|
||||||
|
|
||||||
if (isset($this->readPropertyCache[$key])) {
|
if (isset($this->readPropertyCache[$key])) {
|
||||||
$access = $this->readPropertyCache[$key];
|
return $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 $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)
|
private function getWriteAccessInfo($class, $property, $value)
|
||||||
{
|
{
|
||||||
$key = $class.'::'.$property;
|
$key = $class.'..'.$property;
|
||||||
|
|
||||||
if (isset($this->writePropertyCache[$key])) {
|
if (isset($this->writePropertyCache[$key])) {
|
||||||
$access = $this->writePropertyCache[$key];
|
return $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 $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;
|
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;
|
namespace Symfony\Component\PropertyAccess;
|
||||||
|
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A configurable builder to create a PropertyAccessor.
|
* A configurable builder to create a PropertyAccessor.
|
||||||
*
|
*
|
||||||
@ -28,6 +30,11 @@ class PropertyAccessorBuilder
|
|||||||
*/
|
*/
|
||||||
private $throwExceptionOnInvalidIndex = false;
|
private $throwExceptionOnInvalidIndex = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CacheItemPoolInterface|null
|
||||||
|
*/
|
||||||
|
private $cacheItemPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the use of "__call" by the PropertyAccessor.
|
* Enables the use of "__call" by the PropertyAccessor.
|
||||||
*
|
*
|
||||||
@ -97,6 +104,30 @@ class PropertyAccessorBuilder
|
|||||||
return $this->throwExceptionOnInvalidIndex;
|
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.
|
* Builds and returns a new PropertyAccessor object.
|
||||||
*
|
*
|
||||||
@ -104,6 +135,6 @@ class PropertyAccessorBuilder
|
|||||||
*/
|
*/
|
||||||
public function getPropertyAccessor()
|
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;
|
namespace Symfony\Component\PropertyAccess\Tests;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
|
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
|
||||||
|
|
||||||
class PropertyAccessorBuilderTest extends \PHPUnit_Framework_TestCase
|
class PropertyAccessorBuilderTest extends \PHPUnit_Framework_TestCase
|
||||||
@ -49,7 +51,15 @@ class PropertyAccessorBuilderTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testGetPropertyAccessor()
|
public function testGetPropertyAccessor()
|
||||||
{
|
{
|
||||||
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->getPropertyAccessor());
|
$this->assertInstanceOf(PropertyAccessor::class, $this->builder->getPropertyAccessor());
|
||||||
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->enableMagicCall()->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;
|
namespace Symfony\Component\PropertyAccess\Tests;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||||
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
|
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('baz', $this->propertyAccessor->getValue($object, 'publicAccessor[value2]'));
|
||||||
$this->assertSame(array('value1' => 'foo', 'value2' => 'baz'), $object->getPublicAccessor());
|
$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/polyfill-php70": "~1.0",
|
||||||
"symfony/inflector": "~3.1"
|
"symfony/inflector": "~3.1"
|
||||||
},
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/cache": "~3.1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"psr/cache-implementation": "To cache access methods."
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": { "Symfony\\Component\\PropertyAccess\\": "" },
|
"psr-4": { "Symfony\\Component\\PropertyAccess\\": "" },
|
||||||
"exclude-from-classmap": [
|
"exclude-from-classmap": [
|
||||||
|
Reference in New Issue
Block a user