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

azjezz / psl / 23100780634

15 Mar 2026 01:37AM UTC coverage: 95.57% (-0.06%) from 95.628%
23100780634

push

github

web-flow
bc(io): `BufferedReadHandleInterface::readLine()` now always splits on `\n` instead of `PHP_EOL` (#630)

13 of 21 new or added lines in 7 files covered. (61.9%)

4 existing lines in 4 files now uncovered.

10462 of 10947 relevant lines covered (95.57%)

32.64 hits per line

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

36.61
/src/Psl/Encoding/Base64/EncodingReadHandle.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Psl\Encoding\Base64;
6

7
use Psl;
8
use Psl\Async\CancellationTokenInterface;
9
use Psl\Async\NullCancellationToken;
10
use Psl\IO;
11

12
use function strlen;
13
use function strpos;
14
use function substr;
15

16
/**
17
 * A read handle that base64-encodes raw binary data from an inner readable handle.
18
 *
19
 * Reads {@see CHUNK_SIZE} (57) byte chunks from the inner handle, encodes each to base64,
20
 * and appends {@see LINE_ENDING} after each encoded chunk.
21
 */
22
final class EncodingReadHandle implements IO\BufferedReadHandleInterface
23
{
24
    use IO\ReadHandleConvenienceMethodsTrait;
25

26
    private string $buffer = '';
27
    private bool $eof = false;
28

29
    public function __construct(
30
        private readonly IO\ReadHandleInterface $handle,
31
        private readonly Variant $variant = Variant::Standard,
32
        private readonly bool $padding = true,
33
    ) {}
8✔
34

35
    /**
36
     * {@inheritDoc}
37
     */
38
    public function reachedEndOfDataSource(): bool
39
    {
40
        return $this->eof && $this->buffer === '';
5✔
41
    }
42

43
    /**
44
     * {@inheritDoc}
45
     */
46
    public function tryRead(null|int $max_bytes = null): string
47
    {
48
        if ($this->buffer === '' && !$this->eof) {
×
49
            $this->fillBuffer();
×
50
        }
51

52
        if ($this->buffer === '') {
×
53
            return '';
×
54
        }
55

56
        if (null === $max_bytes || $max_bytes >= strlen($this->buffer)) {
×
57
            $result = $this->buffer;
×
58
            $this->buffer = '';
×
59
            return $result;
×
60
        }
61

62
        $result = substr($this->buffer, 0, $max_bytes);
×
63
        $this->buffer = substr($this->buffer, $max_bytes);
×
64
        return $result;
×
65
    }
66

67
    /**
68
     * {@inheritDoc}
69
     */
70
    public function read(
71
        null|int $max_bytes = null,
72
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
73
    ): string {
74
        if ($this->eof && $this->buffer === '') {
5✔
75
            return '';
×
76
        }
77

78
        if ($this->buffer === '') {
5✔
79
            $this->fillBuffer($cancellation);
5✔
80
        }
81

82
        if ($this->buffer === '') {
5✔
83
            return '';
5✔
84
        }
85

86
        if (null === $max_bytes || $max_bytes >= strlen($this->buffer)) {
4✔
87
            $result = $this->buffer;
4✔
88
            $this->buffer = '';
4✔
89
            return $result;
4✔
90
        }
91

92
        $result = substr($this->buffer, 0, $max_bytes);
×
93
        $this->buffer = substr($this->buffer, $max_bytes);
×
94
        return $result;
×
95
    }
96

97
    public function readByte(CancellationTokenInterface $cancellation = new NullCancellationToken()): string
98
    {
99
        if ($this->buffer === '' && !$this->eof) {
1✔
100
            $this->fillBuffer($cancellation);
1✔
101
        }
102

103
        if ($this->buffer === '') {
1✔
104
            throw new IO\Exception\RuntimeException('Reached EOF without any more data.');
×
105
        }
106

107
        $ret = $this->buffer[0];
1✔
108
        if ($ret === $this->buffer) {
1✔
109
            $this->buffer = '';
×
110
            return $ret;
×
111
        }
112

113
        $this->buffer = substr($this->buffer, 1);
1✔
114
        return $ret;
1✔
115
    }
116

117
    public function readLine(CancellationTokenInterface $cancellation = new NullCancellationToken()): null|string
118
    {
119
        $line = $this->readUntil("\n", $cancellation);
1✔
120
        if ($line !== null) {
1✔
121
            if ($line !== '' && $line[-1] === "\r") {
1✔
122
                return substr($line, 0, -1);
1✔
123
            }
124

UNCOV
125
            return $line;
×
126
        }
127

128
        // No EOL found; return whatever remains, or null if empty
129
        if ($this->buffer === '' && !$this->eof) {
×
130
            $this->fillBuffer($cancellation);
×
131
        }
132

133
        if ($this->buffer === '') {
×
134
            return null;
×
135
        }
136

137
        $result = $this->buffer;
×
138
        $this->buffer = '';
×
139
        return $result;
×
140
    }
141

142
    public function readUntil(
143
        string $suffix,
144
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
145
    ): null|string {
146
        $suffix_len = strlen($suffix);
2✔
147
        $idx = strpos($this->buffer, $suffix);
2✔
148
        if ($idx !== false) {
2✔
149
            $result = substr($this->buffer, 0, $idx);
×
150
            $this->buffer = substr($this->buffer, $idx + $suffix_len);
×
151
            return $result;
×
152
        }
153

154
        while (!$this->eof) {
2✔
155
            $offset = strlen($this->buffer) - $suffix_len + 1;
2✔
156
            $offset = $offset > 0 ? $offset : 0;
2✔
157

158
            $this->fillBuffer($cancellation);
2✔
159

160
            $idx = strpos($this->buffer, $suffix, $offset);
2✔
161
            if ($idx !== false) {
2✔
162
                $result = substr($this->buffer, 0, $idx);
2✔
163
                $this->buffer = substr($this->buffer, $idx + $suffix_len);
2✔
164
                return $result;
2✔
165
            }
166
        }
167

168
        return null;
×
169
    }
170

171
    public function readUntilBounded(
172
        string $suffix,
173
        int $max_bytes,
174
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
175
    ): null|string {
176
        $suffix_len = strlen($suffix);
×
177
        $idx = strpos($this->buffer, $suffix);
×
178
        if ($idx !== false) {
×
179
            if ($idx > $max_bytes) {
×
180
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
181
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
182
                    $max_bytes,
×
183
                    $suffix,
×
184
                ));
×
185
            }
186

187
            $result = substr($this->buffer, 0, $idx);
×
188
            $this->buffer = substr($this->buffer, $idx + $suffix_len);
×
189
            return $result;
×
190
        }
191

192
        if (strlen($this->buffer) > $max_bytes) {
×
193
            throw new IO\Exception\OverflowException(Psl\Str\format(
×
194
                'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
195
                $max_bytes,
×
196
                $suffix,
×
197
            ));
×
198
        }
199

200
        while (!$this->eof) {
×
201
            $offset = strlen($this->buffer) - $suffix_len + 1;
×
202
            $offset = $offset > 0 ? $offset : 0;
×
203

204
            $this->fillBuffer($cancellation);
×
205

206
            $idx = strpos($this->buffer, $suffix, $offset);
×
207
            if ($idx !== false) {
×
208
                if ($idx > $max_bytes) {
×
209
                    throw new IO\Exception\OverflowException(Psl\Str\format(
×
210
                        'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
211
                        $max_bytes,
×
212
                        $suffix,
×
213
                    ));
×
214
                }
215

216
                $result = substr($this->buffer, 0, $idx);
×
217
                $this->buffer = substr($this->buffer, $idx + $suffix_len);
×
218
                return $result;
×
219
            }
220

221
            if (strlen($this->buffer) > $max_bytes) {
×
222
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
223
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
224
                    $max_bytes,
×
225
                    $suffix,
×
226
                ));
×
227
            }
228
        }
229

230
        return null;
×
231
    }
232

233
    private function fillBuffer(CancellationTokenInterface $cancellation = new NullCancellationToken()): void
234
    {
235
        if ($this->eof) {
8✔
236
            return;
×
237
        }
238

239
        $chunk = $this->handle->read(CHUNK_SIZE, $cancellation);
8✔
240
        if ($chunk === '' && $this->handle->reachedEndOfDataSource()) {
8✔
241
            $this->eof = true;
5✔
242
            return;
5✔
243
        }
244

245
        if ($chunk !== '') {
7✔
246
            $this->buffer .= encode($chunk, $this->variant, $this->padding) . LINE_ENDING;
7✔
247
        }
248
    }
249
}
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

© 2026 Coveralls, Inc