ResponseStream   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Test Coverage

Coverage 54.17%

Importance

Changes 0
Metric Value
eloc 93
dl 0
loc 327
ccs 39
cts 72
cp 0.5417
rs 9.68
c 0
b 0
f 0
wmc 34

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
A read() 0 3 2
A isSeekable() 0 3 1
A isWritable() 0 3 1
A __toString() 0 8 2
A detach() 0 11 1
A close() 0 6 2
A tell() 0 3 2
A seek() 0 3 2
A getMetadata() 0 8 4
A __destruct() 0 3 1
A getSize() 0 22 5
A eof() 0 3 2
A write() 0 5 2
A rewind() 0 3 2
A getContents() 0 3 2
A isReadable() 0 3 1
1
<?php
2
3
namespace PhpZip\IO\Stream;
4
5
use Psr\Http\Message\StreamInterface;
6
7
/**
8
 * Implement PSR Message Stream.
9
 */
10
class ResponseStream implements StreamInterface
11
{
12
    /** @var array */
13
    private static $readWriteHash = [
14
        'read' => [
15
            'r' => true,
16
            'w+' => true,
17
            'r+' => true,
18
            'x+' => true,
19
            'c+' => true,
20
            'rb' => true,
21
            'w+b' => true,
22
            'r+b' => true,
23
            'x+b' => true,
24
            'c+b' => true,
25
            'rt' => true,
26
            'w+t' => true,
27
            'r+t' => true,
28
            'x+t' => true,
29
            'c+t' => true,
30
            'a+' => true,
31
        ],
32
        'write' => [
33
            'w' => true,
34
            'w+' => true,
35
            'rw' => true,
36
            'r+' => true,
37
            'x+' => true,
38
            'c+' => true,
39
            'wb' => true,
40
            'w+b' => true,
41
            'r+b' => true,
42
            'x+b' => true,
43
            'c+b' => true,
44
            'w+t' => true,
45
            'r+t' => true,
46
            'x+t' => true,
47
            'c+t' => true,
48
            'a' => true,
49
            'a+' => true,
50
        ],
51
    ];
52
53
    /** @var resource */
54
    private $stream;
55
56
    /** @var int|null */
57
    private $size;
58
59
    /** @var bool */
60
    private $seekable;
61
62
    /** @var bool */
63
    private $readable;
64
65
    /** @var bool */
66
    private $writable;
67
68
    /** @var string|null */
69
    private $uri;
70
71
    /**
72
     * @param resource $stream stream resource to wrap
73
     *
74
     * @throws \InvalidArgumentException if the stream is not a stream resource
75
     */
76 2
    public function __construct($stream)
77
    {
78 2
        if (!\is_resource($stream)) {
79
            throw new \InvalidArgumentException('Stream must be a resource');
80
        }
81 2
        $this->stream = $stream;
82 2
        $meta = stream_get_meta_data($this->stream);
83 2
        $this->seekable = $meta['seekable'];
84 2
        $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
85 2
        $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
86 2
        $this->uri = $this->getMetadata('uri');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getMetadata('uri') can also be of type array. However, the property $uri is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
87 2
    }
88
89
    /**
90
     * Get stream metadata as an associative array or retrieve a specific key.
91
     *
92
     * The keys returned are identical to the keys returned from PHP's
93
     * stream_get_meta_data() function.
94
     *
95
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
96
     *
97
     * @param string $key specific metadata to retrieve
98
     *
99
     * @return array|mixed|null Returns an associative array if no key is
100
     *                          provided. Returns a specific key value if a key is provided and the
101
     *                          value is found, or null if the key is not found.
102
     */
103 2
    public function getMetadata($key = null)
104
    {
105 2
        if (!$this->stream) {
106
            return $key ? null : [];
107
        }
108 2
        $meta = stream_get_meta_data($this->stream);
109
110 2
        return isset($meta[$key]) ? $meta[$key] : null;
111
    }
112
113
    /**
114
     * Reads all data from the stream into a string, from the beginning to end.
115
     *
116
     * This method MUST attempt to seek to the beginning of the stream before
117
     * reading data and read the stream until the end is reached.
118
     *
119
     * Warning: This could attempt to load a large amount of data into memory.
120
     *
121
     * This method MUST NOT raise an exception in order to conform with PHP's
122
     * string casting operations.
123
     *
124
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
125
     *
126
     * @return string
127
     */
128
    public function __toString()
129
    {
130
        if (!$this->stream) {
131
            return '';
132
        }
133
        $this->rewind();
134
135
        return (string) stream_get_contents($this->stream);
136
    }
137
138
    /**
139
     * Seek to the beginning of the stream.
140
     *
141
     * If the stream is not seekable, this method will raise an exception;
142
     * otherwise, it will perform a seek(0).
143
     *
144
     * @throws \RuntimeException on failure
145
     *
146
     * @see http://www.php.net/manual/en/function.fseek.php
147
     * @see seek()
148
     */
149
    public function rewind()
150
    {
151
        $this->seekable && rewind($this->stream);
152
    }
153
154
    /**
155
     * Get the size of the stream if known.
156
     *
157
     * @return int|null returns the size in bytes if known, or null if unknown
158
     */
159 2
    public function getSize()
160
    {
161 2
        if ($this->size !== null) {
162
            return $this->size;
163
        }
164
165 2
        if (!$this->stream) {
166
            return null;
167
        }
168
        // Clear the stat cache if the stream has a URI
169 2
        if ($this->uri !== null) {
170 2
            clearstatcache(true, $this->uri);
171
        }
172 2
        $stats = fstat($this->stream);
173
174 2
        if (isset($stats['size'])) {
175 2
            $this->size = $stats['size'];
176
177 2
            return $this->size;
178
        }
179
180
        return null;
181
    }
182
183
    /**
184
     * Returns the current position of the file read/write pointer.
185
     *
186
     * @throws \RuntimeException on error
187
     *
188
     * @return int Position of the file pointer
189
     */
190
    public function tell()
191
    {
192
        return $this->stream ? ftell($this->stream) : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->stream ? f...($this->stream) : false could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
193
    }
194
195
    /**
196
     * Returns true if the stream is at the end of the stream.
197
     *
198
     * @return bool
199
     */
200
    public function eof()
201
    {
202
        return !$this->stream || feof($this->stream);
203
    }
204
205
    /**
206
     * Returns whether or not the stream is seekable.
207
     *
208
     * @return bool
209
     */
210
    public function isSeekable()
211
    {
212
        return $this->seekable;
213
    }
214
215
    /**
216
     * Seek to a position in the stream.
217
     *
218
     * @see http://www.php.net/manual/en/function.fseek.php
219
     *
220
     * @param int $offset Stream offset
221
     * @param int $whence Specifies how the cursor position will be calculated
222
     *                    based on the seek offset. Valid values are identical to the built-in
223
     *                    PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
224
     *                    offset bytes SEEK_CUR: Set position to current location plus offset
225
     *                    SEEK_END: Set position to end-of-stream plus offset.
226
     *
227
     * @throws \RuntimeException on failure
228
     */
229
    public function seek($offset, $whence = \SEEK_SET)
230
    {
231
        $this->seekable && fseek($this->stream, $offset, $whence);
232
    }
233
234
    /**
235
     * Returns whether or not the stream is writable.
236
     *
237
     * @return bool
238
     */
239
    public function isWritable()
240
    {
241
        return $this->writable;
242
    }
243
244
    /**
245
     * Write data to the stream.
246
     *
247
     * @param string $string the string that is to be written
248
     *
249
     * @throws \RuntimeException on failure
250
     *
251
     * @return int returns the number of bytes written to the stream
252
     */
253
    public function write($string)
254
    {
255
        $this->size = null;
256
257
        return $this->writable ? fwrite($this->stream, $string) : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->writable ?...tream, $string) : false could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
258
    }
