Merge branch '3.3' into 3.4
* 3.3: Improved the design of the redirection method in the web toolbar Update NoSuchPropertyException message for writeProperty [DI] Don't track merged configs when the extension doesn't expose it [Cache] Use namespace versioning for backends that dont support clearing by keys
This commit is contained in:
commit
012c56b8cb
@ -250,6 +250,7 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 17px;
|
height: 17px;
|
||||||
line-height: 17px;
|
line-height: 17px;
|
||||||
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sf-toolbar-block-ajax .sf-toolbar-icon {
|
.sf-toolbar-block-ajax .sf-toolbar-icon {
|
||||||
|
@ -38,7 +38,7 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
|
|||||||
*/
|
*/
|
||||||
protected function __construct($namespace = '', $defaultLifetime = 0)
|
protected function __construct($namespace = '', $defaultLifetime = 0)
|
||||||
{
|
{
|
||||||
$this->namespace = '' === $namespace ? '' : $this->getId($namespace).':';
|
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
|
||||||
if (null !== $this->maxIdLength && strlen($namespace) > $this->maxIdLength - 24) {
|
if (null !== $this->maxIdLength && strlen($namespace) > $this->maxIdLength - 24) {
|
||||||
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, strlen($namespace), $namespace));
|
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, strlen($namespace), $namespace));
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class ProxyAdapter implements AdapterInterface
|
|||||||
{
|
{
|
||||||
$this->pool = $pool;
|
$this->pool = $pool;
|
||||||
$this->poolHash = $poolHash = spl_object_hash($pool);
|
$this->poolHash = $poolHash = spl_object_hash($pool);
|
||||||
$this->namespace = '' === $namespace ? '' : $this->getId($namespace);
|
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace);
|
||||||
$this->namespaceLen = strlen($namespace);
|
$this->namespaceLen = strlen($namespace);
|
||||||
$this->createCacheItem = \Closure::bind(
|
$this->createCacheItem = \Closure::bind(
|
||||||
function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
|
function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
|
||||||
|
@ -148,6 +148,8 @@ final class CacheItem implements CacheItemInterface
|
|||||||
*
|
*
|
||||||
* @param string $key The key to validate
|
* @param string $key The key to validate
|
||||||
*
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
* @throws InvalidArgumentException When $key is not valid
|
* @throws InvalidArgumentException When $key is not valid
|
||||||
*/
|
*/
|
||||||
public static function validateKey($key)
|
public static function validateKey($key)
|
||||||
@ -161,6 +163,8 @@ final class CacheItem implements CacheItemInterface
|
|||||||
if (false !== strpbrk($key, '{}()/\@:')) {
|
if (false !== strpbrk($key, '{}()/\@:')) {
|
||||||
throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key));
|
throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +37,7 @@ abstract class AbstractCache implements CacheInterface, LoggerAwareInterface
|
|||||||
protected function __construct($namespace = '', $defaultLifetime = 0)
|
protected function __construct($namespace = '', $defaultLifetime = 0)
|
||||||
{
|
{
|
||||||
$this->defaultLifetime = max(0, (int) $defaultLifetime);
|
$this->defaultLifetime = max(0, (int) $defaultLifetime);
|
||||||
$this->namespace = '' === $namespace ? '' : $this->getId($namespace).':';
|
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
|
||||||
if (null !== $this->maxIdLength && strlen($namespace) > $this->maxIdLength - 24) {
|
if (null !== $this->maxIdLength && strlen($namespace) > $this->maxIdLength - 24) {
|
||||||
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, strlen($namespace), $namespace));
|
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, strlen($namespace), $namespace));
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class CacheItemTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function testValidKey()
|
public function testValidKey()
|
||||||
{
|
{
|
||||||
$this->assertNull(CacheItem::validateKey('foo'));
|
$this->assertSame('foo', CacheItem::validateKey('foo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +24,8 @@ trait AbstractTrait
|
|||||||
use LoggerAwareTrait;
|
use LoggerAwareTrait;
|
||||||
|
|
||||||
private $namespace;
|
private $namespace;
|
||||||
|
private $namespaceVersion = '';
|
||||||
|
private $versioningIsEnabled = false;
|
||||||
private $deferred = array();
|
private $deferred = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,10 +104,18 @@ trait AbstractTrait
|
|||||||
*/
|
*/
|
||||||
public function clear()
|
public function clear()
|
||||||
{
|
{
|
||||||
|
if ($cleared = $this->versioningIsEnabled) {
|
||||||
|
$this->namespaceVersion = 2;
|
||||||
|
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
|
||||||
|
$this->namespaceVersion = 1 + (int) $v;
|
||||||
|
}
|
||||||
|
$this->namespaceVersion .= ':';
|
||||||
|
$cleared = $this->doSave(array('@'.$this->namespace => $this->namespaceVersion), 0);
|
||||||
|
}
|
||||||
$this->deferred = array();
|
$this->deferred = array();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $this->doClear($this->namespace);
|
return $this->doClear($this->namespace) || $cleared;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
CacheItem::log($this->logger, 'Failed to clear the cache', array('exception' => $e));
|
CacheItem::log($this->logger, 'Failed to clear the cache', array('exception' => $e));
|
||||||
|
|
||||||
@ -158,6 +168,27 @@ trait AbstractTrait
|
|||||||
return $ok;
|
return $ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables/disables versioning of items.
|
||||||
|
*
|
||||||
|
* When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed,
|
||||||
|
* but old keys may need garbage collection and extra round-trips to the back-end are required.
|
||||||
|
*
|
||||||
|
* Calling this method also clears the memoized namespace version and thus forces a resynchonization of it.
|
||||||
|
*
|
||||||
|
* @param bool $enable
|
||||||
|
*
|
||||||
|
* @return bool the previous state of versioning
|
||||||
|
*/
|
||||||
|
public function enableVersioning($enable = true)
|
||||||
|
{
|
||||||
|
$wasEnabled = $this->versioningIsEnabled;
|
||||||
|
$this->versioningIsEnabled = (bool) $enable;
|
||||||
|
$this->namespaceVersion = '';
|
||||||
|
|
||||||
|
return $wasEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like the native unserialize() function but throws an exception if anything goes wrong.
|
* Like the native unserialize() function but throws an exception if anything goes wrong.
|
||||||
*
|
*
|
||||||
@ -189,11 +220,18 @@ trait AbstractTrait
|
|||||||
{
|
{
|
||||||
CacheItem::validateKey($key);
|
CacheItem::validateKey($key);
|
||||||
|
|
||||||
if (null === $this->maxIdLength) {
|
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
|
||||||
return $this->namespace.$key;
|
$this->namespaceVersion = '1:';
|
||||||
|
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
|
||||||
|
$this->namespaceVersion = $v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (strlen($id = $this->namespace.$key) > $this->maxIdLength) {
|
|
||||||
$id = $this->namespace.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22);
|
if (null === $this->maxIdLength) {
|
||||||
|
return $this->namespace.$this->namespaceVersion.$key;
|
||||||
|
}
|
||||||
|
if (strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
|
||||||
|
$id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $id;
|
return $id;
|
||||||
|
@ -54,6 +54,7 @@ trait MemcachedTrait
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent::__construct($namespace, $defaultLifetime);
|
parent::__construct($namespace, $defaultLifetime);
|
||||||
|
$this->enableVersioning();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,7 +243,7 @@ trait MemcachedTrait
|
|||||||
*/
|
*/
|
||||||
protected function doClear($namespace)
|
protected function doClear($namespace)
|
||||||
{
|
{
|
||||||
return $this->checkResultCode($this->getClient()->flush());
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkResultCode($result)
|
private function checkResultCode($result)
|
||||||
|
@ -46,7 +46,9 @@ trait RedisTrait
|
|||||||
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
|
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
|
||||||
throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
|
throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
|
||||||
}
|
}
|
||||||
if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client) {
|
if ($redisClient instanceof \RedisCluster) {
|
||||||
|
$this->enableversioning();
|
||||||
|
} elseif (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \Predis\Client) {
|
||||||
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
||||||
}
|
}
|
||||||
$this->redis = $redisClient;
|
$this->redis = $redisClient;
|
||||||
@ -171,8 +173,8 @@ trait RedisTrait
|
|||||||
*/
|
*/
|
||||||
protected function doClear($namespace)
|
protected function doClear($namespace)
|
||||||
{
|
{
|
||||||
// When using a native Redis cluster, clearing the cache cannot work and always returns false.
|
// When using a native Redis cluster, clearing the cache is done by versioning in AbstractTrait::clear().
|
||||||
// Clearing the cache should then be done by any other means (e.g. by restarting the cluster).
|
// This means old keys are not really removed until they expire and may need gargage collection.
|
||||||
|
|
||||||
$cleared = true;
|
$cleared = true;
|
||||||
$hosts = array($this->redis);
|
$hosts = array($this->redis);
|
||||||
|
@ -67,7 +67,7 @@ class MergeExtensionConfigurationPass implements CompilerPassInterface
|
|||||||
|
|
||||||
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
|
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
|
||||||
// don't keep track of env vars that are *overridden* when configs are merged
|
// don't keep track of env vars that are *overridden* when configs are merged
|
||||||
$resolvingBag->freezeAfterProcessing($extension);
|
$resolvingBag->freezeAfterProcessing($extension, $tmpContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
$container->merge($tmpContainer);
|
$container->merge($tmpContainer);
|
||||||
@ -92,12 +92,16 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag
|
|||||||
$this->mergeEnvPlaceholders($parameterBag);
|
$this->mergeEnvPlaceholders($parameterBag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function freezeAfterProcessing(Extension $extension)
|
public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
|
if (!$config = $extension->getProcessedConfigs()) {
|
||||||
|
// Extension::processConfiguration() wasn't called, we cannot know how configs were merged
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->processedEnvPlaceholders = array();
|
$this->processedEnvPlaceholders = array();
|
||||||
|
|
||||||
// serialize config to catch env vars nested in object graphs
|
// serialize config and container to catch env vars nested in object graphs
|
||||||
$config = serialize($extension->getProcessedConfigs());
|
$config = serialize($config).serialize($container->getDefinitions()).serialize($container->getAliases()).serialize($container->getParameterBag()->all());
|
||||||
|
|
||||||
foreach (parent::getEnvPlaceholders() as $env => $placeholders) {
|
foreach (parent::getEnvPlaceholders() as $env => $placeholders) {
|
||||||
foreach ($placeholders as $placeholder) {
|
foreach ($placeholders as $placeholder) {
|
||||||
|
@ -649,7 +649,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
|
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
|
||||||
$object->{$access[self::ACCESS_NAME]}($value);
|
$object->{$access[self::ACCESS_NAME]}($value);
|
||||||
} elseif (self::ACCESS_TYPE_NOT_FOUND === $access[self::ACCESS_TYPE]) {
|
} elseif (self::ACCESS_TYPE_NOT_FOUND === $access[self::ACCESS_TYPE]) {
|
||||||
throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s".', $property));
|
throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_class($object)));
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
|
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAcces
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
* @expectedExceptionMessage Could not determine access type for property "axes".
|
* @expectedExceptionMessageRegExp /Could not determine access type for property "axes" in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover_[^"]*"./
|
||||||
*/
|
*/
|
||||||
public function testSetValueFailsIfNoAdderNorRemoverFound()
|
public function testSetValueFailsIfNoAdderNorRemoverFound()
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user