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

predis / predis / 20351138744

18 Dec 2025 09:00PM UTC coverage: 92.603% (-0.2%) from 92.788%
20351138744

Pull #1616

github

web-flow
Merge 4023b4e91 into 0ae180c94
Pull Request #1616: Added retry support

301 of 358 new or added lines in 18 files covered. (84.08%)

3 existing lines in 3 files now uncovered.

8213 of 8869 relevant lines covered (92.6%)

112.14 hits per line

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

84.87
/src/Connection/Resource/Stream.php
1
<?php
2

3
/*
4
 * This file is part of the Predis package.
5
 *
6
 * (c) 2009-2020 Daniele Alessandri
7
 * (c) 2021-2025 Till Krüss
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12

13
namespace Predis\Connection\Resource;
14

15
use InvalidArgumentException;
16
use Psr\Http\Message\StreamInterface;
17
use RuntimeException;
18

19
class Stream implements StreamInterface
20
{
21
    /**
22
     * @see https://www.php.net/manual/en/function.fopen.php
23
     * @see https://www.php.net/manual/en/function.gzopen.php
24
     */
25
    private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
26
    private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';
27

28
    /**
29
     * @var resource
30
     */
31
    private $stream;
32

33
    /**
34
     * @var bool
35
     */
36
    private $seekable;
37

38
    /**
39
     * @var bool
40
     */
41
    private $readable;
42

43
    /**
44
     * @var bool
45
     */
46
    private $writable;
47

48
    /**
49
     * @param  resource                 $stream
50
     * @throws InvalidArgumentException if stream is not a valid resource.
51
     */
52
    public function __construct($stream)
1,667✔
53
    {
54
        if (!is_resource($stream)) {
1,667✔
55
            throw new InvalidArgumentException('Given stream is not a valid resource');
1✔
56
        }
57

58
        $this->stream = $stream;
1,666✔
59
        $metadata = stream_get_meta_data($this->stream);
1,666✔
60
        $this->seekable = $metadata['seekable'];
1,666✔
61
        $this->readable = (bool) preg_match(self::READABLE_MODES, $metadata['mode']);
1,666✔
62
        $this->writable = (bool) preg_match(self::WRITABLE_MODES, $metadata['mode']);
1,666✔
63
    }
64

65
    /**
66
     * {@inheritDoc}
67
     */
68
    public function __toString(): string
1✔
69
    {
70
        if ($this->isSeekable()) {
1✔
71
            $this->seek(0);
1✔
72
        }
73

74
        return $this->getContents();
1✔
75
    }
76

77
    /**
78
     * {@inheritDoc}
79
     */
80
    public function close(): void
1,604✔
81
    {
82
        if (isset($this->stream)) {
1,604✔
83
            fclose($this->stream);
1,595✔
84
        }
85

86
        $this->detach();
1,604✔
87
    }
88

89
    /**
90
     * {@inheritDoc}
91
     */
92
    public function detach()
1,615✔
93
    {
94
        if (!isset($this->stream)) {
1,615✔
95
            return null;
10✔
96
        }
97

98
        $result = $this->stream;
1,615✔
99
        unset($this->stream);
1,615✔
100
        $this->readable = $this->writable = $this->seekable = false;
1,615✔
101

102
        return $result;
1,615✔
103
    }
104

105
    /**
106
     * {@inheritDoc}
107
     */
108
    public function getSize(): ?int
1✔
109
    {
110
        if (!isset($this->stream)) {
1✔
111
            return null;
1✔
112
        }
113

114
        $stats = fstat($this->stream);
1✔
115
        if (is_array($stats) && isset($stats['size'])) {
1✔
116
            return $stats['size'];
1✔
117
        }
118

119
        return null;
×
120
    }
121

122
    /**
123
     * {@inheritDoc}
124
     */
125
    public function tell(): int
2✔
126
    {
127
        if (!isset($this->stream)) {
2✔
128
            throw new RuntimeException('Stream is detached');
1✔
129
        }
130

131
        $result = ftell($this->stream);
1✔
132

133
        if ($result === false) {
1✔
134
            throw new RuntimeException('Unable to determine stream position');
×
135
        }
136

137
        return $result;
1✔
138
    }
139

140
    /**
141
     * {@inheritDoc}
142
     */
143
    public function eof(): bool
1,607✔
144
    {
145
        if (!isset($this->stream)) {
1,607✔
146
            throw new RuntimeException('Stream is detached');
1✔
147
        }
148

149
        return feof($this->stream);
1,606✔
150
    }
151

152
    /**
153
     * {@inheritDoc}
154
     */
155
    public function isSeekable(): bool
8✔
156
    {
157
        return $this->seekable;
8✔
158
    }
159

160
    /**
161
     * {@inheritDoc}
162
     */
163
    public function seek(int $offset, int $whence = SEEK_SET): void
7✔
164
    {
165
        if (!isset($this->stream)) {
7✔
166
            throw new RuntimeException('Stream is detached');
1✔
167
        }
168

169
        if (!$this->isSeekable()) {
6✔
170
            throw new RuntimeException('Stream is not seekable');
×
171
        }
172

173
        if (fseek($this->stream, $offset, $whence) === -1) {
6✔
174
            throw new RuntimeException("Unable to seek stream from offset {$offset} to whence {$whence}");
1✔
175
        }
176
    }
177

178
    /**
179
     * {@inheritDoc}
180
     */
181
    public function rewind(): void
4✔
182
    {
183
        $this->seek(0);
4✔
184
    }
185

186
    /**
187
     * {@inheritDoc}
188
     */
189
    public function isWritable(): bool
1,629✔
190
    {
191
        return $this->writable;
1,629✔
192
    }
193

194
    /**
195
     * {@inheritDoc}
196
     * @throws RuntimeException
197
     */
198
    public function write(string $string): int
1,611✔
199
    {
200
        if (!isset($this->stream)) {
1,611✔
201
            throw new RuntimeException('Stream is detached');
1✔
202
        }
203

204
        if (!$this->isWritable()) {
1,610✔
205
            throw new RuntimeException('Cannot write to a non-writable stream');
1✔
206
        }
207

208
        $result = fwrite($this->stream, $string);
1,609✔
209

210
        if ($result === false || $result === 0) {
1,609✔
211
            $metadata = $this->getMetadata();
1✔
212

213
            if ($this->eof()) {
1✔
NEW
214
                throw new RuntimeException('Connection closed by peer during write', 1);
×
215
            }
216

217
            if (!is_resource($this->stream)) {
1✔
NEW
218
                throw new RuntimeException(
×
NEW
219
                    'Stream resource is no longer valid',
×
NEW
220
                    1
×
NEW
221
                );
×
222
            }
223

224
            if (array_key_exists('timed_out', $metadata) && $metadata['timed_out']) {
1✔
NEW
225
                throw new RuntimeException(
×
NEW
226
                    'Stream has been timed out',
×
NEW
227
                    2
×
NEW
228
                );
×
229
            }
230

231
            throw new RuntimeException('Unable to write to stream', 1);
1✔
232
        }
233

234
        return $result;
1,608✔
235
    }
236

237
    /**
238
     * {@inheritDoc}
239
     */
240
    public function isReadable(): bool
1,630✔
241
    {
242
        return $this->readable;
1,630✔
243
    }
244

245
    /**
246
     * {@inheritDoc}
247
     * @param  int              $length If length = -1, reads a stream line by line (e.g fgets())
248
     * @throws RuntimeException
249
     */
250
    public function read(int $length): string
1,610✔
251
    {
252
        if (!isset($this->stream)) {
1,610✔
253
            throw new RuntimeException('Stream is detached');
1✔
254
        }
255

256
        if (!$this->isReadable()) {
1,609✔
257
            throw new RuntimeException('Cannot read from non-readable stream');
1✔
258
        }
259

260
        if ($length < -1) {
1,608✔
261
            throw new RuntimeException('Length parameter cannot be negative');
1✔
262
        }
263

264
        if (0 === $length) {
1,607✔
265
            return '';
1✔
266
        }
267

268
        if ($length === -1) {
1,606✔
269
            $string = fgets($this->stream);
1,604✔
270
        } else {
271
            $string = fread($this->stream, $length);
1,606✔
272
        }
273

274
        if (false === $string) {
1,606✔
275
            $metadata = $this->getMetadata();
6✔
276

277
            if ($this->eof()) {
6✔
NEW
278
                throw new RuntimeException('Connection closed by peer during read', 1);
×
279
            }
280

281
            if (!is_resource($this->stream)) {
6✔
NEW
282
                throw new RuntimeException(
×
NEW
283
                    'Stream resource is no longer valid',
×
NEW
284
                    1
×
NEW
285
                );
×
286
            }
287

288
            if (array_key_exists('timed_out', $metadata) && $metadata['timed_out']) {
6✔
289
                throw new RuntimeException(
6✔
290
                    'Stream has been timed out',
6✔
291
                    2
6✔
292
                );
6✔
293
            }
294

UNCOV
295
            throw new RuntimeException('Unable to read from stream', 1);
×
296
        }
297

298
        return $string;
1,606✔
299
    }
300

301
    /**
302
     * {@inheritDoc}
303
     */
304
    public function getContents(): string
4✔
305
    {
306
        if (!isset($this->stream)) {
4✔
307
            throw new RuntimeException('Stream is detached');
1✔
308
        }
309

310
        if (!$this->isReadable()) {
3✔
311
            throw new RuntimeException('Cannot read from non-readable stream');
1✔
312
        }
313

314
        return stream_get_contents($this->stream);
2✔
315
    }
316

317
    /**
318
     * {@inheritDoc}
319
     * @return mixed
320
     */
321
    public function getMetadata(?string $key = null)
8✔
322
    {
323
        if (!isset($this->stream)) {
8✔
324
            return null;
1✔
325
        }
326

327
        if (!$key) {
8✔
328
            return stream_get_meta_data($this->stream);
8✔
329
        }
330

331
        $metadata = stream_get_meta_data($this->stream);
1✔
332

333
        return $metadata[$key] ?? null;
1✔
334
    }
335
}
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