Passed
Pull Request — master (#2865)
by Jocelyn
04:07
created

VarnishPurger::purgeRequest()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 4
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 $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/master/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) {
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...
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