diff --git a/src/Symfony/Bundle/CacheBundle/CacheBundle.php b/src/Symfony/Bundle/CacheBundle/CacheBundle.php new file mode 100644 index 0000000000000..b89fb2ee57942 --- /dev/null +++ b/src/Symfony/Bundle/CacheBundle/CacheBundle.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\CacheBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +/** + * Bundle. + * + * @author Florin Patan + */ +class CacheBundle extends Bundle +{ + +} diff --git a/src/Symfony/Bundle/CacheBundle/DataCollector/CacheDataCollector.php b/src/Symfony/Bundle/CacheBundle/DataCollector/CacheDataCollector.php new file mode 100644 index 0000000000000..57a5ec27bec42 --- /dev/null +++ b/src/Symfony/Bundle/CacheBundle/DataCollector/CacheDataCollector.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\CacheBundle\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\Cache\CacheProfiler; + +/** + * CacheDataCollector. + * + * @author Florin Patan + */ +class CacheDataCollector extends DataCollector +{ + + private $cacheProfiler; + + public function __construct(CacheProfiler $cacheProfiler) + { + $this->cacheProfiler = $cacheProfiler; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = $this->cacheProfiler->getResults(); + } + + public function getTotalHits() + { + return $this->data['hits']; + } + + public function getTotalMisses() + { + return $this->data['ops'] - $this->data['hits']; + } + + public function getTotalOps() + { + return $this->data['ops']; + } + + public function getTotalTime() + { + return $this->data['time']; + } + + public function getDrivers() + { + return $this->data['drivers']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'cache'; + } +} diff --git a/src/Symfony/Bundle/CacheBundle/DependencyInjection/CacheExtension.php b/src/Symfony/Bundle/CacheBundle/DependencyInjection/CacheExtension.php new file mode 100644 index 0000000000000..274078420e52f --- /dev/null +++ b/src/Symfony/Bundle/CacheBundle/DependencyInjection/CacheExtension.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\CacheBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Config\FileLocator; + +/** + * Cache extension + * + * @author Florin Patan + */ +class CacheExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('cache.xml'); + + $container->setParameter('cache.driver.class', $config['driver']['class']); + + // Configure cache drivers + foreach ($config['drivers'] as $type => $driver) { + if (!$driver['enabled']) { + continue; + } + + $container->setParameter(sprintf('cache.drivers.%s.class', $type), $driver['class']); + $container->setParameter(sprintf('cache.drivers.%s.ttl', $type), $driver['config']['ttl']); + + $cacheDriverDefinition = new Definition(); + $cacheDriverDefinition + ->setPublic(false) + ->setClass(sprintf('%%cache.drivers.%s.class%%', $type)) + ; + + if (isset($driver['instance'])) { + $container->setParameter(sprintf('cache.internal.drivers.%s.instance', $type), $driver['instance']); + + $internalDriverDefinition = new Definition(); + $internalDriverDefinition + ->setPublic(false) + ->setClass(sprintf('%%cache.internal.drivers.%s.instance%%', $type)) + ; + + if (isset($driver['servers']) && !empty($driver['servers'])) { + $container->setParameter(sprintf('cache.internal.drivers.%s.servers', $type), $driver['servers']); + + $internalDriverDefinition->addMethodCall('addServers', array(sprintf('%%cache.internal.drivers.%s.servers%%', $type))); + } + + $container->setDefinition('cache.internal.driver.' . $type, $internalDriverDefinition); + } + + if (isset($driver['service'])) { + $cacheDriverDefinition->addArgument(new Reference($driver['service'])); + } elseif(isset($driver['instance'])) { + $cacheDriverDefinition->addArgument(new Reference(sprintf('cache.internal.driver.%s', $type))); + } + + $container->setDefinition('cache.driver.' . $type, $cacheDriverDefinition); + } + + + // Configure cache instances + foreach ($config['instances'] as $name => $instanceConfig) { + + $instanceType = $instanceConfig['type']; + $driverInstance = $config['drivers'][$instanceType]; + + if (!$driverInstance['enabled']) { + continue; + } + + $instanceConfig['config'] = array_merge($driverInstance['config'], $instanceConfig['config']); + $instanceConfig = array_merge($driverInstance, $instanceConfig); + + if (isset($instanceConfig['service'])) { + unset($instanceConfig['servers'], $instanceConfig['instance']); + } else { + unset($instanceConfig['service']); + } + + if (isset($instanceConfig['servers']) && isset($driverInstance['servers'])) { + $instanceConfig['servers'] = array_merge($driverInstance['servers'], $instanceConfig['servers']); + } + + if (!empty($instanceConfig['servers']) && isset($instanceConfig['instance'])) { + $container->setParameter(sprintf('cache.internal.instances.%s.class', $name), $instanceConfig['instance']); + + $internalDriverDefinition = new Definition(); + $internalDriverDefinition + ->setPublic(false) + ->setClass(sprintf('%%cache.internal.instances.%s.class%%', $name)) + ; + + $container->setParameter(sprintf('cache.internal.instances.%s.servers', $name), $instanceConfig['servers']); + + $internalDriverDefinition->addMethodCall('addServers', array(sprintf('%%cache.internal.instances.%s.servers%%', $name))); + + $internalCacheDriverName = sprintf('cache.internal.driver.%s_%s', $instanceConfig['type'], $name); + $container->setDefinition($internalCacheDriverName, $internalDriverDefinition); + } elseif (isset($instanceConfig['service'])) { + $internalCacheDriverName = $instanceConfig['service']; + } else { + $internalCacheDriverName = sprintf('cache.driver.%s', $instanceConfig['type']); + } + + $container->setParameter(sprintf('cache.instances.%s.instance.class', $name), $instanceConfig['class']); + $container->setParameter(sprintf('cache.instances.%s.instance.config.ttl', $name), $instanceConfig['config']['ttl']); + + $internalInstanceDefinition = new Definition(); + $internalInstanceDefinition + ->setPublic(false) + ->setClass(sprintf('%%cache.instances.%s.instance.class%%', $name)) + ->addArgument(new Reference($internalCacheDriverName)) + ; + + $internalInstanceName = sprintf('cache.instance.%s_%s', $instanceType, $name); + + $container->setDefinition($internalInstanceName, $internalInstanceDefinition); + + $container->setParameter(sprintf('cache.instances.%s.config.ttl', $name), $instanceConfig['config']['ttl']); + + $cacheInstanceDefinition = new Definition(); + $cacheInstanceDefinition + ->setClass('%cache.driver.class%') + ->addMethodCall('setDefaultTtl', array(sprintf('%%cache.instances.%s.config.ttl%%', $name))) + ->addMethodCall('setProfiler', array(new Reference('cache.profiler'))) + ->addMethodCall('setLogger', array(new Reference('logger'))) + ->addArgument(new Reference($internalInstanceName)) + ->addArgument($name) + ->addArgument($instanceConfig['type']) + ; + + + $container->setDefinition('cache.' . $name, $cacheInstanceDefinition); + } + } +} diff --git a/src/Symfony/Bundle/CacheBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/CacheBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000000..658aa006a644e --- /dev/null +++ b/src/Symfony/Bundle/CacheBundle/DependencyInjection/Configuration.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\CacheBundle\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +/** + * Cache extension configuration structure. + * + * @author Florin Patan + */ +class Configuration implements ConfigurationInterface +{ + + /** + * Generates the configuration tree builder. + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('cache'); + + $rootNode + ->children() + ->arrayNode('driver') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('class') + ->info('This holds our cache driver class name') + ->defaultValue('Symfony\\Component\\Cache\\CacheDriver') + ->end() + ->end() + ->end() + ->arrayNode('drivers') + ->cannotBeEmpty() + ->isRequired() + ->info('This holds cache drivers definitions') + ->append($this->addApcNode()) + ->append($this->addMemcachedNode()) + ->end() + ->append($this->addCacheInstances()) + ->end() + ; + + return $treeBuilder; + } + + /** + * Add APC driver definition section + * + * @return \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition + */ + private function addApcNode() + { + $node = new TreeBuilder(); + $node = $node->root('apc'); + + $node + ->children() + ->booleanNode('enabled') + ->defaultTrue() + ->info('Enable APC driver') + ->end() + ->scalarNode('class') + ->defaultValue('Symfony\\Component\\Cache\\Driver\\Apc') + ->info('The APC cache driver class') + ->end() + ->arrayNode('config') + ->info('Configuration for APC cache driver, applies to all instances unless specified in the instance') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('ttl') + ->info('Default TTL for APC entries') + ->defaultValue(600) + ->end() + ->end() + ->end() + ->end(); + + return $node; + } + + /** + * Add Memcached driver definition section + * + * @return \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition + */ + private function addMemcachedNode() + { + $node = new TreeBuilder(); + $node = $node->root('memcached'); + + $node + ->fixXmlConfig('server', 'servers') + ->children() + ->booleanNode('enabled') + ->defaultTrue() + ->info('Enable Memcached driver') + ->end() + ->scalarNode('class') + ->defaultValue('Symfony\\Component\\Cache\\Driver\\Memcached') + ->info('The Memcached cache driver class') + ->end() + ->scalarNode('instance') + ->defaultValue('Memcached') + ->info('The Memcached class to be instantiated if there is no service specified') + ->end() + ->scalarNode('service') + ->defaultValue(null) + ->info('If you specify a service name here, it will be used as a connection to memcached and it must be an instance of \Memcached') + ->end() + ->arrayNode('servers') + ->info('The memcache driver requires at least one server to be configured if there is no service specified') + ->example(array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100)) + ->requiresAtLeastOneElement() + ->prototype('array') + ->children() + ->scalarNode('host') + ->isRequired() + ->end() + ->scalarNode('port') + ->defaultValue(11211) + ->end() + ->scalarNode('weight') + ->defaultValue(100) + ->end() + ->end() + ->end() + ->end() + ->arrayNode('config') + ->info('Configuration for Memcached cache driver, applies to all instances unless specified in the instance') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('ttl') + ->info('Default TTL for Memcached entries') + ->defaultValue(600) + ->end() + ->end() + ->end() + ->end(); + + return $node; + } + + /** + * Add driver instances + * + * @return \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition + */ + private function addCacheInstances() + { + $node = new TreeBuilder(); + $node = $node->root('instances'); + + $node + ->isRequired() + ->requiresAtLeastOneElement() + ->prototype('array') + ->fixXmlConfig('server', 'servers') + ->children() + ->scalarNode('type') + ->info('Type of instance') + ->example('type: apc') + ->end() + ->booleanNode('enabled') + ->info('Is this driver instance enabled or not') + ->defaultTrue() + ->end() + ->scalarNode('service') + ->info('If specified, this service will be injected to the cache driver that supports it at construction time, else the default instance will be used, see memcached for example') + ->example('service: doctrine.common.cache.instance') + ->end() + ->arrayNode('config') + ->info('Configuration information particular to this driver instance') + ->beforeNormalization() + ->always() + ->then(function ($options) { return $options; }) + ->end() + ->prototype('variable')->end() + ->end() + ->arrayNode('servers') + ->info('Server configuration particular to this driver instance') + ->beforeNormalization() + ->always() + ->then(function ($options) { return $options; }) + ->end() + ->prototype('variable')->end() + ->end() + ->end() + ->end() + ->end(); + + return $node; + } + +} diff --git a/src/Symfony/Bundle/CacheBundle/Factory/CacheFactory.php b/src/Symfony/Bundle/CacheBundle/Factory/CacheFactory.php new file mode 100644 index 0000000000000..bf094824b2f49 --- /dev/null +++ b/src/Symfony/Bundle/CacheBundle/Factory/CacheFactory.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Symfony\Bundle\CacheBundle\Factory; + +/** + * This class takes care of creating the cache drivers and instances + * + * @author Florin Patan + */ +class CacheFactory +{ + /** + * This is the cache manager + * + * @var \Symfony\Component\Cache\Cache + */ + private $manager; + + /** + * Set the cache manager + * + * @param \Symfony\Component\Cache\Cache $cacheManager + * + * @return \Symfony\Bundle\CacheBundle\Factory\CacheFactory + */ + public function setCacheManager($cacheManager) + { + $this->manager = $cacheManager; + + return $this; + } + + /** + * Add the cache instances + * + * @param array $instances + * + * @return CacheFactory + */ + public function addCacheInstances($instances) + { + $this->manager->addDriverInstances($instances); + + return $this; + } + + /** + * Get the cache driver + * + * @param string $cacheName + * + * @return \Symfony\Component\Cache\Drivers\CacheDriverInterface + * + * @throws \RuntimeException When invalid caching instance is not specified + */ + public function get($cacheName) + { + // Fetch our instance + return $this->manager->getDriverInstance($cacheName); + } +} diff --git a/src/Symfony/Bundle/CacheBundle/Resources/config/cache.xml b/src/Symfony/Bundle/CacheBundle/Resources/config/cache.xml new file mode 100644 index 0000000000000..55e5b276c50d7 --- /dev/null +++ b/src/Symfony/Bundle/CacheBundle/Resources/config/cache.xml @@ -0,0 +1,25 @@ + + + + + + true + Symfony\Component\Cache\CacheProfiler + Symfony\Bundle\CacheBundle\DataCollector\CacheDataCollector + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/CacheBundle/Resources/views/Collector/cache.html.twig b/src/Symfony/Bundle/CacheBundle/Resources/views/Collector/cache.html.twig new file mode 100644 index 0000000000000..f606b9a7e46a1 --- /dev/null +++ b/src/Symfony/Bundle/CacheBundle/Resources/views/Collector/cache.html.twig @@ -0,0 +1,98 @@ +{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %} + +{% block toolbar %} + {% set icon %} + cache + Cache + {{ collector.totalHits }} hits + {{ collector.totalMisses }} misses + {{ collector.totalTime }} ms + {% endset %} + {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %} +{% endblock %} + +{% block menu %} + + + Cache + + Cache + + + {{ collector.totalHits }} + {{ collector.totalMisses }} + {{ collector.totalTime }} ms + + + +{% endblock %} + +{% block panel %} +

