1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the API Platform project. |
5
|
|
|
* |
6
|
|
|
* (c) Kévin Dunglas <[email protected]> |
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
|
|
|
declare(strict_types=1); |
13
|
|
|
|
14
|
|
|
namespace ApiPlatform\Core\HttpCache; |
15
|
|
|
|
16
|
|
|
use GuzzleHttp\ClientInterface; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Purges Varnish. |
20
|
|
|
* |
21
|
|
|
* @author Kévin Dunglas <[email protected]> |
22
|
|
|
* |
23
|
|
|
* @experimental |
24
|
|
|
*/ |
25
|
|
|
final class VarnishPurger implements PurgerInterface |
26
|
|
|
{ |
27
|
|
|
private $maxHeaderLength; |
28
|
|
|
private $clients; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @param ClientInterface[] $clients |
32
|
|
|
* @param int $maxHeaderLength |
33
|
|
|
*/ |
34
|
|
|
public function __construct(array $clients, $maxHeaderLength = 7500) |
35
|
|
|
{ |
36
|
|
|
$this->clients = $clients; |
37
|
|
|
$this->maxHeaderLength = $maxHeaderLength; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Calculate how many tags fit into the header. |
42
|
|
|
* |
43
|
|
|
* This assumes that the tags are separated by one character. |
44
|
|
|
* |
45
|
|
|
* From https://github.com/FriendsOfSymfony/FOSHttpCache/blob/2.8.0/src/ProxyClient/HttpProxyClient.php#L137 |
46
|
|
|
* |
47
|
|
|
* @param string[] $escapedTags |
48
|
|
|
* @param string $glue The concatenation string to use |
49
|
|
|
* |
50
|
|
|
* @return int Number of tags per tag invalidation request |
51
|
|
|
*/ |
52
|
|
|
private function determineTagsPerHeader($escapedTags, $glue) |
53
|
|
|
{ |
54
|
|
|
if (mb_strlen(implode($glue, $escapedTags)) < $this->maxHeaderLength) { |
55
|
|
|
return \count($escapedTags); |
56
|
|
|
} |
57
|
|
|
/* |
58
|
|
|
* estimate the amount of tags to invalidate by dividing the max |
59
|
|
|
* header length by the largest tag (minus the glue length) |
60
|
|
|
*/ |
61
|
|
|
$tagsize = max(array_map('mb_strlen', $escapedTags)); |
62
|
|
|
|
63
|
|
|
return (int) floor($this->maxHeaderLength / ($tagsize + \strlen($glue))) ?: 1; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* {@inheritdoc} |
68
|
|
|
*/ |
69
|
|
|
public function purge(array $iris) |
70
|
|
|
{ |
71
|
|
|
if (!$iris) { |
|
|
|
|
72
|
|
|
return; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
$chunkSize = $this->determineTagsPerHeader($iris, '|'); |
76
|
|
|
|
77
|
|
|
$irisChunks = array_chunk($iris, $chunkSize); |
78
|
|
|
foreach ($irisChunks as $irisChunk) { |
79
|
|
|
$this->purgeRequest($irisChunk); |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
private function purgeRequest(array $iris) |
84
|
|
|
{ |
85
|
|
|
// Create the regex to purge all tags in just one request |
86
|
|
|
$parts = array_map(function ($iri) { |
87
|
|
|
return sprintf('(^|\,)%s($|\,)', preg_quote($iri)); |
88
|
|
|
}, $iris); |
89
|
|
|
|
90
|
|
|
$regex = \count($parts) > 1 ? sprintf('(%s)', implode(')|(', $parts)) : array_shift($parts); |
91
|
|
|
|
92
|
|
|
foreach ($this->clients as $client) { |
93
|
|
|
$client->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => $regex]]); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.