Skip to content

Commit

Permalink
feature #18371 [FrameworkBundle] integrate the Cache component (xabbu…
Browse files Browse the repository at this point in the history
…h, nicolas-grekas)

This PR was merged into the 3.1-dev branch.

Discussion
----------

[FrameworkBundle] integrate the Cache component

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #17537
| License       | MIT
| Doc PR        | -

Last commit is the diff with #17868.

Commits
-------

4152634 [FrameworkBundle] Add default pool & system adapter
714b916 [FrameworkBundle] Add & use Psr6CacheClearer
4740c5c [FrameworkBundle] use abstract cache.pool decoration and aliases
92b1a20 [FrameworkBundle] Fix and add tests for cache pool wiring
e44bfdc [FrameworkBundle] Add cache-pool tag and wiring
281eafa [FrameworkBundle] Integrate the Cache component
bc51fde [Cache] Normalize constructor arguments order
  • Loading branch information
fabpot committed Apr 7, 2016
2 parents eb40f16 + 4152634 commit 0b67fa3
Show file tree
Hide file tree
Showing 28 changed files with 592 additions and 13 deletions.
@@ -0,0 +1,79 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
use Symfony\Component\DependencyInjection\Reference;

/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CachePoolPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$attributes = array(
'provider',
'namespace',
'default_lifetime',
);
foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) {
$adapter = $pool = $container->getDefinition($id);
if ($pool->isAbstract()) {
continue;
}
if (!isset($tags[0]['namespace'])) {
$tags[0]['namespace'] = $this->getNamespace($id);
}
while ($adapter instanceof DefinitionDecorator) {
$adapter = $container->findDefinition($adapter->getParent());
if ($t = $adapter->getTag('cache.pool')) {
$tags[0] += $t[0];
}
}
if (isset($tags[0]['clearer'])) {
$clearer = $container->getDefinition($tags[0]['clearer']);
} else {
$clearer = null;
}
unset($tags[0]['clearer']);

if (isset($tags[0]['provider']) && is_string($tags[0]['provider'])) {
$tags[0]['provider'] = new Reference($tags[0]['provider']);
}
$i = 0;
foreach ($attributes as $attr) {
if (isset($tags[0][$attr])) {
$pool->replaceArgument($i++, $tags[0][$attr]);
}
unset($tags[0][$attr]);
}
if (!empty($tags[0])) {
throw new \InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace" and "default_lifetime", found "%s".', $id, implode('", "', array_keys($tags[0]))));
}

if (null !== $clearer) {
$clearer->addMethodCall('addPool', array(new Reference($id)));
}
}
}

private function getNamespace($id)
{
return substr(str_replace('/', '-', base64_encode(md5('symfony.'.$id, true))), 0, 10);
}
}
Expand Up @@ -114,6 +114,7 @@ public function getConfigTreeBuilder()
$this->addSerializerSection($rootNode);
$this->addPropertyAccessSection($rootNode);
$this->addPropertyInfoSection($rootNode);
$this->addCacheSection($rootNode);

return $treeBuilder;
}
Expand Down Expand Up @@ -547,4 +548,39 @@ private function addPropertyInfoSection(ArrayNodeDefinition $rootNode)
->end()
;
}

private function addCacheSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('cache')
->info('Cache configuration')
->addDefaultsIfNotSet()
->fixXmlConfig('pool')
->children()
->arrayNode('pools')
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('adapter')
->info('The cache pool adapter service to use as template definition.')
->defaultValue('cache.adapter.shared')
->end()
->booleanNode('public')->defaultFalse()->end()
->integerNode('default_lifetime')->end()
->scalarNode('provider')
->info('The service name to use as provider when the specified adapter needs one.')
->end()
->scalarNode('namespace')
->info('The namespace where cached items are stored. Auto-generated by default. Set to false to disable namespacing.')
->end()
->scalarNode('clearer')->defaultValue('cache.default_pools_clearer')->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
}
Expand Up @@ -122,6 +122,7 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerFragmentsConfiguration($config['fragments'], $container, $loader);
$this->registerTranslatorConfiguration($config['translator'], $container);
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
$this->registerCacheConfiguration($config['cache'], $container, $loader);

if ($this->isConfigEnabled($container, $config['router'])) {
$this->registerRouterConfiguration($config['router'], $container, $loader);
Expand Down Expand Up @@ -1016,6 +1017,28 @@ private function registerPropertyInfoConfiguration(array $config, ContainerBuild
}
}

private function registerCacheConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
$loader->load('cache_pools.xml');

foreach ($config['pools'] as $name => $poolConfig) {
$poolDefinition = new DefinitionDecorator($poolConfig['adapter']);
$poolDefinition->setPublic($poolConfig['public']);
unset($poolConfig['adapter'], $poolConfig['public']);

$poolDefinition->addTag('cache.pool', $poolConfig);
$container->setDefinition('cache.pool.'.$name, $poolDefinition);
}

$this->addClassesToCompile(array(
'Psr\Cache\CacheItemInterface',
'Psr\Cache\CacheItemPoolInterface',
'Symfony\Component\Cache\Adapter\AdapterInterface',
'Symfony\Component\Cache\Adapter\AbstractAdapter',
'Symfony\Component\Cache\CacheItem',
));
}

