Passed
Pull Request — master (#59)
by
unknown
13:30
created

GeneratorStream::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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