Cache information

+
Hits: {{ collector.totalHits }} Misses: {{ collector.totalMisses }} Operations: {{ collector.totalOps }} Time: {{ collector.totalTime }}ms
+ + {% for driverType, driverInfo in collector.drivers %} +

Driver type {{ driverType }}

+ +
+ {% for driverName, driverData in driverInfo %} +

Driver name {{ driverName }}

+ + {% set driverOps = 0 %} + {% set driverHits = 0 %} + {% set driverTime = 0 %} + + + + + + + {% for operation, operationInfo in driverData %} + + + + + {% endfor %} +
OperationData
{{ operation }} + + {% set instanceOps = 0 %} + {% set instanceHits = 0 %} + {% set instanceTime = 0 %} + + + + + + + + {% for key, info in operationInfo %} + {% set driverOps = driverOps + 1 %} + {% set driverTime = driverTime + info['duration'] %} + + {% set instanceOps = instanceOps + 1 %} + {% set instanceTime = instanceTime + info['duration'] %} + + + + + + + {% endfor %} +
KeyTimeSuccess
{{ key }}{{ info['duration'] }} + {% if info['result'] %} + {% set driverHits = driverHits + 1 %} + {% set instanceHits = instanceHits + 1 %} + YES + {% else %} + NO + {% endif %}
+ + Total hits: {{ instanceHits }} Total misses {{ instanceOps - instanceHits }} Total operations {{ instanceOps }} Total time: {{ instanceTime }}ms +
+ + Total hits: {{ driverHits }} Total misses {{ driverOps - driverHits }} Total operations {{ driverOps }} Total time: {{ driverTime }}ms + {% endfor %} +
+ + {% endfor %} +{% endblock %} \ No newline at end of file