Skip to content

Commit 2463553

Browse files
authored
Merge pull request #354 from FriendsOfSymfony/rule-matcher
Add rule matchers for cacheable and must invalidate
2 parents 0de349f + 2dc0a5b commit 2463553

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+699
-362
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ Changelog
3232

3333
* Deprecated methods have been removed.
3434

35+
### Rule matcher
36+
37+
* **BC break:** The `match_response` and `additional_cacheable_status`
38+
configuration parameters were removed for individual match rules.
39+
40+
* **BC break:** The second argument of the `RuleMatcher` constructor was changed
41+
to take a `ResponseMatcherInterface`.
42+
43+
* Cacheable status codes are now configured globally
44+
(`cacheable.response.additional_status`).
45+
3546
1.3.7
3647
-----
3748

Resources/doc/features/headers.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ This is an example configuration. For more, see the
4141
-
4242
match:
4343
attributes: { _controller: ^AcmeBundle:Default:.* }
44-
additional_cacheable_status: [400]
4544
headers:
4645
cache_control: { public: true, max_age: 15, s_maxage: 30 }
4746
last_modified: "-1 hour"

Resources/doc/includes/safe.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
1. the HTTP request matches *all* criteria defined under ``match``
22
2. the HTTP request is :term:`safe` (GET or HEAD)
33
3. the HTTP response is considered :term:`cacheable` (override with
4-
:ref:`additional_cacheable_status` and :ref:`match_response`).
4+
:ref:`additional_status`).

Resources/doc/reference/configuration.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ for the bundle.
1515
configuration/user-context
1616
configuration/flash-message
1717
configuration/debug
18+
configuration/cacheable
1819
configuration/match
1920
configuration/test
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
cacheable
2+
=========
3+
4+
``response``
5+
------------
6+
7+
Configure which responses are considered :term:`cacheable`. This bundle will
8+
only set Cache-Control headers, including tags etc., on cacheable responses.
9+
10+
.. _additional_status:
11+
12+
``additional_status``
13+
^^^^^^^^^^^^^^^^^^^^^
14+
15+
**type**: ``array``
16+
17+
Following `RFC 7231`_, by default responses are considered :term:`cacheable`
18+
if they have status code 200, 203, 204, 206, 300, 301, 404, 405, 410, 414 or 501.
19+
You can add status codes to this list by setting ``additional_status``:
20+
21+
.. code-block:: yaml
22+
23+
# app/config/config.yml
24+
fos_http_cache:
25+
cacheable:
26+
response:
27+
additional_status:
28+
- 100
29+
- 500
30+
31+
``expression``
32+
^^^^^^^^^^^^^^
33+
34+
**type**: ``string``
35+
36+
An ExpressionLanguage expression to decide whether the response is considered
37+
cacheable. The expression can access the Response object with the response variable.
38+
39+
.. code-block:: yaml
40+
41+
# app/config/config.yml
42+
fos_http_cache:
43+
cacheable:
44+
response:
45+
expression: "response.getStatusCode() >= 300"
46+
47+
You cannot set both ``expression`` and ``additional_status``.
48+
49+
.. _RFC 7231: https://tools.ietf.org/html/rfc7231#section-6.1

Resources/doc/reference/configuration/headers.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ cache headers even if they are already set:
3434
-
3535
match:
3636
attributes: { _controller: ^Acme\\TestBundle\\Controller\\DefaultController::.* }
37-
additional_cacheable_status: [400]
3837
headers:
3938
cache_control:
4039
public: true

Resources/doc/reference/configuration/match.rst

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -121,50 +121,6 @@ regular expressions.
121121
match:
122122
attributes: { _controller: ^AcmeBundle:Default:.* }
123123
124-
.. _additional_cacheable_status:
125-
126-
``additional_cacheable_status``
127-
-------------------------------
128-
129-
**type**: ``array``
130-
131-
By default, a rule will only match cacheable status codes: 200, 203, 300, 301,
132-
302, 404 and 410 (as described in the `RFC 7231`_).
133-
134-
`additional_cacheable_status` let you define a list of additional HTTP
135-
status codes of the response for which to also apply the rule.
136-
137-
.. code-block:: yaml
138-
139-
match:
140-
additional_cacheable_status: [400, 403]
141-
142-
.. _match_response:
143-
144-
``match_response``
145-
------------------
146-
147-
**type**: ``string``
148-
149-
.. note::
150-
151-
``match_response`` :ref:`requires the ExpressionLanguage component <requirements>`.
152-
153-
An ExpressionLanguage expression to decide whether the response should have
154-
the effect applied. If not set, headers are applied if the request is
155-
:term:`safe`. The expression can access the ``Response`` object with the
156-
``response`` variable. For example, to handle all failed requests, you can do:
157-
158-
.. code-block:: yaml
159-
160-
-
161-
match:
162-
match_response: response.getStatusCode() >= 400
163-
# ...
164-
165-
You cannot set both ``match_response`` and ``additional_cacheable_status``
166-
inside the same rule.
167-
168124
.. _Trusting Proxies: http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html
169125
.. _controllers as services: http://symfony.com/doc/current/cookbook/controller/service.html
170126
.. _RFC 7231: http://tools.ietf.org/html/rfc7231#page-48

Resources/doc/reference/glossary.rst

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ Glossary
44
.. glossary::
55

66
Cacheable
7-
A *response* is considered cacheable when the status code is one of
8-
200, 203, 300, 301, 302, 404, 410. This range of status codes can be
9-
extended with :ref:`additional_cacheable_status` or overridden with
10-
:ref:`match_response`.
7+
According to `RFC 7231`_, a *response* is considered cacheable when its
8+
status code is one of 200, 203, 204, 206, 300, 301, 404, 405, 410, 414
9+
or 501.
1110

1211
Safe
1312
A *request* is safe if its HTTP method is GET or HEAD. Safe methods
1413
only retrieve data and do not change the application state, and
1514
therefore can be served with a response from the cache.
1615

17-
18-
16+
.. _RFC 7231: https://tools.ietf.org/html/rfc7231#section-6.1

src/DependencyInjection/Configuration.php

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ function ($v) {
111111
})
112112
;
113113

114+
$this->addCacheableResponseSection($rootNode);
114115
$this->addCacheControlSection($rootNode);
115116
$this->addProxyClientSection($rootNode);
116117
$this->addCacheManagerSection($rootNode);
@@ -124,6 +125,37 @@ function ($v) {
124125
return $treeBuilder;
125126
}
126127

128+
private function addCacheableResponseSection(ArrayNodeDefinition $rootNode)
129+
{
130+
$rootNode
131+
->children()
132+
->arrayNode('cacheable')
133+
->addDefaultsIfNotSet()
134+
->children()
135+
->arrayNode('response')
136+
->addDefaultsIfNotSet()
137+
->children()
138+
->arrayNode('additional_status')
139+
->prototype('scalar')->end()
140+
->info('Additional response HTTP status codes that will be considered cacheable.')
141+
->end()
142+
->scalarNode('expression')
143+
->defaultNull()
144+
->info('Expression to decide whether response is cacheable.')
145+
->end()
146+
->end()
147+
148+
->validate()
149+
->ifTrue(function ($v) {
150+
return !empty($v['additional_status']) && !empty($v['expression']);
151+
})
152+
->thenInvalid('You may not set both additional_status and expression.')
153+
->end()
154+
->end()
155+
->end()
156+
->end();
157+
}
158+
127159
/**
128160
* Cache header control main section.
129161
*
@@ -223,12 +255,6 @@ private function addMatch(NodeBuilder $rules)
223255
->fixXmlConfig('method')
224256
->fixXmlConfig('ip')
225257
->fixXmlConfig('attribute')
226-
->validate()
227-
->ifTrue(function ($v) {
228-
return !empty($v['additional_cacheable_status']) && !empty($v['match_response']);
229-
})
230-
->thenInvalid('You may not set both additional_cacheable_status and match_response.')
231-
->end()
232258
->validate()
233259
->ifTrue(function ($v) {
234260
return !empty($v['match_response']) && !class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage');
@@ -265,14 +291,6 @@ private function addMatch(NodeBuilder $rules)
265291
->prototype('scalar')->end()
266292
->info('Regular expressions on request attributes.')
267293
->end()
268-
->arrayNode('additional_cacheable_status')
269-
->prototype('scalar')->end()
270-
->info('Additional response HTTP status codes that will match.')
271-
->end()
272-
->scalarNode('match_response')
273-
->defaultNull()
274-
->info('Expression to decide whether response should be matched. Replaces HTTP code check and additional_cacheable_status.')
275-
->end()
276294
->end()
277295
->end()
278296
;

src/DependencyInjection/FOSHttpCacheExtension.php

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use FOS\HttpCache\ProxyClient\HttpDispatcher;
1515
use FOS\HttpCacheBundle\DependencyInjection\Compiler\HashGeneratorPass;
16+
use FOS\HttpCacheBundle\Http\ResponseMatcher\ExpressionResponseMatcher;
1617
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1718
use Symfony\Component\Config\FileLocator;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -53,6 +54,8 @@ public function load(array $configs, ContainerBuilder $container)
5354
$loader->load('cache_control_listener.xml');
5455
}
5556

57+
$this->loadCacheable($container, $config['cacheable']);
58+
5659
if (!empty($config['cache_control'])) {
5760
$this->loadCacheControl($container, $config['cache_control']);
5861
}
@@ -131,6 +134,22 @@ public function load(array $configs, ContainerBuilder $container)
131134
}
132135
}
133136

137+
private function loadCacheable(ContainerBuilder $container, array $config)
138+
{
139+
$definition = $container->getDefinition($this->getAlias().'.response_matcher.cacheable');
140+
141+
// Change CacheableResponseMatcher to ExpressionResponseMatcher
142+
if ($config['response']['expression']) {
143+
$definition->setClass(ExpressionResponseMatcher::class)
144+
->setArguments([$config['response']['expression']]);
145+
} else {
146+
$container->setParameter(
147+
$this->getAlias().'.cacheable.response.additional_status',
148+
$config['response']['additional_status']
149+
);
150+
}
151+
}
152+
134153
/**
135154
* @param ContainerBuilder $container
136155
* @param array $config
@@ -156,44 +175,14 @@ private function parseRuleMatcher(ContainerBuilder $container, array $match)
156175
{
157176
$match['ips'] = (empty($match['ips'])) ? null : $match['ips'];
158177

159-
$requestMatcher = $this->createRequestMatcher(
178+
return $this->createRequestMatcher(
160179
$container,
161180
$match['path'],
162181
$match['host'],
163182
$match['methods'],
164183
$match['ips'],
165184
$match['attributes']
166185
);
167-
168-
$extraCriteria = [];
169-
foreach (['additional_cacheable_status', 'match_response'] as $extra) {
170-
if (isset($match[$extra])) {
171-
$extraCriteria[$extra] = $match[$extra];
172-
}
173-
}
174-
175-
return $this->createRuleMatcher(
176-
$container,
177-
$requestMatcher,
178-
$extraCriteria
179-
);
180-
}
181-
182-
private function createRuleMatcher(ContainerBuilder $container, Reference $requestMatcher, array $extraCriteria)
183-
{
184-
$arguments = [(string) $requestMatcher, $extraCriteria];
185-
$serialized = serialize($arguments);
186-
$id = $this->getAlias().'.rule_matcher.'.md5($serialized).sha1($serialized);
187-
188-
if (!$container->hasDefinition($id)) {
189-
$container
190-
->setDefinition($id, new DefinitionDecorator($this->getAlias().'.rule_matcher'))
191-
->replaceArgument(0, $requestMatcher)
192-
->replaceArgument(1, $extraCriteria)
193-
;
194-
}
195-
196-
return new Reference($id);
197186
}
198187

199188
private function loadUserContext(ContainerBuilder $container, XmlFileLoader $loader, array $config)

src/EventListener/AbstractRuleListener.php

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,41 @@
1111

1212
namespace FOS\HttpCacheBundle\EventListener;
1313

14-
use FOS\HttpCacheBundle\Http\RuleMatcherInterface;
1514
use Symfony\Component\HttpFoundation\Request;
16-
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
1716

1817
abstract class AbstractRuleListener
1918
{
2019
/**
21-
* @var array List of arrays with RuleMatcher, settings array
20+
* @var array List of arrays with RequestMatcher, settings array
2221
*/
2322
private $rulesMap = [];
2423

