Skip to content

Commit

Permalink
Merge pull request #67 from FriendsOfSymfony/configure-tags
Browse files Browse the repository at this point in the history
Also have configuration rules for tags
  • Loading branch information
ddeboer committed Jun 6, 2014
2 parents 8d78768 + 27ec9ef commit 691876a
Show file tree
Hide file tree
Showing 16 changed files with 431 additions and 174 deletions.
79 changes: 70 additions & 9 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;

/**
* This is the class that validates and merges configuration from your app/config files
* This class contains the configuration information for the bundle
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
* This information is solely responsible for how the different configuration
* sections are normalized, and merged.
*
* @author David de Boer <[email protected]>
* @author David Buchmann <[email protected]>
*/
class Configuration implements ConfigurationInterface
{
Expand All @@ -24,6 +28,18 @@ public function getConfigTreeBuilder()
$rootNode = $treeBuilder->root('fos_http_cache');

$rootNode
->validate()
->ifTrue(function ($v) {return $v['cache_manager']['enabled'] && !isset($v['proxy_client']);})
->then(function ($v) {
if ('auto' === $v['cache_manager']['enabled']) {
$v['cache_manager']['enabled'] = false;

return $v;
}
throw new InvalidConfigurationException('You need to configure a proxy_client to use the cache_manager.');
})
->end()

->children()
->booleanNode('debug')
->defaultValue('%kernel.debug%')
Expand All @@ -39,9 +55,8 @@ public function getConfigTreeBuilder()
$this->addUserContextListenerSection($rootNode);
$this->addRulesSection($rootNode);
$this->addProxyClientSection($rootNode);
$this->addTagListenerSection($rootNode);
$this->addCacheManager($rootNode);
$this->addFlashMessageListenerSection($rootNode);
$this->addInvalidatorsSection($rootNode);

return $treeBuilder;
}
Expand Down Expand Up @@ -106,6 +121,21 @@ private function addRulesSection(ArrayNodeDefinition $rootNode)

$this->addMatchSection($rules);
$this->addHeaderSection($rules);
$this->addTagSection($rules);
}

private function addTagSection(NodeBuilder $rules)
{
$rules
->arrayNode('tags')
->prototype('scalar')
->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
->end()->end()
->arrayNode('tag_expressions')
->prototype('scalar')
->info('Tags to add to the response on safe requests, to invalidate on unsafe requests')
->end()
;
}

private function addMatchSection(NodeBuilder $rules)
Expand Down Expand Up @@ -221,9 +251,37 @@ private function addProxyClientSection(ArrayNodeDefinition $rootNode)
->end();
}

private function addTagListenerSection(ArrayNodeDefinition $rootNode)
private function addCacheManager(ArrayNodeDefinition $rootNode)
{
$rootNode
$invalidationNode = $rootNode
->children()
->arrayNode('cache_manager')
->addDefaultsIfNotSet()
->beforeNormalization()
->ifArray()
->then(function ($v) {
$v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;

return $v;
})
->end()
->info('Configure the cache manager. Needs a proxy_client to be configured.')
->children()
->enumNode('enabled')
->values(array(true, false, 'auto'))
->defaultValue('auto')
->info('Allows to disable the invalidation manager. Enabled by default if you configure a proxy client.')
->end()
->end()
;

$this->addTagListenerSection($invalidationNode);
$this->addInvalidatorsSection($invalidationNode);
}

private function addTagListenerSection(ArrayNodeDefinition $invalidationNode)
{
$invalidationNode
->children()
->arrayNode('tag_listener')
->addDefaultsIfNotSet()
Expand Down Expand Up @@ -272,21 +330,24 @@ private function addFlashMessageListenerSection(ArrayNodeDefinition $rootNode)
->end();
}

private function addInvalidatorsSection(ArrayNodeDefinition $rootNode)
private function addInvalidatorsSection(ArrayNodeDefinition $invalidationNode)
{
$rootNode
$invalidationNode
->children()
->arrayNode('invalidators')
->arrayNode('route_invalidators')
->useAttributeAsKey('name')
->info('Groups of origin routes that invalidate target routes when a request is made')
->prototype('array')
->children()
->arrayNode('origin_routes')
->isRequired()
->requiresAtLeastOneElement()
->prototype('scalar')->end()
->info('Invalidate the target routes in this group when one of these routes is called')
->end()
->arrayNode('invalidate_routes')
->useAttributeAsKey('name')
->info('Target routes to invalidate when an origin route is called')
->prototype('array')
->children()
->scalarNode('parameter_mapper')->end()
Expand Down
96 changes: 61 additions & 35 deletions DependencyInjection/FOSHttpCacheExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,16 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('cache_control_listener.xml');
}

if (!empty($config['rules'])) {
$this->loadRules($container, $config);
}

if (isset($config['proxy_client'])) {
$container->setParameter($this->getAlias().'.invalidators', $config['invalidators']);
$this->loadProxyClient($container, $loader, $config['proxy_client']);
}

$loader->load('cache_manager.xml');

