Completed
Push — master ( 74ac7c...809da3 )
by Damien
02:39
created

MessageBody::setMetadata()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 25
rs 8.439
cc 5
eloc 13
nc 9
nop 1
1
<?php
2
/**
3
 * Veto.
4
 * PHP Microframework.
5
 *
6
 * @author Damien Walsh <[email protected]>
7
 * @copyright Damien Walsh 2013-2014
8
 * @version 0.1
9
 * @package veto
10
 */
11
namespace Veto\Http;
12
13
use Psr\Http\Message\StreamInterface;
14
15
/**
16
 * A HTTP Message Body, according to PSR-7.
17
 *
18
 * @since 0.1
19
 */
20
class MessageBody implements StreamInterface
21
{
22
    /**
23
     * Modes in which a stream is readable
24
     *
25
     * @var array
26
     * @link http://php.net/manual/function.fopen.php
27
     */
28
    protected static $readableModes = array('r', 'r+', 'w+', 'a+', 'x+', 'c+');
29
30
    /**
31
     * Modes in which a stream is writable
32
     *
33
     * @var array
34
     * @link http://php.net/manual/function.fopen.php
35
     */
36
    protected static $writableModes = array('r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+');
37
38
    /**
39
     * The stream underlying the message body
40
     *
41
     * @var resource
42
     */
43
    protected $stream;
44
45
    /**
46
     * The underlying stream's metadata
47
     *
48
     * @var null|array
49
     */
50
    protected $metadata;
51
52
    /**
53
     * Is the underlying stream readable?
54
     *
55
     * @var bool
56
     */
57
    protected $readable;
58
59
    /**
60
     * Is the underlying stream writable?
61
     *
62
     * @var bool
63
     */
64
    protected $writable;
65
66
    /**
67
     * Is the underlying stream seekable?
68
     *
69
     * @var bool
70
     */
71
    protected $seekable;
72
73
    /**
74
     * Create a new message body with the provided stream underlying it.
75
     *
76
     * @param resource $stream
77
     */
78
    public function __construct($stream)
79
    {
80
        if (!is_resource($stream)) {
81
            throw new \InvalidArgumentException(
82
                '\Veto\Http\Body::__construct() argument must be a PHP stream resource'
83
            );
84
        }
85
86
        $this->attach($stream);
87
    }
88
89
    /**
90
     * Attach a resource to this message body.
91
     *
92
     * @param resource $stream
93
     */
94
    public function attach($stream)
95
    {
96
        if (false === is_resource($stream)) {
97
            throw new \InvalidArgumentException(
98
                '\Veto\Http\Body::attach() argument must be a PHP stream resource'
99
            );
100
        }
101
102
        // If we are already attached, detach first
103
        if (true === $this->isAttached()) {
104
            $this->detach();
105
        }
106
107
        $this->stream = $stream;
108
        $this->setMetadata($stream);
109
    }
110
111
    /**
112
     * Separates any underlying resources from the stream.
113
     *
114
     * After the stream has been detached, the stream is in an unusable state.
115
     *
116
     * @return resource|null Underlying PHP stream, if any
117
     */
118
    public function detach()
119
    {
120
        $stream = $this->stream;
121
        $this->stream = null;
122
123
        return $stream;
124
    }
125
126
    /**
127
     * Check if a stream resource is already attached to this message body.
128
     *
129
     * @return bool
130
     */
131
    public function isAttached()
132
    {
133
        return is_resource($this->stream);
134
    }
135
136
    /**
137
     * Set the metadata state information on this object, sourced from the stream metadata.
138
     *
139
     * @param resource $stream
140
     */
141
    protected function setMetadata($stream)
142
    {
143
        $this->metadata = stream_get_meta_data($stream);
144
145
        // Check for readable modes
146
        $this->readable = false;
147
        foreach (self::$readableModes as $mode) {
148
            if (strpos($this->metadata['mode'], $mode) === 0) {
149
                $this->readable = true;
150
                break;
151
            }
152
        }
153
154
        // Check for writable modes
155
        $this->writable = false;
156
        foreach (self::$writableModes as $mode) {
157
            if (strpos($this->metadata['mode'], $mode) === 0) {
158
                $this->writable = true;
159
                break;
160
            }
161
        }
162
163
        // Is the underlying stream seekable?
164
        $this->seekable = $this->metadata['seekable'];
165
    }
166
167
    /**
168
     * Get the size of the stream if known
169
     *
170
     * @return int|null Returns the size in bytes if known, or null if unknown.
171
     */
172
    public function getSize()
173
    {
174
        if (true === $this->isAttached()) {
175
            $stats = fstat($this->stream);
176
            return isset($stats['size']) ? $stats['size'] : null;
177
        }
178
179
        return null;
180
    }
181
182
    /**
183
     * Returns the current position of the file read/write pointer
184
     *
185
     * @return int|bool Position of the file pointer or false on error.
186
     */
187
    public function tell()
188
    {
189
        return $this->isAttached() ? ftell($this->stream) : false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->isAttached() ? ft...$this->stream) : false; of type integer|false adds false to the return on line 189 which is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::tell of type integer. It seems like you forgot to handle an error condition.
Loading history...
190
    }
