Completed
Pull Request — master (#331)
by David
06:07 queued 02:30
created

InvalidationListener::getExpressionLanguage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
crap 2
1
<?php
2
3
/*
4
 * This file is part of the FOSHttpCacheBundle package.
5
 *
6
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace FOS\HttpCacheBundle\EventListener;
13
14
use FOS\HttpCache\Exception\ExceptionCollection;
15
use FOS\HttpCacheBundle\CacheManager;
16
use FOS\HttpCacheBundle\Configuration\InvalidatePath;
17
use FOS\HttpCacheBundle\Configuration\InvalidateRoute;
18
use Symfony\Component\Console\ConsoleEvents;
19
use Symfony\Component\Console\Event\ConsoleEvent;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
22
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\HttpKernel\KernelEvents;
26
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
27
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
28
29
/**
30
 * On kernel.terminate event, this event handler invalidates routes for the
31
 * current request and flushes the CacheManager.
32
 *
33
 * @author David de Boer <[email protected]>
34
 */
35
class InvalidationListener extends AbstractRuleListener implements EventSubscriberInterface
36
{
37
    /**
38
     * Cache manager.
39
     *
40
     * @var CacheManager
41
     */
42
    private $cacheManager;
43
44
    /**
45
     * Router.
46
     *
47
     * @var UrlGeneratorInterface
48
     */
49
    private $urlGenerator;
50
51
    /**
52
     * Router.
53
     *
54
     * @var ExpressionLanguage|null
55
     */
56
    private $expressionLanguage;
57
58
    /**
59
     * Constructor.
60
     *
61
     * @param CacheManager            $cacheManager
62
     * @param UrlGeneratorInterface   $urlGenerator
63
     * @param ExpressionLanguage|null $expressionLanguage
64
     */
65 23
    public function __construct(
66
        CacheManager $cacheManager,
67
        UrlGeneratorInterface $urlGenerator,
68
        ExpressionLanguage $expressionLanguage = null
69
    ) {
70 23
        $this->cacheManager = $cacheManager;
71 23
        $this->urlGenerator = $urlGenerator;
72 23
        $this->expressionLanguage = $expressionLanguage ?: new ExpressionLanguage();
73 23
    }
74
75
    /**
76
     * Apply invalidators and flush cache manager.
77
     *
78
     * On kernel.terminate:
79
     * - see if any invalidators apply to the current request and, if so, add
80
     *   their routes to the cache manager;
81
     * - flush the cache manager in order to send invalidation requests to the
82
     *   HTTP cache.
83
     *
84
     * @param PostResponseEvent $event
85
     */
86 15
    public function onKernelTerminate(PostResponseEvent $event)
87
    {
88 15
        $request = $event->getRequest();
89 15
        $response = $event->getResponse();
90
91
        // Don't invalidate any caches if the request was unsuccessful
92 15
        if ($response->getStatusCode() >= 200
93 15
            && $response->getStatusCode() < 400
94
        ) {
95 12
            $this->handleInvalidation($request, $response);
96
        }
97
98
        try {
99 15
            $this->cacheManager->flush();
100
        } catch (ExceptionCollection $e) {
101
            // swallow exception
102
            // there is the fos_http_cache.event_listener.log to log them
103
        }
104 15
    }
105
106
    /**
107
     * Flush cache manager when kernel exception occurs.
108
     */
109 1
    public function onKernelException()
110
    {
111
        try {
112 1
            $this->cacheManager->flush();
113
        } catch (ExceptionCollection $e) {
114
            // swallow exception
115
            // there is the fos_http_cache.event_listener.log to log them
116
        }
117 1
    }
118
119
    /**
120
     * Flush cache manager when console terminates or errors.
121
     *
122
     * @throws ExceptionCollection If an exception occurs during flush
123
     */
124 8
    public function onConsoleTerminate(ConsoleEvent $event)
125
    {
126 8
        $num = $this->cacheManager->flush();
127
128 8
        if ($num > 0 && OutputInterface::VERBOSITY_VERBOSE <= $event->getOutput()->getVerbosity()) {
129 4
            $event->getOutput()->writeln(sprintf('Sent %d invalidation request(s)', $num));
130
        }
131 8
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 23
    public static function getSubscribedEvents()
137
    {
138
        return [
139 23
            KernelEvents::TERMINATE => 'onKernelTerminate',
140 23
            KernelEvents::EXCEPTION => 'onKernelException',
141 23
            ConsoleEvents::TERMINATE => 'onConsoleTerminate',
142 23
            ConsoleEvents::EXCEPTION => 'onConsoleTerminate',
143
        ];
144
    }
145
146
    /**
147
     * Handle the invalidation annotations and configured invalidators.
148
     *
149
     * @param Request  $request
150
     * @param Response $response
151
     */
152 12
    private function handleInvalidation(Request $request, Response $response)
153
    {
154
        // Check controller annotations
155 12
        if ($paths = $request->attributes->get('_invalidate_path')) {
156 3
            $this->invalidatePaths($paths);
157
        }
158
159 12
        if ($routes = $request->attributes->get('_invalidate_route')) {
160 1
            $this->invalidateRoutes($routes, $request);
161
        }
162
163
        // Check configured invalidators
164 12
        if (!$invalidatorConfigs = $this->matchRule($request, $response)) {
165 9
            return;
166
        }
167
168 3
        $requestParams = $request->attributes->get('_route_params');
169 3
        foreach ($invalidatorConfigs as $route => $config) {
170 3
            $path = $this->urlGenerator->generate($route, $requestParams);
171
            // If extra route parameters should be ignored, strip the query
172
            // string generated by the Symfony router from the path
173 3
            if (isset($config['ignore_extra_params'])
174 3
                && $config['ignore_extra_params']
175 3
                && $pos = strpos($path, '?')
176
            ) {
177
                $path = substr($path, 0, $pos);
178
            }
179
180 3
            $this->cacheManager->invalidatePath($path);
181
        }
182 3
    }
183
184
    /**
185
     * Invalidate paths from annotations.
186
     *
187
     * @param array|InvalidatePath[] $pathConfigurations
188
     */
189 3
    private function invalidatePaths(array $pathConfigurations)
190
    {
191 3
        foreach ($pathConfigurations as $pathConfiguration) {
192 3
            foreach ($pathConfiguration->getPaths() as $path) {
193 3
                $this->cacheManager->invalidatePath($path);
194
            }
195
        }
196 3
    }
197
198
    /**
199
     * Invalidate routes from annotations.
200
     *
201
     * @param array|InvalidateRoute[] $routes
202
     * @param Request                 $request
203
     */
204 1
    private function invalidateRoutes(array $routes, Request $request)
205
    {
206 1
        $values = $request->attributes->all();
207
        // if there is an attribute called "request", it needs to be accessed through the request.
208 1
        $values['request'] = $request;
209
210 1
        foreach ($routes as $route) {
211 1
            $params = [];
212
213 1
            if (null !== $route->getParams()) {
214
                // Iterate over route params and try to evaluate their values
215 1
                foreach ($route->getParams() as $key => $value) {
216 1
                    if (is_array($value)) {
217 1
                        $value = $this->expressionLanguage->evaluate($value['expression'], $values);
218
                    }
219
220 1
                    $params[$key] = $value;
221
                }
222
            }
223
224 1
            $this->cacheManager->invalidateRoute($route->getName(), $params);
225
        }
226 1
    }
227
}
228