Passed
Pull Request — master (#514)
by Fabien
10:39
created

CacheInvalidator   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Test Coverage

Coverage 87.84%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 73
c 1
b 0
f 0
dl 0
loc 293
ccs 65
cts 74
cp 0.8784
rs 9.76
wmc 33

12 Methods

Rating   Name   Duplication   Size   Complexity  
A refreshPath() 0 9 2
A flush() 0 16 5
B supports() 0 20 8
A invalidate() 0 9 2
A invalidateRegex() 0 9 2
A invalidateTags() 0 8 2
A invalidatePath() 0 9 2
A clearCache() 0 9 2
A setEventDispatcher() 0 8 2
A dispatch() 0 9 2
A getEventDispatcher() 0 11 3
A __construct() 0 3 1
1
<?php
2
3
/*
4
 * This file is part of the FOSHttpCache 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\HttpCache;
13
14
use FOS\HttpCache\Exception\ExceptionCollection;
15
use FOS\HttpCache\Exception\InvalidArgumentException;
16
use FOS\HttpCache\Exception\ProxyResponseException;
17
use FOS\HttpCache\Exception\ProxyUnreachableException;
18
use FOS\HttpCache\Exception\UnsupportedProxyOperationException;
19
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
20
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
21
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
22
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
23
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
24
use FOS\HttpCache\ProxyClient\ProxyClient;
25
use FOS\HttpCache\ProxyClient\Symfony;
26
use Symfony\Component\EventDispatcher\EventDispatcher;
27
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
28
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
29
use Toflar\Psr6HttpCacheStore\Psr6Store;
0 ignored issues
show
Bug introduced by
The type Toflar\Psr6HttpCacheStore\Psr6Store was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
31
/**
32
 * Manages HTTP cache invalidation.
33
 *
34
 * @author David de Boer <[email protected]>
35
 * @author David Buchmann <[email protected]>
36
 * @author André Rømcke <[email protected]>
37
 */
38
class CacheInvalidator
39
{
40
    /**
41
     * Value to check support of invalidatePath operation.
42
     */
43
    public const PATH = 'path';
44
45
    /**
46
     * Value to check support of refreshPath operation.
47
     */
48
    public const REFRESH = 'refresh';
49
50
    /**
51
     * Value to check support of invalidate and invalidateRegex operations.
52
     */
53
    public const INVALIDATE = 'invalidate';
54
55
    /**
56
     * Value to check support of invalidateTags operation.
57
     */
58
    public const TAGS = 'tags';
59
60
    /**
61
     * Value to check support of clearCache operation.
62
     */
63
    public const CLEAR = 'clear';
64
65
    /**
66
     * @var ProxyClient
67
     */
68
    private $cache;
69
70
    /**
71
     * @var EventDispatcherInterface
72
     */
73
    private $eventDispatcher;
74
75
    /**
76
     * Constructor.
77
     *
78
     * @param ProxyClient $cache HTTP cache
79
     */
80 17
    public function __construct(ProxyClient $cache)
81
    {
82 17
        $this->cache = $cache;
83 17
    }
84
85
    /**
86
     * Check whether this invalidator instance supports the specified
87
     * operation.
88
     *
89
     * Support for PATH means invalidatePath will work, REFRESH means
90
     * refreshPath works, TAGS means that invalidateTags works and
91
     * INVALIDATE is for the invalidate and invalidateRegex methods.
92
     *
93
     * @param string $operation one of the class constants
94
     *
95
     * @return bool
96
     *
97
     * @throws InvalidArgumentException
98
     */
99 3
    public function supports($operation)
100
    {
101
        switch ($operation) {
102 3
            case self::PATH:
103 2
                return $this->cache instanceof PurgeCapable;
104 3
            case self::REFRESH:
105 2
                return $this->cache instanceof RefreshCapable;
106 3
            case self::INVALIDATE:
107 2
                return $this->cache instanceof BanCapable;
108 3
            case self::TAGS:
109 2
                $supports = $this->cache instanceof TagCapable;
110 2
                if ($supports && $this->cache instanceof Symfony) {
111
                    return class_exists(Psr6Store::class);
112
                }
113
114 2
                return $supports;
115 1
            case self::CLEAR:
116
                return $this->cache instanceof ClearCapable;
117
            default:
118 1
                throw new InvalidArgumentException('Unknown operation '.$operation);
119
        }
120
    }
121
122
    /**
123
     * Set event dispatcher - may only be called once.
124
     *
125
     * @throws \Exception when trying to override the event dispatcher
126
     */
127 2
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
128
    {
129 2
        if ($this->eventDispatcher) {
130
            // if you want to set a custom event dispatcher, do so right after instantiating
131
            // the invalidator.
132 1
            throw new \Exception('You may not change the event dispatcher once it is set.');
133
        }
134 2
        $this->eventDispatcher = $eventDispatcher;
135 2
    }
136
137
    /**
138
     * Get the event dispatcher used by the cache invalidator.
139
     *
140
     * @return EventDispatcherInterface
141
     */
142 2
    public function getEventDispatcher()
143
    {
144 2
        if (!$this->eventDispatcher) {
145 1
            if (class_exists(LegacyEventDispatcherProxy::class)) {
146 1
                $this->eventDispatcher = LegacyEventDispatcherProxy::decorate(new EventDispatcher());
147
            } else {
148
                $this->eventDispatcher = new EventDispatcher();
149
            }
150
        }
151
152 2
        return $this->eventDispatcher;
153
    }
154
155
    /**
156
     * Invalidate a path or URL.
157
     *
158
     * @param string $path    Path or URL
159
     * @param array  $headers HTTP headers (optional)
160
     *
161
     * @throws UnsupportedProxyOperationException
162
     *
163
     * @return $this
164
     */
165 2
    public function invalidatePath($path, array $headers = [])
166
    {
167 2
        if (!$this->cache instanceof PurgeCapable) {
168 1
            throw UnsupportedProxyOperationException::cacheDoesNotImplement('PURGE');
169
        }
170
171 1
        $this->cache->purge($path, $headers);
172
173 1
        return $this;
174
    }
175
176
    /**
177
     * Refresh a path or URL.
178
     *
179
     * @param string $path    Path or URL
180
     * @param array  $headers HTTP headers (optional)
181
     *
182
     * @see RefreshCapable::refresh()
183
     *
184
     * @throws UnsupportedProxyOperationException
185
     *
186
     * @return $this
187
     */
188 2
    public function refreshPath($path, array $headers = [])
189
    {
190 2
        if (!$this->cache instanceof RefreshCapable) {
191 1
            throw UnsupportedProxyOperationException::cacheDoesNotImplement('REFRESH');
192
        }
193
194 1
        $this->cache->refresh($path, $headers);
195
196 1
        return $this;
197
    }
198
199
    /**
200
     * Invalidate all cached objects matching the provided HTTP headers.
201
     *
202
     * Each header is a a POSIX regular expression, for example
203
     * ['X-Host' => '^(www\.)?(this|that)\.com$']
204
     *
205
     * @see BanCapable::ban()
206
     *
207
     * @param array $headers HTTP headers that path must match to be banned
208
     *
209
     * @throws UnsupportedProxyOperationException If HTTP cache does not support BAN requests
210
     *
211
     * @return $this
212
     */
213 2
    public function invalidate(array $headers)
214
    {
215 2
        if (!$this->cache instanceof BanCapable) {
216 1
            throw UnsupportedProxyOperationException::cacheDoesNotImplement('BAN');
217
        }
218
219 1
        $this->cache->ban($headers);
220
221 1
        return $this;
222
    }
223
224
    /**
225
     * Remove/Expire cache objects based on cache tags.
226
     *
227
     * @see TagCapable::tags()
228
     *
229
     * @param array $tags Tags that should be removed/expired from the cache
230
     *
231
     * @throws UnsupportedProxyOperationException If HTTP cache does not support Tags invalidation
232
     *
233
     * @return $this
234
     */
235 3
    public function invalidateTags(array $tags)
236
    {
237 3
        if (!$this->cache instanceof TagCapable) {
238 1
            throw UnsupportedProxyOperationException::cacheDoesNotImplement('Tags');
239
        }
240 2
        $this->cache->invalidateTags($tags);
241
242 2
        return $this;
243
    }
244
245
    /**
246
     * Invalidate URLs based on a regular expression for the URI, an optional
247
     * content type and optional limit to certain hosts.
248
     *
249
     * The hosts parameter can either be a regular expression, e.g.
250
     * '^(www\.)?(this|that)\.com$' or an array of exact host names, e.g.
251
     * ['example.com', 'other.net']. If the parameter is empty, all hosts
252
     * are matched.
253
     *
254
     * @see BanCapable::banPath()
255
     *
256
     * @param string       $path        Regular expression pattern for URI to
257
     *                                  invalidate
258
     * @param string       $contentType Regular expression pattern for the content
259
     *                                  type to limit banning, for instance 'text'
260
     * @param array|string $hosts       Regular expression of a host name or list of
261
     *                                  exact host names to limit banning
262
     *
263
     * @throws UnsupportedProxyOperationException If HTTP cache does not support BAN requests
264
     *
265
     * @return $this
266
     */
267 2
    public function invalidateRegex($path, $contentType = null, $hosts = null)
268
    {
269 2
        if (!$this->cache instanceof BanCapable) {
270 1
            throw UnsupportedProxyOperationException::cacheDoesNotImplement('BAN');
271
        }
272
273 1
        $this->cache->banPath($path, $contentType, $hosts);
274
275 1
        return $this;
276
    }
277
278
    /**
279
     * Clear the cache completely.
280
     *
281
     * @throws UnsupportedProxyOperationException if HTTP cache does not support clearing the cache completely
282
     *
283
     * @return $this
284
     */
285
    public function clearCache()
286
    {
287
        if (!$this->cache instanceof ClearCapable) {
288
            throw UnsupportedProxyOperationException::cacheDoesNotImplement('CLEAR');
289
        }
290
291
        $this->cache->clear();
292
293
        return $this;
294
    }
295
296
    /**
297
     * Send all pending invalidation requests.
298
     *
299
     * @return int the number of cache invalidations performed per caching server
300
     *
301
     * @throws ExceptionCollection if any errors occurred during flush
302
     */
303 3
    public function flush()
304
    {
305
        try {
306 3
            return $this->cache->flush();
307 1
        } catch (ExceptionCollection $exceptions) {
308 1
            foreach ($exceptions as $exception) {
309 1
                $event = new Event();
310 1
                $event->setException($exception);
311 1
                if ($exception instanceof ProxyResponseException) {
312 1
                    $this->dispatch($event, Events::PROXY_RESPONSE_ERROR);
313 1
                } elseif ($exception instanceof ProxyUnreachableException) {
314 1
                    $this->dispatch($event, Events::PROXY_UNREACHABLE_ERROR);
315
                }
316
            }
317
318 1
            throw $exceptions;
319
        }
320
    }
321
322 1
    private function dispatch(Event $event, $eventName)
323
    {
324
        // LegacyEventDispatcherProxy exists in Symfony >= 4.3
325 1
        if (class_exists(LegacyEventDispatcherProxy::class)) {
326
            // New Symfony 4.3 EventDispatcher signature
327 1
            $this->getEventDispatcher()->dispatch($event, $eventName);
328
        } else {
329
            // Old EventDispatcher signature
330
            $this->getEventDispatcher()->dispatch($eventName, $event);
331
        }
332 1
    }
333
}
334