Completed
Pull Request — master (#2865)
by Jocelyn
10:00 queued 04:15
created

VarnishPurger::purge()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 11
rs 10
c 0
b 0
f 0
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 $clients;
28
    private $maxHeaderLength;
29
30
    /**
31
     * @param ClientInterface[] $clients
32
     */
33
    public function __construct(array $clients, int $maxHeaderLength = 7500)
34
    {
35
        $this->clients = $clients;
36
        $this->maxHeaderLength = $maxHeaderLength;
37
    }
38
39
    /**
40
     * Calculate how many tags fit into the header.
41
     *
42
     * This assumes that the tags are separated by one character.
43
     *
44
     * From https://github.com/FriendsOfSymfony/FOSHttpCache/blob/2.8.0/src/ProxyClient/HttpProxyClient.php#L137
45
     *
46
     * @param string[] $escapedTags
47
     * @param string   $glue        The concatenation string to use
48
     *
49
     * @return int Number of tags per tag invalidation request
50
     */
51
    private function determineTagsPerHeader($escapedTags, $glue)
52
    {
53
        if (mb_strlen(implode($glue, $escapedTags)) < $this->maxHeaderLength) {
54
            return \count($escapedTags);
55
        }
56
        /*
57
         * estimate the amount of tags to invalidate by dividing the max
58
         * header length by the largest tag (minus the glue length)
59
         */
60
        $tagsize = max(array_map('mb_strlen', $escapedTags));
61
62
        return (int) floor($this->maxHeaderLength / ($tagsize + \strlen($glue))) ?: 1;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function purge(array $iris)
69
    {
70
        if (!$iris) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $iris of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
71
            return;
72
        }
73
74
        $chunkSize = $this->determineTagsPerHeader($iris, '|');
75
76
        $irisChunks = array_chunk($iris, $chunkSize);
77
        foreach ($irisChunks as $irisChunk) {
78
            $this->purgeRequest($irisChunk);
79
        }
80
    }
81
82
    private function purgeRequest(array $iris)
83
    {
84
        // Create the regex to purge all tags in just one request
85
        $parts = array_map(function ($iri) {
86
            return sprintf('(^|\,)%s($|\,)', preg_quote($iri));
87
        }, $iris);
88
89
        $regex = \count($parts) > 1 ? sprintf('(%s)', implode(')|(', $parts)) : array_shift($parts);
90
91
        foreach ($this->clients as $client) {
92
            $client->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => $regex]]);
93
        }
94
    }
95
}
96