Completed
Push — master ( 6552fb...220974 )
by David
02:35
created

CacheInvalidator   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 91.38%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 11
dl 0
loc 259
ccs 53
cts 58
cp 0.9138
rs 10
c 0
b 0
f 0

10 Methods

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