Varnish   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 68
c 2
b 0
f 0
dl 0
loc 166
ccs 60
cts 60
cp 1
rs 10
wmc 17

8 Methods

Rating   Name   Duplication   Size   Complexity  
A ban() 0 10 1
A banPath() 0 21 5
A purge() 0 5 1
A refresh() 0 6 1
A invalidateTags() 0 20 5
A configureOptions() 0 29 2
A invalidateByBan() 0 4 1
A invalidateByPurgekeys() 0 7 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\ProxyClient;
13
14
use FOS\HttpCache\Exception\InvalidArgumentException;
15
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
16
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
17
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
18
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
19
use Symfony\Component\OptionsResolver\Options;
20
21
/**
22
 * Varnish HTTP cache invalidator.
23
 *
24
 * Additional constructor options:
25
 * - tag_mode            Whether to use ban or the xkey extension for cache tagging.
26
 * - tags_header         Header for sending tag invalidation requests to
27
 *                       Varnish, if tag_mode is ban, defaults to X-Cache-Tags,
28
 *                       otherwise defaults to xkey-softpurge.
29
 * - header_length       Maximum header length when invalidating tags. If there
30
 *                       are more tags to invalidate than fit into the header,
31
 *                       the invalidation request is split into several requests.
32
 *                       Defaults to 7500
33
 * - default_ban_headers Map of header name => header value that have to be set
34
 *                       on each ban request, merged with the built-in headers
35
 *
36
 * @author David de Boer <[email protected]>
37
 */
38
class Varnish extends HttpProxyClient implements BanCapable, PurgeCapable, RefreshCapable, TagCapable
39
{
40
    public const HTTP_METHOD_BAN = 'BAN';
41
42
    public const HTTP_METHOD_PURGE = 'PURGE';
43
44
    public const HTTP_METHOD_PURGEKEYS = 'PURGEKEYS';
45
46
    public const HTTP_METHOD_REFRESH = 'GET';
47
48
    public const HTTP_HEADER_HOST = 'X-Host';
49
50
    public const HTTP_HEADER_URL = 'X-Url';
51
52
    public const HTTP_HEADER_CONTENT_TYPE = 'X-Content-Type';
53
54
    public const TAG_BAN = 'ban';
55
56
    public const TAG_XKEY = 'purgekeys';
57
58
    /**
59
     * Default name of the header used to invalidate content with specific tags.
60
     *
61
     * This happens to be the same as TagHeaderFormatter::DEFAULT_HEADER_NAME
62
     * but does not technically need to be the same.
63
     *
64
     * @var string
65
     */
66
    public const DEFAULT_HTTP_HEADER_CACHE_TAGS = 'X-Cache-Tags';
67
68
    public const DEFAULT_HTTP_HEADER_CACHE_XKEY = 'xkey-softpurge';
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 6
    public function invalidateTags(array $tags)
74
    {
75 6
        $banMode = self::TAG_BAN === $this->options['tag_mode'];
76
77
        // If using BAN's, escape each tag
78 6
        if ($banMode) {
79 3
            $tags = array_map('preg_quote', $this->escapeTags($tags));
80
        }
81
82 6
        $chunkSize = $this->determineTagsPerHeader($tags, $banMode ? '|' : ' ');
83
84 6
        foreach (array_chunk($tags, $chunkSize) as $tagchunk) {
85 6
            if ($banMode) {
86 3
                $this->invalidateByBan($tagchunk);
87
            } else {
88 3
                $this->invalidateByPurgekeys($tagchunk);
89
            }
90
        }
91
92 6
        return $this;
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 9
    public function ban(array $headers)
99
    {
100 9
        $headers = array_merge(
101 9
            $this->options['default_ban_headers'],
102
            $headers
103
        );
104
105 9
        $this->queueRequest(self::HTTP_METHOD_BAN, '/', $headers, false);
106
107 9
        return $this;
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 4
    public function banPath($path, $contentType = null, $hosts = null)
114
    {
115 4
        if (is_array($hosts)) {
116 2
            if (!count($hosts)) {
117 1
                throw new InvalidArgumentException('Either supply a list of hosts or null, but not an empty array.');
118
            }
119 1
            $hosts = '^('.implode('|', $hosts).')$';
120
        }
121
122
        $headers = [
123 3
            self::HTTP_HEADER_URL => $path,
124
        ];
125
126 3
        if ($contentType) {
127 2
            $headers[self::HTTP_HEADER_CONTENT_TYPE] = $contentType;
128
        }
129 3
        if ($hosts) {
130 1
            $headers[self::HTTP_HEADER_HOST] = $hosts;
131
        }
132
133 3
        return $this->ban($headers);
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139 4
    public function purge($url, array $headers = [])
140
    {
141 4
        $this->queueRequest(self::HTTP_METHOD_PURGE, $url, $headers);
142
143 4
        return $this;
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149 3
    public function refresh($url, array $headers = [])
150
    {
151 3
        $headers = array_merge($headers, ['Cache-Control' => 'no-cache']);
152 3
        $this->queueRequest(self::HTTP_METHOD_REFRESH, $url, $headers);
153
154 3
        return $this;
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160 21
    protected function configureOptions()
161
    {
162 21
        $resolver = parent::configureOptions();
163 21
        $resolver->setDefaults([
164 21
            'tags_header' => self::DEFAULT_HTTP_HEADER_CACHE_TAGS,
165 21
            'tag_mode' => self::TAG_BAN,
166 21
            'header_length' => 7500,
167
            'default_ban_headers' => [],
168
        ]);
169
        $resolver->setDefault('tags_header', function (Options $options) {
170 9
            if (self::TAG_BAN === $options['tag_mode']) {
171 8
                return self::DEFAULT_HTTP_HEADER_CACHE_TAGS;
172
            }
173
174 1
            return self::DEFAULT_HTTP_HEADER_CACHE_XKEY;
175 21
        });
176 21
        $resolver->setAllowedValues('tag_mode', [self::TAG_BAN, self::TAG_XKEY]);
177
        $resolver->setNormalizer('default_ban_headers', function ($resolver, $specified) {
178 21
            return array_merge(
179
                [
180 21
                    self::HTTP_HEADER_HOST => self::REGEX_MATCH_ALL,
181 21
                    self::HTTP_HEADER_URL => self::REGEX_MATCH_ALL,
182 21
                    self::HTTP_HEADER_CONTENT_TYPE => self::REGEX_MATCH_ALL,
183
                ],
184
                $specified
185
            );
186 21
        });
187
188 21
        return $resolver;
189
    }
190
191 3
    private function invalidateByBan(array $tagchunk)
192
    {
193 3
        $tagExpression = sprintf('(^|,)(%s)(,|$)', implode('|', $tagchunk));
194 3
        $this->ban([$this->options['tags_header'] => $tagExpression]);
195 3
    }
196
197 3
    private function invalidateByPurgekeys(array $tagchunk)
198
    {
199 3
        $this->queueRequest(
200 3
            self::HTTP_METHOD_PURGEKEYS,
201 3
            '/',
202 3
            [$this->options['tags_header'] => implode(' ', $tagchunk)],
203 3
            false
204
        );
205 3
    }
206
}
207