diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index 2bad905c7286b..9abe75f181cf8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -8,6 +8,7 @@ CHANGELOG
* Deprecated the `$parser` argument of `ControllerResolver::__construct()` and `DelegatingLoader::__construct()`
* Deprecated the `controller_name_converter` and `resolve_controller_name_subscriber` services
* The `ControllerResolver` and `DelegatingLoader` classes have been marked as `final`
+ * Added support for configuring chained cache pools
4.3.0
-----
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index cb704c126a8e1..eb2f620e921f8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -978,8 +978,38 @@ private function addCacheSection(ArrayNodeDefinition $rootNode)
->arrayNode('pools')
->useAttributeAsKey('name')
->prototype('array')
+ ->fixXmlConfig('adapter')
+ ->beforeNormalization()
+ ->ifTrue(function ($v) { return (isset($v['adapters']) || \is_array($v['adapter'] ?? null)) && isset($v['provider']); })
+ ->thenInvalid('Pool cannot have a "provider" while "adapter" is set to a map')
+ ->end()
->children()
- ->scalarNode('adapter')->defaultValue('cache.app')->end()
+ ->arrayNode('adapters')
+ ->info('One or more adapters to chain for creating the pool, defaults to "cache.app".')
+ ->beforeNormalization()
+ ->always()->then(function ($values) {
+ if ([0] === array_keys($values) && \is_array($values[0])) {
+ return $values[0];
+ }
+ $adapters = [];
+
+ foreach ($values as $k => $v) {
+ if (\is_int($k) && \is_string($v)) {
+ $adapters[] = $v;
+ } elseif (!\is_array($v)) {
+ $adapters[$k] = $v;
+ } elseif (isset($v['provider'])) {
+ $adapters[$v['provider']] = $v['name'] ?? $v;
+ } else {
+ $adapters[] = $v['name'] ?? $v;
+ }
+ }
+
+ return $adapters;
+ })
+ ->end()
+ ->prototype('scalar')->end()
+ ->end()
->scalarNode('tags')->defaultNull()->end()
->booleanNode('public')->defaultFalse()->end()
->integerNode('default_lifetime')->end()
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 5721f49262ce9..329e39e0d094f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -29,6 +29,7 @@
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
@@ -1809,16 +1810,29 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
}
foreach (['app', 'system'] as $name) {
$config['pools']['cache.'.$name] = [
- 'adapter' => $config[$name],
+ 'adapters' => [$config[$name]],
'public' => true,
'tags' => false,
];
}
foreach ($config['pools'] as $name => $pool) {
- if ($config['pools'][$pool['adapter']]['tags'] ?? false) {
- $pool['adapter'] = '.'.$pool['adapter'].'.inner';
+ $pool['adapters'] = $pool['adapters'] ?: ['cache.app'];
+
+ foreach ($pool['adapters'] as $provider => $adapter) {
+ if ($config['pools'][$adapter]['tags'] ?? false) {
+ $pool['adapters'][$provider] = $adapter = '.'.$adapter.'.inner';
+ }
+ }
+
+ if (1 === \count($pool['adapters'])) {
+ if (!isset($pool['provider']) && !\is_int($provider)) {
+ $pool['provider'] = $provider;
+ }
+ $definition = new ChildDefinition($adapter);
+ } else {
+ $definition = new Definition(ChainAdapter::class, [$pool['adapters'], 0]);
+ $pool['reset'] = 'reset';
}
- $definition = new ChildDefinition($pool['adapter']);
if ($pool['tags']) {
if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) {
@@ -1849,7 +1863,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
}
$definition->setPublic($pool['public']);
- unset($pool['adapter'], $pool['public'], $pool['tags']);
+ unset($pool['adapters'], $pool['public'], $pool['tags']);
$definition->addTag('cache.pool', $pool);
$container->setDefinition($name, $definition);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
index 04157511dc1a4..c11b3462c2008 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
@@ -269,6 +269,10 @@
+
+
+
+
@@ -278,6 +282,11 @@
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
index 2a85f849fa88a..8d92edf766924 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
@@ -24,6 +24,14 @@
'cache.def' => [
'default_lifetime' => 11,
],
+ 'cache.chain' => [
+ 'default_lifetime' => 12,
+ 'adapter' => [
+ 'cache.adapter.array',
+ 'cache.adapter.filesystem',
+ 'redis://foo' => 'cache.adapter.redis',
+ ],
+ ],
],
],
]);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
index 0ebf2a960aed7..2db74964b53e7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
@@ -12,6 +12,11 @@
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml
index 514e782e6e148..ee20bc74b22d6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml
@@ -17,3 +17,9 @@ framework:
provider: app.cache_pool
cache.def:
default_lifetime: 11
+ cache.chain:
+ default_lifetime: 12
+ adapter:
+ - cache.adapter.array
+ - cache.adapter.filesystem
+ - {name: cache.adapter.redis, provider: 'redis://foo'}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index e99f37d9d6d72..264551153d6d5 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -20,10 +20,12 @@
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
+use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Config\Resource\FileExistenceResource;
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -1423,13 +1425,36 @@ public function testCacheDefaultRedisProviderWithEnvVar()
public function testCachePoolServices()
{
- $container = $this->createContainerFromFile('cache');
+ $container = $this->createContainerFromFile('cache', [], true, false);
+ $container->setParameter('cache.prefix.seed', 'test');
+ $container->addCompilerPass(new CachePoolPass());
+ $container->compile();
$this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.foo', 'cache.adapter.apcu', 30);
$this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.bar', 'cache.adapter.doctrine', 5);
$this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.baz', 'cache.adapter.filesystem', 7);
$this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.foobar', 'cache.adapter.psr6', 10);
$this->assertCachePoolServiceDefinitionIsCreated($container, 'cache.def', 'cache.app', 11);
+
+ $chain = $container->getDefinition('cache.chain');
+
+ $this->assertSame(ChainAdapter::class, $chain->getClass());
+
+ $expected = [
+ [
+ (new ChildDefinition('cache.adapter.array'))
+ ->replaceArgument(0, 12),
+ (new ChildDefinition('cache.adapter.filesystem'))
+ ->replaceArgument(0, 'x5nX4TVTWn')
+ ->replaceArgument(1, 12),
+ (new ChildDefinition('cache.adapter.redis'))
+ ->replaceArgument(0, new Reference('.cache_connection.kYdiLgf'))
+ ->replaceArgument(1, 'x5nX4TVTWn')
+ ->replaceArgument(2, 12),
+ ],
+ 12,
+ ];
+ $this->assertEquals($expected, $chain->getArguments());
}
public function testRemovesResourceCheckerConfigCacheFactoryArgumentOnlyIfNoDebug()
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index 2a06127e379db..242d05ab62fb1 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -18,7 +18,7 @@
"require": {
"php": "^7.1.3",
"ext-xml": "*",
- "symfony/cache": "^4.3|^5.0",
+ "symfony/cache": "^4.4|^5.0",
"symfony/config": "^4.2|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/error-catcher": "^4.4|^5.0",
diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php
index 5d7a2369c22e6..111c5564456ec 100644
--- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php
+++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php
@@ -13,6 +13,7 @@
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -97,7 +98,50 @@ public function process(ContainerBuilder $container)
if (isset($tags[0]['provider'])) {
$tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
}
- $i = 0;
+
+ if (ChainAdapter::class === $class) {
+ $adapters = [];
+ foreach ($adapter->getArgument(0) as $provider => $adapter) {
+ $chainedPool = $adapter = new ChildDefinition($adapter);
+ $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]];
+ $chainedClass = '';
+
+ while ($adapter instanceof ChildDefinition) {
+ $adapter = $container->findDefinition($adapter->getParent());
+ $chainedClass = $chainedClass ?: $adapter->getClass();
+ if ($t = $adapter->getTag($this->cachePoolTag)) {
+ $chainedTags[0] += $t[0];
+ }
+ }
+
+ if (ChainAdapter::class === $chainedClass) {
+ throw new InvalidArgumentException(sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent()));
+ }
+
+ $i = 0;
+
+ if (isset($chainedTags[0]['provider'])) {
+ $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider'])));
+ }
+
+ if (isset($tags[0]['namespace']) && ArrayAdapter::class !== $adapter->getClass()) {
+ $chainedPool->replaceArgument($i++, $tags[0]['namespace']);
+ }
+
+ if (isset($tags[0]['default_lifetime'])) {
+ $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']);
+ }
+
+ $adapters[] = $chainedPool;
+ }
+
+ $pool->replaceArgument(0, $adapters);
+ unset($tags[0]['provider'], $tags[0]['namespace']);
+ $i = 1;
+ } else {
+ $i = 0;
+ }
+
foreach ($attributes as $attr) {
if (!isset($tags[0][$attr])) {
// no-op
@@ -105,7 +149,7 @@ public function process(ContainerBuilder $container)
if ($tags[0][$attr]) {
$pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
}
- } elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) {
+ } elseif ('namespace' !== $attr || ArrayAdapter::class !== $class) {
$pool->replaceArgument($i++, $tags[0][$attr]);
}
unset($tags[0][$attr]);