191
192
    /**
193
     * Returns true if the stream is at the end of the stream.
194
     *
195
     * @return bool
196
     */
197
    public function eof()
198
    {
199
        return $this->isAttached() ? feof($this->stream) : true;
200
    }
201
202
    /**
203
     * Returns whether or not the stream is seekable.
204
     *
205
     * @return bool
206
     */
207
    public function isSeekable()
208
    {
209
        return $this->isAttached() && $this->seekable;
210
    }
211
212
    /**
213
     * Seek to a position in the stream.
214
     *
215
     * @link http://www.php.net/manual/en/function.fseek.php
216
     * @param int $offset Stream offset
217
     * @param int $whence Specifies how the cursor position will be calculated
218
     *     based on the seek offset. Valid values are identical to the built-in
219
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
220
     *     offset bytes SEEK_CUR: Set position to current location plus offset
221
     *     SEEK_END: Set position to end-of-stream plus offset.
222
     * @return bool Returns TRUE on success or FALSE on failure.
223
     */
224
    public function seek($offset, $whence = SEEK_SET)
225
    {
226
        return $this->isAttached() && $this->isSeekable() ?
227
            fseek($this->stream, $offset, $whence) :
228
            false;
229
    }
230
231
    /**
232
     * Seek to the beginning of the stream.
233
     *
234
     * If the stream is not seekable, this method will return FALSE, indicating
235
     * failure; otherwise, it will perform a seek(0), and return the status of
236
     * that operation.
237
     *
238
     * @see seek()
239
     * @link http://www.php.net/manual/en/function.fseek.php
240
     * @return bool Returns TRUE on success or FALSE on failure.
241
     */
242
    public function rewind()
243
    {
244
        return $this->isAttached() && $this->isSeekable() ? rewind($this->stream) : false;
245
    }
246
247
    /**
248
     * Returns whether or not the stream is writable.
249
     *
250
     * @return bool
251
     */
252
    public function isWritable()
253
    {
254
        return is_null($this->writable) ? false : $this->writable;
255
    }
256
257
    /**
258
     * Write data to the stream.
259
     *
260
     * @param string $string The string that is to be written.
261
     * @return int|bool Returns the number of bytes written to the stream on
262
     *     success or FALSE on failure.
263
     */
264
    public function write($string)
265
    {
266
        return $this->isAttached() && $this->isWritable() ? fwrite($this->stream, $string) : false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->isAttached() && $...ream, $string) : false; of type integer|false adds false to the return on line 266 which is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::write of type integer. It seems like you forgot to handle an error condition.
Loading history...
267
    }
268
269
    /**
270
     * Returns whether or not the stream is readable.
271
     *
272
     * @return bool
273
     */
274
    public function isReadable()
275
    {
276
        return is_null($this->readable) ? false : $this->readable;
277
    }
278
279
    /**
280
     * Read data from the stream.
281
     *
282
     * @param int $length Read up to $length bytes from the object and return
283
     *     them. Fewer than $length bytes may be returned if underlying stream
284
     *     call returns fewer bytes.
285
     * @return string|false Returns the data read from the stream, false if
286
     *     unable to read or if an error occurs.
287
     */
288
    public function read($length)
289
    {
290
        return $this->isAttached() && $this->isReadable() ? fread($this->stream, $length) : false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->isAttached() && $...ream, $length) : false; of type string|false adds false to the return on line 290 which is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::read of type string. It seems like you forgot to handle an error condition.
Loading history...
291
    }
292
293
    /**
294
     * Returns the remaining contents in a string
295
     *
296
     * @return string
297
     */
298
    public function getContents()
299
    {
300
        return $this->isAttached() && $this->isReadable() ? stream_get_contents($this->stream) : '';
301
    }
302
303
    /**
304
     * Get stream metadata as an associative array or retrieve a specific key.
305
     *
306
     * The keys returned are identical to the keys returned from PHP's
307
     * stream_get_meta_data() function.
308
     *
309
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
310
     * @param string $key Specific metadata to retrieve.
311
     * @return array|mixed|null Returns an associative array if no key is
312
     *     provided. Returns a specific key value if a key is provided and the
313
     *     value is found, or null if the key is not found.
314
     */
315
    public function getMetadata($key = null)
316
    {
317
        if (true === is_null($key)) {
318
            return $this->metadata;
319
        }
320
321
        return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
322
    }
323
324
    /**
325
     * Closes the stream and any underlying resources.
326
     *
327
     * @return void
328
     */
329
    public function close()
330
    {
331
        fclose($this->stream);
332
    }
333
334
    /**
335
     * Read the entire contents of the message body into a PHP string.
336
     *
337
     * @return string
338
     */
339
    public function __toString()
340
    {
341
        return $this->isAttached() ? stream_get_contents($this->stream, -1, 0) : '';
342
    }
343
}
344