Test Failed
Branch master (f83914)
by Rasul
07:02
created

Stream   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 110
dl 0
loc 254
rs 8.64
c 0
b 0
f 0
wmc 47

How to fix   Complexity   

Complex Class

Complex classes like Stream often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Stream, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Furious\Psr7;
6
7
use Furious\Psr7\Exception\IncorrectStreamPositionException;
8
use Furious\Psr7\Exception\InvalidArgumentException;
9
use Furious\Psr7\Exception\NotReadableStreamException;
10
use Furious\Psr7\Exception\NotSeekableStreamException;
11
use Furious\Psr7\Exception\NotWritableStreamException;
12
use Furious\Psr7\Exception\RuntimeException;
13
use Furious\Psr7\Exception\UnableToSeekException;
14
use Psr\Http\Message\StreamInterface;
15
use const SEEK_CUR;
16
use function is_string;
17
use function fwrite;
18
use function is_resource;
19
use function fopen;
20
use function fseek;
21
use function fread;
22
use function stream_get_meta_data;
23
use function clearstatcache;
24
use function fstat;
25
use function ftell;
26
use function feof;
27
use function stream_get_contents;
28
29
class Stream implements StreamInterface
30
{
31
    private bool $seekable;
1 ignored issue
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
32
    private bool $readable;
33
    private bool $writable;
34
    /** @var mixed */
35
    private $uri = null;
36
    private ?int $size = null;
37
    /** @var resource|null */
38
    private $stream;
39
40
    private const READ_HASH = [
41
        'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
42
        'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
43
        'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
44
        'x+t' => true, 'c+t' => true, 'a+' => true,
45
    ];
46
47
    private const WRITE_HASH = [
48
        'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
49
        'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
50
        'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
51
        'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
52
    ];
53
54
    public static function new($body = ''): StreamInterface
55
    {
56
        $new = new self();
57
58
        if ($body instanceof StreamInterface) {
59
            return $body;
60
        }
61
62
        if (is_string($body)) {
63
            $body = $new->initializeAsString($body);
64
        }
65
66
        if (is_array($body)) {
67
            $body = $new->initializeAsArray($body);
68
        }
69
70
        if (is_resource($body)) {
71
            $new->stream = $body;
72
            $meta = stream_get_meta_data($new->stream);
73
            $new->seekable = $meta['seekable'] and !fseek($new->stream, 0, SEEK_CUR);
74
            $new->readable = isset(self::READ_HASH[$meta['mode']]);
75
            $new->writable = isset(self::WRITE_HASH[$meta['mode']]);
76
            $new->uri = $new->getMetadata('uri');
77
78
            return $new;
79
        }
80
81
        throw new InvalidArgumentException('Body must be array, instance of StreamInterface or null');
82
    }
83
84
    private function initializeAsString(string $body)
85
    {
86
        if ($body === 'php://input') {
87
            $resource = fopen('php://input', 'rw+');
88
        } else {
89
            $resource = fopen('php://temp', 'rw+');
90
        }
91
92
        if (false === $resource) {
93
            throw new RuntimeException('Could not open file');
94
        }
95
96
        fwrite($resource, $body);
97
        return $resource;
98
    }
99
100
    private function initializeAsArray(array $body)
101
    {
102
        $body = json_encode($body);
103
        $resource = fopen('php://temp', 'rw+');
104
105
        if (false === $resource) {
106
            throw new RuntimeException('Could not open file');
107
        }
108
109
        fwrite($resource, $body);
110
        return $resource;
111
    }
112
113
    public function __toString()
114
    {
115
        if ($this->isSeekable()) {
116
            $this->seek(0);
117
        }
118
119
        return $this->getContents();
120
    }
121
122
    public function __destruct()
123
    {
124
        $this->close();
125
    }
126
127
    public function close(): void
128
    {
129
        if (isset($this->stream)) {
130
            if (is_resource($this->stream)) {
131
                fclose($this->stream);
132
            }
133
            $this->detach();
134
        }
135
    }
136
137
    /**
138
     * @return resource|null
139
     */
140
    public function detach()
141
    {
142
        if (!isset($this->stream)) {
143
            return null;
144
        }
145
146
        $result = $this->stream;
147
        unset($this->stream);
148
149
        $this->size = null;
150
        $this->uri = null;
151
        $this->readable = false;
152
        $this->writable = false;
153
        $this->seekable = false;
154
155
        return $result;
156
    }
157
158
    public function getSize(): ?int
159
    {
160
        if (null !== $this->size) {
161
            return $this->size;
162
        }
163
164
        if (!isset($this->stream)) {
165
            return null;
166
        }
167
168
        if ($this->uri) {
169
            clearstatcache(true, $this->uri);
170
        }
171
172
        $stats = fstat($this->stream);
173
        if (isset($stats['size'])) {
174
            $this->size = $stats['size'];
175
            return $this->size;
176
        }
177
178
        return null;
179
    }
180
181
    public function tell(): int
182
    {
183
        if (false === $result = ftell($this->stream)) {
184
            throw new IncorrectStreamPositionException();
185
        }
186
187
        return $result;
188
    }
189
190
    public function read($length): string
191
    {
192
        if (!$this->readable) {
193
            throw new NotReadableStreamException();
194
        }
195
196
        return fread($this->stream, $length);
197
    }
198
199
200
    public function eof(): bool
201
    {
202
        return !$this->stream or feof($this->stream);
203
    }
204
205
    public function isSeekable(): bool
206
    {
207
        return $this->seekable;
208
    }
209
210
    public function isWritable(): bool
211
    {
212
        return $this->writable;
213
    }
214
215
    public function isReadable(): bool
216
    {
217
        return $this->readable;
218
    }
219
220
    public function seek($offset, $whence = SEEK_SET): void
221
    {
222
        if (!$this->seekable) {
223
            throw new NotSeekableStreamException();
224
        }
225
226
        if (-1 === fseek($this->stream, $offset, $whence)) {
227
            throw new UnableToSeekException($offset, $whence);
228
        }
229
    }
230
231
    public function rewind(): void
232
    {
233
        $this->seek(0);
234
    }
235
236
    public function write($string): int
237
    {
238
        if (!$this->writable) {
239
            throw new NotWritableStreamException('Cannot write to a non-writable stream');
240
        }
241
242
        $this->size = null;
243
244
        $result = fwrite($this->stream, $string);
245
        if (false === $result) {
246
            throw new NotWritableStreamException();
247
        }
248
249
        return $result;
250
    }
251
252
    public function getContents(): string
253
    {
254
        if (!isset($this->stream)) {
255
            throw new NotReadableStreamException();
256
        }
257
258
        $contents = stream_get_contents($this->stream);
259
        if (false === $contents) {
260
            throw new NotReadableStreamException();
261
        }
262
263
        return $contents;
264
    }
265
266
    /**
267
     * @param string $key
268
     * @return array|mixed|null
269
     */
270
    public function getMetadata($key = null)
271
    {
272
        if (!isset($this->stream)) {
273
            return $key ? null : [];
274
        }
275
276
        $meta = stream_get_meta_data($this->stream);
277
278
        if (null === $key) {
279
            return $meta;
280
        }
281
282
        return $meta[$key] ?? null;
283
    }
284
}