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

ptondereau / biscuit-sf-bundle / 30813598889

03 Aug 2026 12:26PM UTC coverage: 91.238% (-1.9%) from 93.09%
30813598889

push

github

web-flow
feat(revocation)!: compose revocation stores and align 401s with RFC 6750 (#7)

739 of 842 new or added lines in 27 files covered. (87.77%)

1843 of 2020 relevant lines covered (91.24%)

13.53 hits per line

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

98.15
/src/Revocation/Store/StaticRevocationStore.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Biscuit\BiscuitBundle\Revocation\Store;
6

7
use Biscuit\BiscuitBundle\Revocation\EnumerableRevocationStoreInterface;
8
use Biscuit\BiscuitBundle\Revocation\Exception\RevocationStoreUnavailableException;
9
use Biscuit\BiscuitBundle\Revocation\RevocationEntry;
10

11
final class StaticRevocationStore implements EnumerableRevocationStoreInterface
12
{
13
    /**
14
     * @var list<non-empty-string>|null
15
     */
16
    private ?array $ids = null;
17

18
    /**
19
     * @var array<string, true>
20
     */
21
    private array $index = [];
22

23
    /**
24
     * @param array<array-key, mixed>|string $revocationIds a list of ids, or an env placeholder
25
     */
26
    public function __construct(
29 ✔
27
        private readonly array|string $revocationIds = [],
28
        private readonly ?string $file = null,
29
    ) {
30
    }
29 ✔
31

32
    public function findRevoked(array $revocationIds): ?string
24 ✔
33
    {
34
        if ([] === $this->ids()) {
24 ✔
35
            return null;
2 ✔
36
        }
37

38
        foreach ($revocationIds as $revocationId) {
20 ✔
39
            if (isset($this->index[strtolower($revocationId)])) {
20 ✔
40
                return $revocationId;
17 ✔
41
            }
42
        }
43

44
        return null;
4 ✔
45
    }
46

47
    public function all(): iterable
6 ✔
48
    {
49
        foreach ($this->ids() as $revocationId) {
6 ✔
50
            yield new RevocationEntry($revocationId);
6 ✔
51
        }
52
    }
53

54
    /**
55
     * @return list<non-empty-string>
56
     */
57
    private function ids(): array
28 ✔
58
    {
59
        if (null !== $this->ids) {
28 ✔
60
            return $this->ids;
4 ✔
61
        }
62

63
        $ids = $this->normalize($this->revocationIds);
28 ✔
64

65
        if (null !== $this->file) {
28 ✔
66
            $ids = [...$ids, ...$this->normalize($this->readFile($this->file))];
6 ✔
67
        }
68

69
        $index = [];
26 ✔
70
        $unique = [];
26 ✔
71

72
        foreach ($ids as $id) {
26 ✔
73
            if (isset($index[$id])) {
24 ✔
74
                continue;
1 ✔
75
            }
76

77
            $index[$id] = true;
24 ✔
78
            $unique[] = $id;
24 ✔
79
        }
80

81
        $this->index = $index;
26 ✔
82

83
        return $this->ids = $unique;
26 ✔
84
    }
85

86
    /**
87
     * @return list<non-empty-string>
88
     */
89
    private function normalize(mixed $value): array
28 ✔
90
    {
91
        $input = \is_array($value) ? $value : [$value];
28 ✔
92
        $normalized = [];
28 ✔
93

94
        array_walk_recursive($input, static function (mixed $id) use (&$normalized): void {
28 ✔
95
            if (!\is_string($id)) {
24 ✔
96
                return;
1 ✔
97
            }
98

99
            $id = strtolower(trim($id));
24 ✔
100

101
            if ('' !== $id) {
24 ✔
102
                $normalized[] = $id;
24 ✔
103
            }
104
        });
28 ✔
105

106
        return $normalized;
28 ✔
107
    }
108

109
    /**
110
     * @return list<string>
111
     */
112
    private function readFile(string $file): array
6 ✔
113
    {
114
        if (!is_file($file) || !is_readable($file)) {
6 ✔
115
            throw new RevocationStoreUnavailableException(sprintf('Revocation list file "%s" is missing or not readable.', $file));
1 ✔
116
        }
117

118
        $contents = @file_get_contents($file);
5 ✔
119

120
        if (false === $contents) {
5 ✔
NEW
121
            throw new RevocationStoreUnavailableException(sprintf('Revocation list file "%s" could not be read.', $file));
×
122
        }
123

124
        $trimmed = trim($contents);
5 ✔
125

126
        if ('' === $trimmed) {
5 ✔
127
            return [];
1 ✔
128
        }
129

130
        if (str_starts_with($trimmed, '[')) {
4 ✔
131
            $decoded = json_decode($trimmed, true);
2 ✔
132

133
            if (!\is_array($decoded)) {
2 ✔
134
                throw new RevocationStoreUnavailableException(sprintf('Revocation list file "%s" does not contain a valid JSON list.', $file));
1 ✔
135
            }
136

137
            return array_values(array_filter($decoded, 'is_string'));
1 ✔
138
        }
139

140
        $lines = preg_split('/\R/', $trimmed);
2 ✔
141

142
        return false === $lines ? [] : $lines;
2 ✔
143
    }
144
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc