diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php
new file mode 100644
index 0000000000000..395169caaca47
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\DefinitionDecorator;
+
+/**
+ * @author Nicolas Grekas
+ */
+class CachePoolPass implements CompilerPassInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) {
+ $pool = $container->getDefinition($id);
+
+ if (!$pool instanceof DefinitionDecorator) {
+ throw new \InvalidArgumentException(sprintf('Services tagged with "cache.pool" must have a parent service but "%s" has none.', $id));
+ }
+
+ $adapter = $pool;
+
+ do {
+ $adapterId = $adapter->getParent();
+ $adapter = $container->getDefinition($adapterId);
+ } while ($adapter instanceof DefinitionDecorator && !$adapter->hasTag('cache.adapter'));
+
+ if (!$adapter->hasTag('cache.adapter')) {
+ throw new \InvalidArgumentException(sprintf('Services tagged with "cache.pool" must have a parent service tagged with "cache.adapter" but "%s" has none.', $id));
+ }
+
+ $tags = $adapter->getTag('cache.adapter');
+
+ if (!isset($tags[0]['namespace_arg_index'])) {
+ throw new \InvalidArgumentException(sprintf('Invalid "cache.adapter" tag for service "%s": attribute "namespace_arg_index" is missing.', $adapterId));
+ }
+
+ if (!$adapter->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('Services tagged as "cache.adapter" must be abstract: "%s" is not.', $adapterId));
+ }
+
+ if (0 <= $namespaceArgIndex = $tags[0]['namespace_arg_index']) {
+ $pool->replaceArgument($namespaceArgIndex, $this->getNamespace($id));
+ }
+ }
+ }
+
+ private function getNamespace($id)
+ {
+ return substr(str_replace('/', '-', base64_encode(md5('symfony.'.$id, true))), 0, 10);
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index ab55275b20b2c..83981d8c76603 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -114,6 +114,7 @@ public function getConfigTreeBuilder()
$this->addSerializerSection($rootNode);
$this->addPropertyAccessSection($rootNode);
$this->addPropertyInfoSection($rootNode);
+ $this->addCacheSection($rootNode);
return $treeBuilder;
}
@@ -547,4 +548,33 @@ private function addPropertyInfoSection(ArrayNodeDefinition $rootNode)
->end()
;
}
+
+ private function addCacheSection(ArrayNodeDefinition $rootNode)
+ {
+ $rootNode
+ ->children()
+ ->arrayNode('cache')
+ ->info('Cache configuration')
+ ->fixXmlConfig('pool')
+ ->children()
+ ->arrayNode('pools')
+ ->useAttributeAsKey('name')
+ ->prototype('array')
+ ->children()
+ ->enumNode('type')
+ ->info('The cache pool type (one of "apcu", "doctrine", "psr6" or "filesystem")')
+ ->isRequired()
+ ->values(array('apcu', 'doctrine', 'psr6', 'filesystem'))
+ ->end()
+ ->integerNode('default_lifetime')->defaultValue(0)->end()
+ ->scalarNode('cache_provider_service')->defaultNull()->end()
+ ->scalarNode('directory')->defaultNull()->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+ }
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 324915ecb211d..956610f410c85 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -138,6 +138,10 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerPropertyInfoConfiguration($config['property_info'], $container, $loader);
}
+ if (isset($config['cache'])) {
+ $this->registerCacheConfiguration($config['cache'], $container, $loader);
+ }
+
$loader->load('debug_prod.xml');
$definition = $container->findDefinition('debug.debug_handlers_listener');
@@ -1017,6 +1021,27 @@ private function registerPropertyInfoConfiguration(array $config, ContainerBuild
}
}
+ private function registerCacheConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
+ {
+ if (!empty($config['pools'])) {
+ $loader->load('cache_adapters.xml');
+ }
+
+ foreach ($config['pools'] as $name => $poolConfig) {
+ $poolDefinition = new DefinitionDecorator('cache.adapter.'.$poolConfig['type']);
+ $poolDefinition->replaceArgument(1, $poolConfig['default_lifetime']);
+
+ if ('doctrine' === $poolConfig['type'] || 'psr6' === $poolConfig['type']) {
+ $poolDefinition->replaceArgument(0, new Reference($poolConfig['cache_provider_service']));
+ } elseif ('filesystem' === $poolConfig['type'] && isset($poolConfig['directory'][0])) {
+ $poolDefinition->replaceArgument(0, $poolConfig['directory']);
+ }
+
+ $poolDefinition->addTag('cache.pool');
+ $container->setDefinition('cache.pool.'.$name, $poolDefinition);
+ }
+ }
+
/**
* Gets a hash of the kernel root directory.
*
diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
index 94062da0039f5..9c0c91eda6b1f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -14,6 +14,7 @@
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass;
+use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
@@ -87,6 +88,7 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new SerializerPass());
$container->addCompilerPass(new PropertyInfoPass());
+ $container->addCompilerPass(new CachePoolPass());
if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache_adapters.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache_adapters.xml
new file mode 100644
index 0000000000000..9c49c8672de8d
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache_adapters.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %kernel.cache_dir%
+
+
+
+
+
+
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 cead2295ed1ac..cee9299e44e58 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
@@ -25,6 +25,7 @@
+
@@ -202,4 +203,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php
new file mode 100644
index 0000000000000..f07c04c7e0767
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
+
+use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\DefinitionDecorator;
+
+class CachePoolPassTest extends \PHPUnit_Framework_TestCase
+{
+ private $cachePoolPass;
+
+ protected function setUp()
+ {
+ $this->cachePoolPass = new CachePoolPass();
+ }
+
+ public function testNamespaceArgumentIsReplaced()
+ {
+ $container = new ContainerBuilder();
+ $adapter = new Definition();
+ $adapter->setAbstract(true);
+ $adapter->addTag('cache.adapter', array('namespace_arg_index' => 0));
+ $container->setDefinition('app.cache_adapter', $adapter);
+ $cachePool = new DefinitionDecorator('app.cache_adapter');
+ $cachePool->addArgument(null);
+ $cachePool->addTag('cache.pool');
+ $container->setDefinition('app.cache_pool', $cachePool);
+
+ $this->cachePoolPass->process($container);
+
+ $this->assertSame('yRnzIIVLvL', $cachePool->getArgument(0));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Services tagged with "cache.pool" must have a parent service but "app.cache_pool" has none.
+ */
+ public function testThrowsExceptionWhenCachePoolHasNoParentDefinition()
+ {
+ $container = new ContainerBuilder();
+ $cachePool = new Definition();
+ $cachePool->addTag('cache.pool');
+ $container->setDefinition('app.cache_pool', $cachePool);
+
+ $this->cachePoolPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Services tagged with "cache.pool" must have a parent service tagged with "cache.adapter" but "app.cache_pool" has none.
+ */
+ public function testThrowsExceptionWhenCachePoolIsNotBasedOnAdapter()
+ {
+ $container = new ContainerBuilder();
+ $container->register('app.cache_adapter');
+ $cachePool = new DefinitionDecorator('app.cache_adapter');
+ $cachePool->addTag('cache.pool');
+ $container->setDefinition('app.cache_pool', $cachePool);
+
+ $this->cachePoolPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Invalid "cache.adapter" tag for service "app.cache_adapter": attribute "namespace_arg_index" is missing.
+ */
+ public function testThrowsExceptionWhenCacheAdapterDefinesNoNamespaceArgument()
+ {
+ $container = new ContainerBuilder();
+ $adapter = new Definition();
+ $adapter->setAbstract(true);
+ $adapter->addTag('cache.adapter');
+ $container->setDefinition('app.cache_adapter', $adapter);
+ $cachePool = new DefinitionDecorator('app.cache_adapter');
+ $cachePool->addTag('cache.pool');
+ $container->setDefinition('app.cache_pool', $cachePool);
+
+ $this->cachePoolPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Services tagged as "cache.adapter" must be abstract: "app.cache_adapter" is not.
+ */
+ public function testThrowsExceptionWhenCacheAdapterIsNotAbstract()
+ {
+ $container = new ContainerBuilder();
+ $adapter = new Definition();
+ $adapter->addTag('cache.adapter', array('namespace_arg_index' => 0));
+ $container->setDefinition('app.cache_adapter', $adapter);
+ $cachePool = new DefinitionDecorator('app.cache_adapter');
+ $cachePool->addTag('cache.pool');
+ $container->setDefinition('app.cache_pool', $cachePool);
+
+ $this->cachePoolPass->process($container);
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
new file mode 100644
index 0000000000000..63e5441293f60
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
@@ -0,0 +1,27 @@
+loadFromExtension('framework', array(
+ 'cache' => array(
+ 'pools' => array(
+ 'foo' => array(
+ 'type' => 'apcu',
+ 'default_lifetime' => 30,
+ ),
+ 'bar' => array(
+ 'type' => 'doctrine',
+ 'default_lifetime' => 5,
+ 'cache_provider_service' => 'app.doctrine_cache_provider',
+ ),
+ 'baz' => array(
+ 'type' => 'filesystem',
+ 'default_lifetime' => 7,
+ 'directory' => 'app/cache/psr',
+ ),
+ 'foobar' => array(
+ 'type' => 'psr6',
+ 'default_lifetime' => 10,
+ 'cache_provider_service' => 'app.cache_pool',
+ ),
+ ),
+ ),
+));
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
new file mode 100644
index 0000000000000..f3d26f7380290
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml
new file mode 100644
index 0000000000000..0d45b13527161
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/cache.yml
@@ -0,0 +1,18 @@
+framework:
+ cache:
+ pools:
+ foo:
+ type: apcu
+ default_lifetime: 30
+ bar:
+ type: doctrine
+ default_lifetime: 5
+ cache_provider_service: app.doctrine_cache_provider
+ baz:
+ type: filesystem
+ default_lifetime: 7
+ directory: app/cache/psr
+ foobar:
+ type: psr6
+ default_lifetime: 10
+ cache_provider_service: app.cache_pool
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index 55d9a16e77e5c..93478df449b99 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -13,6 +13,9 @@
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
+use Symfony\Component\Cache\Adapter\ApcuAdapter;
+use Symfony\Component\Cache\Adapter\DoctrineAdapter;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
@@ -568,6 +571,16 @@ public function testPropertyInfoEnabled()
$this->assertTrue($container->has('property_info'));
}
+ public function testCachePoolServices()
+ {
+ $container = $this->createContainerFromFile('cache');
+
+ $this->assertCachePoolServiceDefinitionIsCreated($container, 'foo', 'apcu', array('index_1' => 30), 0);
+ $this->assertCachePoolServiceDefinitionIsCreated($container, 'bar', 'doctrine', array('index_0' => new Reference('app.doctrine_cache_provider'), 'index_1' => 5));
+ $this->assertCachePoolServiceDefinitionIsCreated($container, 'baz', 'filesystem', array('index_0' => 'app/cache/psr', 'index_1' => 7));
+ $this->assertCachePoolServiceDefinitionIsCreated($container, 'foobar', 'psr6', array('index_0' => new Reference('app.cache_pool'), 'index_1' => 10));
+ }
+
protected function createContainer(array $data = array())
{
return new ContainerBuilder(new ParameterBag(array_merge(array(
@@ -636,4 +649,39 @@ private function assertVersionStrategy(ContainerBuilder $container, Reference $r
$this->assertEquals($format, $versionStrategy->getArgument(1));
}
}
+
+ private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $container, $name, $type, array $arguments, $namespaceArgumentIndex = null)
+ {
+ $id = 'cache.pool.'.$name;
+
+ $this->assertTrue($container->has($id), sprintf('Service definition "%s" for cache pool of type "%s" is registered', $id, $type));
+
+ $poolDefinition = $container->getDefinition($id);
+
+ $this->assertInstanceOf(DefinitionDecorator::class, $poolDefinition, sprintf('Cache pool "%s" is based on an abstract cache adapter.', $name));
+ $this->assertEquals($arguments, $poolDefinition->getArguments());
+
+ $adapterDefinition = $container->getDefinition($poolDefinition->getParent());
+
+ switch ($type) {
+ case 'apcu':
+ $this->assertSame(ApcuAdapter::class, $adapterDefinition->getClass());
+ break;
+ case 'doctrine':
+ $this->assertSame(DoctrineAdapter::class, $adapterDefinition->getClass());
+ break;
+ case 'filesystem':
+ $this->assertSame(FilesystemAdapter::class, $adapterDefinition->getClass());
+ break;
+ }
+
+ $this->assertTrue($adapterDefinition->hasTag('cache.adapter'), sprintf('Service definition "%s" is tagged with the "cache.adapter" tag.', $id));
+
+ $tag = $adapterDefinition->getTag('cache.adapter');
+
+ if (null !== $namespaceArgumentIndex) {
+ $this->assertTrue(isset($tag[0]['namespace-arg-index']), 'The namespace argument index is given by the "namespace-arg-index" attribute of the "cache.adapter" tag.');
+ $this->assertSame($namespaceArgumentIndex, $tag[0]['namespace-arg-index'], 'The namespace argument index is given by the "namespace-arg-index" attribute of the "cache.adapter" tag.');
+ }
+ }
}