2524
/**
2625
* Add a rule matcher with a list of header directives to apply if the
2726
* request and response are matched.
2827
*
29-
* @param RuleMatcherInterface $ruleMatcher The headers apply to responses matched by this matcher
30-
* @param array $settings An array of header configuration
28+
* @param RequestMatcherInterface $requestMatcher The headers apply to responses matched by this matcher
29+
* @param array $settings An array of header configuration
3130
*/
3231
public function addRule(
33-
RuleMatcherInterface $ruleMatcher,
32+
RequestMatcherInterface $requestMatcher,
3433
array $settings = []
3534
) {
36-
$this->rulesMap[] = [$ruleMatcher, $settings];
35+
$this->rulesMap[] = [$requestMatcher, $settings];
3736
}
3837

3938
/**
4039
* Return the settings for the current request if any rule matches.
4140
*
42-
* @param Request $request
43-
* @param Response $response
41+
* @param Request $request
4442
*
4543
* @return array|false Settings to apply or false if no rule matched
4644
*/
47-
protected function matchRule(Request $request, Response $response)
45+
protected function matchRule(Request $request)
4846
{
4947
foreach ($this->rulesMap as $elements) {
50-
if ($elements[0]->matches($request, $response)) {
48+
if ($elements[0]->matches($request)) {
5149
return $elements[1];
5250
}
5351
}

0 commit comments

Comments
 (0)