/**
* Gets a hash of the kernel root directory.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Expand Up @@ -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\ControllerArgumentValueResolverPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
Expand Down Expand Up @@ -89,6 +90,7 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new SerializerPass());
$container->addCompilerPass(new PropertyInfoPass());
$container->addCompilerPass(new ControllerArgumentValueResolverPass());
$container->addCompilerPass(new CachePoolPass());

if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);
Expand Down
@@ -0,0 +1,54 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>

<service id="cache.default_pools_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer" public="false">
<tag name="kernel.cache_clearer" />
</service>

<service id="cache.adapter.shared" alias="cache.adapter.filesystem" />
<service id="cache.adapter.local" alias="cache.adapter.filesystem" />

<service id="cache.pool.shared" parent="cache.adapter.shared">
<tag name="cache.pool" clearer="cache.default_pools_clearer" />
</service>

<service id="cache.pool.local" parent="cache.adapter.local">
<tag name="cache.pool" clearer="cache.default_pools_clearer" />
</service>

<service id="cache.adapter.apcu" class="Symfony\Component\Cache\Adapter\ApcuAdapter" abstract="true">
<argument /> <!-- namespace -->
<argument /> <!-- default lifetime -->
</service>

<service id="cache.adapter.doctrine" class="Symfony\Component\Cache\Adapter\DoctrineAdapter" abstract="true">
<argument /> <!-- Doctrine provider service -->
<argument /> <!-- namespace -->
<argument /> <!-- default lifetime -->
</service>

<service id="cache.adapter.filesystem" class="Symfony\Component\Cache\Adapter\FilesystemAdapter" abstract="true">
<argument /> <!-- namespace -->
<argument /> <!-- default lifetime -->
<argument>%kernel.cache_dir%/pools</argument>
</service>

<service id="cache.adapter.psr6" class="Symfony\Component\Cache\Adapter\ProxyAdapter" abstract="true">
<argument /> <!-- PSR-6 provider service -->
<argument /> <!-- namespace -->
<argument /> <!-- default lifetime -->
</service>

<service id="cache.adapter.redis" class="Symfony\Component\Cache\Adapter\RedisAdapter" abstract="true">
<argument /> <!-- Redis connection object -->
<argument /> <!-- namespace -->
<argument /> <!-- default lifetime -->
</service>

</services>
</container>
Expand Up @@ -25,6 +25,7 @@
<xsd:element name="property-access" type="property_access" minOccurs="0" maxOccurs="1" />
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
</xsd:all>

<xsd:attribute name="http-method-override" type="xsd:boolean" />
Expand Down Expand Up @@ -202,4 +203,19 @@
<xsd:complexType name="property_info">
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>

<xsd:complexType name="cache">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="pool" type="cache_pool" />
</xsd:choice>
</xsd:complexType>

<xsd:complexType name="cache_pool">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="adapter" type="xsd:string" />
<xsd:attribute name="public" type="xsd:boolean" />
<xsd:attribute name="default-lifetime" type="xsd:integer" />
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="clearer" type="xsd:string" />
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,85 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
use Symfony\Component\DependencyInjection\Reference;

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.pool');
$container->setDefinition('app.cache_adapter', $adapter);
$container->setAlias('app.cache_adapter_alias', 'app.cache_adapter');
$cachePool = new DefinitionDecorator('app.cache_adapter_alias');
$cachePool->addArgument(null);
$cachePool->addTag('cache.pool');
$container->setDefinition('app.cache_pool', $cachePool);

$this->cachePoolPass->process($container);

$this->assertSame('yRnzIIVLvL', $cachePool->getArgument(0));
}

public function testArgsAreReplaced()
{
$container = new ContainerBuilder();
$cachePool = new Definition();
$cachePool->addTag('cache.pool', array(
'provider' => 'foobar',
'default_lifetime' => 3,
));
$cachePool->addArgument(null);
$cachePool->addArgument(null);
$cachePool->addArgument(null);
$container->setDefinition('app.cache_pool', $cachePool);

$this->cachePoolPass->process($container);

$this->assertInstanceOf(Reference::class, $cachePool->getArgument(0));
$this->assertSame('foobar', (string) $cachePool->getArgument(0));
$this->assertSame('yRnzIIVLvL', $cachePool->getArgument(1));
$this->assertSame(3, $cachePool->getArgument(2));
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid "cache.pool" tag for service "app.cache_pool": accepted attributes are
*/
public function testThrowsExceptionWhenCachePoolTagHasUnknownAttributes()
{
$container = new ContainerBuilder();
$adapter = new Definition();
$adapter->setAbstract(true);
$adapter->addTag('cache.pool');
$container->setDefinition('app.cache_adapter', $adapter);
$cachePool = new DefinitionDecorator('app.cache_adapter');
$cachePool->addTag('cache.pool', array('foobar' => 123));
$container->setDefinition('app.cache_pool', $cachePool);

$this->cachePoolPass->process($container);
}
}
Expand Up @@ -265,6 +265,9 @@ protected static function getBundleDefaultConfig()
'base_urls' => array(),
'packages' => array(),
),
'cache' => array(
'pools' => array(),
),
);
}
}
@@ -0,0 +1,29 @@
<?php

$container->loadFromExtension('framework', array(
'cache' => array(
'pools' => array(
'foo' => array(
'adapter' => 'cache.adapter.apcu',
'default_lifetime' => 30,
),
'bar' => array(
'adapter' => 'cache.adapter.doctrine',
'default_lifetime' => 5,
'provider' => 'app.doctrine_cache_provider',
),
'baz' => array(
'adapter' => 'cache.adapter.filesystem',
'default_lifetime' => 7,
),
'foobar' => array(
'adapter' => 'cache.adapter.psr6',
'default_lifetime' => 10,
'provider' => 'app.cache_pool',
),
'def' => array(
'default_lifetime' => 11,
),
),
),
));

0 comments on commit 0b67fa3

Please sign in to comment.