259
260
    /**
261
     * Returns whether or not the stream is readable.
262
     *
263
     * @return bool
264
     */
265
    public function isReadable()
266
    {
267
        return $this->readable;
268
    }
269
270
    /**
271
     * Read data from the stream.
272
     *
273
     * @param int $length Read up to $length bytes from the object and return
274
     *                    them. Fewer than $length bytes may be returned if underlying stream
275
     *                    call returns fewer bytes.
276
     *
277
     * @throws \RuntimeException if an error occurs
278
     *
279
     * @return string returns the data read from the stream, or an empty string
280
     *                if no bytes are available
281
     */
282
    public function read($length)
283
    {
284
        return $this->readable ? fread($this->stream, $length) : '';
285
    }
286
287
    /**
288
     * Returns the remaining contents in a string.
289
     *
290
     * @throws \RuntimeException if unable to read or an error occurs while
291
     *                           reading
292
     *
293
     * @return string
294
     */
295
    public function getContents()
296
    {
297
        return $this->stream ? stream_get_contents($this->stream) : '';
298
    }
299
300
    /**
301
     * Closes the stream when the destructed.
302
     */
303 2
    public function __destruct()
304
    {
305 2
        $this->close();
306 2
    }
307
308
    /**
309
     * Closes the stream and any underlying resources.
310
     */
311 2
    public function close()
312
    {
313 2
        if (\is_resource($this->stream)) {
314 2
            fclose($this->stream);
315
        }
316 2
        $this->detach();
317 2
    }
318
319
    /**
320
     * Separates any underlying resources from the stream.
321
     *
322
     * After the stream has been detached, the stream is in an unusable state.
323
     *
324
     * @return resource|null Underlying PHP stream, if any
325
     */
326 2
    public function detach()
327
    {
328 2
        $result = $this->stream;
329 2
        $this->stream = null;
330 2
        $this->size = null;
331 2
        $this->uri = null;
332 2
        $this->readable = false;
333 2
        $this->writable = false;
334 2
        $this->seekable = false;
335
336 2
        return $result;
337
    }
338
}
339