• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 10315659289

09 Aug 2024 07:49AM UTC coverage: 7.841% (-0.006%) from 7.847%
10315659289

push

github

soyuka
style: cs fixes

70 of 529 new or added lines in 176 files covered. (13.23%)

160 existing lines in 58 files now uncovered.

12688 of 161818 relevant lines covered (7.84%)

26.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/HttpCache/VarnishPurger.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.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
declare(strict_types=1);
13

14
namespace ApiPlatform\HttpCache;
15

16
use Symfony\Contracts\HttpClient\HttpClientInterface;
17

18
/**
19
 * Purges Varnish.
20
 *
21
 * @author Kévin Dunglas <dunglas@gmail.com>
22
 */
23
final class VarnishPurger implements PurgerInterface
24
{
25
    private const DEFAULT_VARNISH_MAX_HEADER_LENGTH = 8000;
26
    private const REGEXP_PATTERN = '(%s)($|\,)';
27
    private readonly int $maxHeaderLength;
28

29
    /**
30
     * @param HttpClientInterface[] $clients
31
     */
32
    public function __construct(private readonly iterable $clients, int $maxHeaderLength = self::DEFAULT_VARNISH_MAX_HEADER_LENGTH)
33
    {
34
        $this->maxHeaderLength = $maxHeaderLength - mb_strlen(self::REGEXP_PATTERN) + 2; // 2 for %s
×
35
    }
36

37
    /**
38
     * Calculate how many tags fit into the header.
39
     *
40
     * This assumes that the tags are separated by one character.
41
     *
42
     * From https://github.com/FriendsOfSymfony/FOSHttpCache/blob/2.8.0/src/ProxyClient/HttpProxyClient.php#L137
43
     *
44
     * @param string[] $escapedTags
45
     * @param string   $glue        The concatenation string to use
46
     *
47
     * @return int Number of tags per tag invalidation request
48
     */
49
    private function determineTagsPerHeader(array $escapedTags, string $glue): int
50
    {
51
        if (mb_strlen(implode($glue, $escapedTags)) < $this->maxHeaderLength) {
×
52
            return \count($escapedTags);
×
53
        }
54
        /*
55
         * estimate the amount of tags to invalidate by dividing the max
56
         * header length by the largest tag (minus the glue length)
57
         */
58
        $tagsize = max(array_map('mb_strlen', $escapedTags));
×
59
        $gluesize = \strlen($glue);
×
60

61
        return (int) floor(($this->maxHeaderLength + $gluesize) / ($tagsize + $gluesize)) ?: 1;
×
62
    }
63

64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function purge(array $iris): void
68
    {
69
        if (!$iris) {
×
70
            return;
×
71
        }
72

73
        $chunkSize = $this->determineTagsPerHeader($iris, '|');
×
74

75
        $irisChunks = array_chunk($iris, $chunkSize);
×
76
        foreach ($irisChunks as $irisChunk) {
×
77
            $this->purgeRequest($irisChunk);
×
78
        }
79
    }
80

81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function getResponseHeaders(array $iris): array
85
    {
86
        return ['Cache-Tags' => implode(',', $iris)];
×
87
    }
88

89
    private function purgeRequest(array $iris): void
90
    {
91
        // Create the regex to purge all tags in just one request
92
        $parts = array_map(static fn ($iri): string => // here we should remove the prefix as it's not discriminent and cost a lot to compute
×
93
preg_quote($iri), $iris);
×
94

95
        foreach ($this->chunkRegexParts($parts) as $regex) {
×
NEW
96
            $regex = \sprintf(self::REGEXP_PATTERN, $regex);
×
97
            $this->banRegex($regex);
×
98
        }
99
    }
100

101
    private function banRegex(string $regex): void
102
    {
103
        foreach ($this->clients as $client) {
×
104
            $client->request('BAN', '', ['headers' => ['ApiPlatform-Ban-Regex' => $regex]]);
×
105
        }
106
    }
107

108
    private function chunkRegexParts(array $parts): iterable
109
    {
110
        if (1 === \count($parts)) {
×
111
            yield $parts[0];
×
112

113
            return;
×
114
        }
115

116
        $concatenatedParts = implode("\n", $parts);
×
117

118
        if (\strlen($concatenatedParts) <= $this->maxHeaderLength) {
×
119
            yield str_replace("\n", '|', $concatenatedParts);
×
120

121
            return;
×
122
        }
123

124
        $lastSeparator = strrpos(substr($concatenatedParts, 0, $this->maxHeaderLength + 1), "\n");
×
125

126
        $chunk = substr($concatenatedParts, 0, $lastSeparator);
×
127

128
        yield str_replace("\n", '|', $chunk);
×
129

130
        $nextParts = \array_slice($parts, substr_count($chunk, "\n") + 1);
×
131

132
        yield from $this->chunkRegexParts($nextParts);
×
133
    }
134
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc