Passed
Pull Request — master (#75)
by Alexander
36:39 queued 06:28
created

GeneratorStream::seek()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 2
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Stream;
6
7
use Generator;
8
use Psr\Http\Message\StreamInterface;
9
10
final class GeneratorStream implements StreamInterface
11
{
12
    public const READ_MODE_AS_IS = 1;
13
    public const READ_MODE_FIRST_YIELD = 2;
14
15
    private int $readMode = self::READ_MODE_AS_IS;
16
17
    private ?Generator $stream;
18
19
    private bool $readable;
20
21
    private ?int $size = null;
22
23
    private int $caret = 0;
24
25
    private bool $started = false;
26
27
28
    public function __construct(Generator $body)
29
    {
30
        $this->stream = $body;
31
        $this->readable = true;
32
    }
33
34
    public function __toString(): string
35
    {
36
        try {
37
            if ($this->isSeekable()) {
38
                $this->seek(0);
39
            }
40
41
            return $this->getContents();
42
        } catch (\Exception $e) {
43
            return '';
44
        }
45
    }
46
47
    public function close(): void
48
    {
49
        if (isset($this->stream)) {
50
            $this->detach();
51
        }
52
    }
53
54
    public function detach()
55
    {
56
        if (!isset($this->stream)) {
57
            return null;
58
        }
59
        $result = $this->stream;
60
        unset($this->stream);
61
        $this->size = null;
62
        $this->caret = 0;
63
        $this->started = false;
64
        $this->readable = false;
65
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type Generator which is incompatible with the return type mandated by Psr\Http\Message\StreamInterface::detach() of null|resource.
Loading history...
66
    }
67
68
    public function getSize(): ?int
69
    {
70
        if (null !== $this->size) {
71
            return $this->size;
72
        }
73
74
        if (!isset($this->stream)) {
75
            return null;
76
        }
77
78
        return null;
79
    }
80
81
    public function tell(): int
82
    {
83
        return $this->caret;
84
    }
85
86
    public function eof(): bool
87
    {
88
        return $this->stream === null || !$this->stream->valid();
89
    }
90
91
    public function isSeekable(): bool
92
    {
93
        return false;
94
    }
95
96
    public function seek($offset, $whence = \SEEK_SET): void
97
    {
98
        if (!$this->isSeekable()) {
99
            throw new \RuntimeException('Stream is not seekable');
100
        }
101
    }
102
103
    public function rewind(): void
104
    {
105
        $this->stream->rewind();
0 ignored issues
show
Bug introduced by
The method rewind() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

105
        $this->stream->/** @scrutinizer ignore-call */ 
106
                       rewind();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
106
        $this->caret = 0;
107
        $this->started = false;
108
    }
109
110
    public function isWritable(): bool
111
    {
112
        return false;
113
    }
114
115
    public function write($string): int
116
    {
117
        if (!$this->isWritable()) {
118
            throw new \RuntimeException('Cannot write to a non-writable stream');
119
        }
120
        return 0;
121
    }
122
123
    public function isReadable(): bool
124
    {
125
        return $this->readable;
126
    }
127
128
    public function setReadMode(int $mode): void
129
    {
130
        $this->readMode = $mode;
131
    }
132
133
    public function read($length): string
134
    {
135
        if (!$this->readable) {
136
            throw new \RuntimeException('Cannot read from non-readable stream');
137
        }
138
        if ($this->eof()) {
139
            throw new \RuntimeException('Cannot read from ended stream');
140
        }
141
        if (!$this->started) {
142
            $this->started = true;
143
            $read = (string)$this->stream->current();
144
            $this->caret += strlen($read);
145
            return $read;
146
        }
147
        if ($this->readMode === self::READ_MODE_FIRST_YIELD) {
148
            $content = '';
149
            while (!$this->eof()) {
150
                $content .= $this->stream->send(null);
151
            }
152
            return $content;
153
        }
154
        $read = (string)$this->stream->send(null);
155
        $this->caret += strlen($read);
156
        if ($this->eof()) {
157
            $this->size = $this->caret;
158
        }
159
        return $read;
160
    }
161
162
    public function getContents(): string
163
    {
164
        if (!isset($this->stream)) {
165
            throw new \RuntimeException('Unable to read stream contents');
166
        }
167
        $content = '';
168
        while (!$this->eof()) {
169
            $content .= $this->read(PHP_INT_MAX);
170
        }
171
        return $content;
172
    }
173
174
    public function getMetadata($key = null)
175
    {
176
        if (!isset($this->stream)) {
177
            return $key ? null : [];
178
        }
179
180
        $meta = [
181
            'seekable' => $this->isSeekable(),
182
            'eof' => $this->eof(),
183
        ];
184
185
        if (null === $key) {
186
            return $meta;
187
        }
188
189
        return $meta[$key] ?? null;
190
    }
191
}
192