[FrameworkBundle] Allow creating chained cache pools by providing several adapters

This commit is contained in:
Nicolas Grekas 2019-06-29 23:05:23 +02:00
parent b9b03fe1d3
commit 29ba091898
10 changed files with 152 additions and 10 deletions

View File

@ -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
-----

View File

@ -978,8 +978,38 @@ class Configuration implements ConfigurationInterface
->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()

View File

@ -29,6 +29,7 @@ use Symfony\Component\BrowserKit\AbstractBrowser;
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 @@ class FrameworkExtension extends Extension
}
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 @@ class FrameworkExtension extends Extension
}
$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);

View File

@ -269,6 +269,10 @@
</xsd:complexType>
<xsd:complexType name="cache_pool">
<xsd:sequence>
<xsd:element name="adapter" type="cache_pool_adapter" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="adapter" type="xsd:string" />
<xsd:attribute name="tags" type="xsd:string" />
@ -278,6 +282,11 @@
<xsd:attribute name="clearer" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="cache_pool_adapter">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="provider" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="workflow">
<xsd:sequence>
<xsd:element name="initial-marking" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

View File

@ -24,6 +24,14 @@ $container->loadFromExtension('framework', [
'cache.def' => [
'default_lifetime' => 11,
],
'cache.chain' => [
'default_lifetime' => 12,
'adapter' => [
'cache.adapter.array',
'cache.adapter.filesystem',
'redis://foo' => 'cache.adapter.redis',
],
],
],
],
]);

View File

@ -12,6 +12,11 @@
<framework:pool name="cache.baz" adapter="cache.adapter.filesystem" default-lifetime="7" />
<framework:pool name="cache.foobar" adapter="cache.adapter.psr6" default-lifetime="10" provider="app.cache_pool" />
<framework:pool name="cache.def" default-lifetime="11" />
<framework:pool name="cache.chain" default-lifetime="12">
<framework:adapter name="cache.adapter.array" />
<framework:adapter name="cache.adapter.filesystem" />
<framework:adapter name="cache.adapter.redis" provider="redis://foo" />
</framework:pool>
</framework:cache>
</framework:config>
</container>

View File

@ -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'}

View File

@ -20,10 +20,12 @@ use Symfony\Bundle\FullStack;
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 @@ abstract class FrameworkExtensionTest extends TestCase
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()

View File

@ -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",

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\DependencyInjection;
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 @@ class CachePoolPass implements CompilerPassInterface
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 @@ class CachePoolPass implements CompilerPassInterface
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]);