Completed
Push — master ( 9a04b6...20ed72 )
by David
13s
created

src/EventListener/InvalidationListener.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 FOS\HttpCacheBundle\Http\RuleMatcherInterface;
19
use Symfony\Component\Console\ConsoleEvents;
20
use Symfony\Component\Console\Event\ConsoleEvent;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
23
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
27
use Symfony\Component\HttpKernel\KernelEvents;
28
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
29
30
/**
31
 * On kernel.terminate event, this event handler invalidates routes for the
32
 * current request and flushes the CacheManager.
33
 *
34
 * @author David de Boer <[email protected]>
35
 */
36
class InvalidationListener extends AbstractRuleListener implements EventSubscriberInterface
37
{
38
    /**
39
     * Cache manager.
40
     *
41
     * @var CacheManager
42
     */
43
    private $cacheManager;
44
45
    /**
46
     * Router.
47
     *
48
     * @var UrlGeneratorInterface
49
     */
50
    private $urlGenerator;
51
52
    /**
53
     * Router.
54
     *
55
     * @var ExpressionLanguage|null
56
     */
57
    private $expressionLanguage;
58
59
    /**
60
     * @var RuleMatcherInterface
61
     */
62
    private $mustInvalidateRule;
63
64
    /**
65
     * Constructor.
66
     *
67
     * @param CacheManager            $cacheManager
68
     * @param UrlGeneratorInterface   $urlGenerator
69
     * @param ExpressionLanguage|null $expressionLanguage
70
     */
71 29 View Code Duplication
    public function __construct(
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
72
        CacheManager $cacheManager,
73
        UrlGeneratorInterface $urlGenerator,
74
        RuleMatcherInterface $mustInvalidateRule,
75
        ExpressionLanguage $expressionLanguage = null
76
    ) {
77 29
        $this->cacheManager = $cacheManager;
78 29
        $this->urlGenerator = $urlGenerator;
79 29
        $this->expressionLanguage = $expressionLanguage ?: new ExpressionLanguage();
80 29
        $this->mustInvalidateRule = $mustInvalidateRule;
81 29
    }
82
83
    /**
84
     * Apply invalidators and flush cache manager.
85
     *
86
     * On kernel.terminate:
87
     * - see if any invalidators apply to the current request and, if so, add
88
     *   their routes to the cache manager;
89
     * - flush the cache manager in order to send invalidation requests to the
90
     *   HTTP cache.
91
     *
92
     * @param PostResponseEvent $event
93
     */
94 19
    public function onKernelTerminate(PostResponseEvent $event)
95
    {
96 19
        $request = $event->getRequest();
97 19
        $response = $event->getResponse();
98
99
        // Don't invalidate any caches if the request was unsuccessful
100 19
        if ($this->mustInvalidateRule->matches($request, $response)) {
101 10
            $this->handleInvalidation($request, $response);
102
        }
103
104
        try {
105 19
            $this->cacheManager->flush();
106
        } catch (ExceptionCollection $e) {
107
            // swallow exception
108
            // there is the fos_http_cache.event_listener.log to log them
109
        }
110 19
    }
111
112
    /**
113
     * Flush cache manager when kernel exception occurs.
114
     */
115 1
    public function onKernelException()
116
    {
117
        try {
118 1
            $this->cacheManager->flush();
119
        } catch (ExceptionCollection $e) {
120
            // swallow exception
121
            // there is the fos_http_cache.event_listener.log to log them
122
        }
123 1
    }
124
125
    /**
126
     * Flush cache manager when console terminates or errors.
127
     *
128
     * @throws ExceptionCollection If an exception occurs during flush
129
     */
130 9
    public function onConsoleTerminate(ConsoleEvent $event)
131
    {
132 9
        $num = $this->cacheManager->flush();
133
134 9
        if ($num > 0 && OutputInterface::VERBOSITY_VERBOSE <= $event->getOutput()->getVerbosity()) {
135 5
            $event->getOutput()->writeln(sprintf('Sent %d invalidation request(s)', $num));
136
        }
137 9
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 23
    public static function getSubscribedEvents()
143
    {
144
        return [
145 23
            KernelEvents::TERMINATE => 'onKernelTerminate',
146 23
            KernelEvents::EXCEPTION => 'onKernelException',
147 23
            ConsoleEvents::TERMINATE => 'onConsoleTerminate',
148 23
            ConsoleEvents::EXCEPTION => 'onConsoleTerminate',
0 ignored issues
show
Deprecated Code introduced by
The constant Symfony\Component\Console\ConsoleEvents::EXCEPTION has been deprecated with message: The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
149
        ];
150
    }
151
152
    /**
153
     * Handle the invalidation annotations and configured invalidators.
154
     *
155
     * @param Request  $request
156
     * @param Response $response
157
     */
158 10
    private function handleInvalidation(Request $request, Response $response)
159
    {
160
        // Check controller annotations
161 10
        if ($paths = $request->attributes->get('_invalidate_path')) {
162 4
            $this->invalidatePaths($paths);
163
        }
164
165 10
        if ($routes = $request->attributes->get('_invalidate_route')) {
166 2
            $this->invalidateRoutes($routes, $request);
167
        }
168
169
        // Check configured invalidators
170 10
        if (!$invalidatorConfigs = $this->matchRule($request, $response)) {
171 6
            return;
172
        }
173
174 4
        $requestParams = $request->attributes->get('_route_params');
175 4
        foreach ($invalidatorConfigs as $route => $config) {
176 4
            $path = $this->urlGenerator->generate($route, $requestParams);
177
            // If extra route parameters should be ignored, strip the query
178
            // string generated by the Symfony router from the path
179 4
            if (isset($config['ignore_extra_params'])
180 4
                && $config['ignore_extra_params']
181 4
                && $pos = strpos($path, '?')
182
            ) {
183 1
                $path = substr($path, 0, $pos);
184
            }
185
186 4
            $this->cacheManager->invalidatePath($path);
187
        }
188 4
    }
189
190
    /**
191
     * Invalidate paths from annotations.
192
     *
193
     * @param array|InvalidatePath[] $pathConfigurations
194
     */
195 4
    private function invalidatePaths(array $pathConfigurations)
196
    {
197 4
        foreach ($pathConfigurations as $pathConfiguration) {
198 4
            foreach ($pathConfiguration->getPaths() as $path) {
199 4
                $this->cacheManager->invalidatePath($path);
200
            }
201
        }
202 4
    }
203
204
    /**
205
     * Invalidate routes from annotations.
206
     *
207
     * @param array|InvalidateRoute[] $routes
208
     * @param Request                 $request
209
     */
210 2
    private function invalidateRoutes(array $routes, Request $request)
211
    {
212 2
        $values = $request->attributes->all();
213
        // if there is an attribute called "request", it needs to be accessed through the request.
214 2
        $values['request'] = $request;
215
216 2
        foreach ($routes as $route) {
217 2
            $params = [];
218
219 2
            if (null !== $route->getParams()) {
220
                // Iterate over route params and try to evaluate their values
221 2
                foreach ($route->getParams() as $key => $value) {
222 2
                    if (is_array($value)) {
223 2
                        $value = $this->expressionLanguage->evaluate($value['expression'], $values);
224
                    }
225
226 2
                    $params[$key] = $value;
227
                }
228
            }
229
230 2
            $this->cacheManager->invalidateRoute($route->getName(), $params);
231
        }
232 2
    }
233
}
234