if ($config['tag_listener']['enabled']) {
// true or auto
if (class_exists('\Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
$loader->load('tag_listener.xml');
} elseif (true === $config['tag_listener']['enabled']) {
// silently skip if set to auto
throw new \RuntimeException('The TagListener requires symfony/expression-language');
}
}
if ($config['cache_manager']['enabled'] && isset($config['proxy_client'])) {
$this->loadCacheManager($container, $loader, $config['cache_manager']);
}

if (version_compare(Kernel::VERSION, '2.4.0', '>=')) {
$container
->getDefinition('fos_http_cache.command.invalidate_path')
->addTag('console.command')
;
}
} elseif (!empty($config['invalidators'])) {
throw new InvalidConfigurationException('You need to configure a proxy client to use the invalidators.');
} elseif (true === $config['tag_listener']['enabled']) {
throw new InvalidConfigurationException('You need to configure a proxy client to use the tag listener.');
if (!empty($config['rules'])) {
$this->loadRules($container, $config);
}

if ($config['user_context']['enabled']) {
Expand All @@ -78,14 +58,13 @@ public function load(array $configs, ContainerBuilder $container)

/**
* @param ContainerBuilder $container
* @param $config
* @param array $config
*
* @throws InvalidConfigurationException
*/
protected function loadRules(ContainerBuilder $container, $config)
protected function loadRules(ContainerBuilder $container, array $config)
{
foreach ($config['rules'] as $rule) {
if (!isset($rule['headers'])) {
continue;
}
$match = $rule['match'];

$match['ips'] = (empty($match['ips'])) ? null : $match['ips'];
Expand All @@ -111,11 +90,27 @@ protected function loadRules(ContainerBuilder $container, $config)
$extraCriteria
);

$tags = array(
'tags' => $rule['tags'],
'expressions' => $rule['tag_expressions'],
);
if (count($tags['tags']) || count($tags['expressions'])) {
if (!$container->hasDefinition($this->getAlias() . '.event_listener.tag')) {
throw new InvalidConfigurationException('To configure tags, you need to have the tag event listener enabled, requiring symfony/expression-language');
}

$container
->getDefinition($this->getAlias() . '.event_listener.tag')
->addMethodCall('addRule', array($ruleMatcher, $tags))
;
}

$container
->getDefinition($this->getAlias() . '.event_listener.cache_control')
->addMethodCall('add', array($ruleMatcher, $rule['headers']))
;
if (isset($rule['headers'])) {
$container
->getDefinition($this->getAlias() . '.event_listener.cache_control')
->addMethodCall('addRule', array($ruleMatcher, $rule['headers']))
;
}
}
}

Expand Down Expand Up @@ -209,6 +204,37 @@ protected function loadVarnish(ContainerBuilder $container, XmlFileLoader $loade
$container->setParameter($this->getAlias() . '.proxy_client.varnish.base_url', $config['base_url']);
}

protected function loadCacheManager(ContainerBuilder $container, XmlFileLoader $loader, array $config)
{
$container->setParameter($this->getAlias().'.cache_manager.route_invalidators', $config['route_invalidators']);

$container->setParameter(
$this->getAlias() . '.cache_manager.additional_status',
isset($config['additional_status']) ? $config['additional_status'] : array()
);
$container->setParameter(
$this->getAlias() . '.cache_manager.match_response',
isset($config['match_response']) ? $config['match_response'] : null
);
$loader->load('cache_manager.xml');
if (version_compare(Kernel::VERSION, '2.4.0', '>=')) {
$container
->getDefinition('fos_http_cache.command.invalidate_path')
->addTag('console.command')
;
}

if ($config['tag_listener']['enabled']) {
// true or auto
if (class_exists('\Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
$loader->load('tag_listener.xml');
} elseif (true === $config['tag_listener']['enabled']) {
// silently skip if set to auto
throw new InvalidConfigurationException('The TagListener requires symfony/expression-language');
}
}
}

private function validateUrl($url, $msg)
{
if (false === strpos($url, '://')) {
Expand Down
68 changes: 68 additions & 0 deletions EventListener/AbstractRuleSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace FOS\HttpCacheBundle\EventListener;

use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class AbstractRuleSubscriber
{
/**
* @var array List of arrays with RuleMatcher, settings array.
*/
private $rulesMap = array();

/**
* Add a rule matcher with a list of header directives to apply if the
* request and response are matched.
*
* @param RuleMatcherInterface $ruleMatcher The headers apply to responses matched by this matcher.
* @param array $settings An array of header configuration.
* @param int $priority Optional priority of this matcher. Higher priority is applied first.
*/
public function addRule(
RuleMatcherInterface $ruleMatcher,
array $settings = array(),
$priority = 0
) {
if (!isset($this->rulesMap[$priority])) {
$this->rulesMap[$priority] = array();
}
$this->rulesMap[$priority][] = array($ruleMatcher, $settings);
}
/**
* Return the settings for the current request if any rule matches.
*
* @param Request $request
* @param Response $response
*
* @return array|false Settings to apply or false if no rule matched.
*/
protected function matchConfiguration(Request $request, Response $response)
{
foreach ($this->getRules() as $elements) {
if ($elements[0]->matches($request, $response)) {
return $elements[1];
}
}

return false;
}

/**
* Get the rules ordered by priority.
*
* @return array of array with rule matcher, settings
*/
private function getRules()
{
$sortedRules = array();
krsort($this->rulesMap);
foreach ($this->rulesMap as $rules) {
$sortedRules = array_merge($sortedRules, $rules);
}

return $sortedRules;
}
}
Loading

0 comments on commit 691876a

Please sign in to comment.