Passed
Pull Request — master (#59)
by Alexander
22:15 queued 08:16
created

GeneratorStream::getMetadata()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

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