Completed
Push — master ( 5ea837...9dec3c )
by Paul
9s
created

Stream   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 42
c 3
b 0
f 0
lcom 1
cbo 0
dl 0
loc 326
rs 8.295

17 Methods

Rating   Name   Duplication   Size   Complexity  
A read() 0 12 4
A __construct() 0 14 3
A __toString() 0 8 2
A close() 0 9 2
A detach() 0 7 1
B attach() 0 23 5
A getSize() 0 10 2
A tell() 0 8 2
A eof() 0 8 2
A isSeekable() 0 10 2
A seek() 0 10 3
A rewind() 0 10 2
A isWritable() 0 10 2
A write() 0 8 2
A isReadable() 0 11 3
A getContents() 0 8 2
A getMetadata() 0 13 3

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
 * This file is part of the PPI Framework.
4
 *
5
 * @copyright  Copyright (c) 2011-2016 Paul Dragoonis <[email protected]>
6
 * @license    http://opensource.org/licenses/mit-license.php MIT
7
 *
8
 * @link       http://www.ppi.io
9
 */
10
11
namespace PPI\Framework\Http;
12
13
use InvalidArgumentException;
14
use Psr\Http\Message\StreamInterface;
15
16
/**
17
 * Implementation of PSR HTTP streams.
18
 *
19
 * This is a PHP-5.3 backport of Phly\Http\Stream authored by Matthew Weier O'Phinney.
20
 *
21
 * @see https://github.com/phly/http/blob/master/src/Stream.php
22
 *
23
 * @author Vítor Brandão <[email protected]>
24
 */
25
class Stream implements StreamInterface
26
{
27
    /**
28
     * @var resource
29
     */
30
    protected $resource;
31
32
    /**
33
     * @var string|resource
34
     */
35
    protected $stream;
36
37
    /**
38
     * @param string|resource $stream
39
     * @param string          $mode   Mode with which to open stream
40
     *
41
     * @throws InvalidArgumentException
42
     */
43
    public function __construct($stream, $mode = 'r')
44
    {
45
        $this->stream = $stream;
46
47
        if (is_resource($stream)) {
48
            $this->resource = $stream;
49
        } elseif (is_string($stream)) {
50
            $this->resource = fopen($stream, $mode);
51
        } else {
52
            throw new InvalidArgumentException(
53
                'Invalid stream provided; must be a string stream identifier or resource'
54
            );
55
        }
56
    }
57
58
    /**
59
     * Reads all data from the stream into a string, from the beginning to end.
60
     *
61
     * This method MUST attempt to seek to the beginning of the stream before
62
     * reading data and read the stream until the end is reached.
63
     *
64
     * Warning: This could attempt to load a large amount of data into memory.
65
     *
66
     * @return string
67
     */
68
    public function __toString()
69
    {
70
        if (! $this->isReadable()) {
71
            return '';
72
        }
73
74
        return stream_get_contents($this->resource, -1, 0);
75
    }
76
77
    /**
78
     * Closes the stream and any underlying resources.
79
     */
80
    public function close()
81
    {
82
        if (! $this->resource) {
83
            return;
84
        }
85
86
        $resource = $this->detach();
87
        fclose($resource);
88
    }
89
90
    /**
91
     * Separates any underlying resources from the stream.
92
     *
93
     * After the stream has been detached, the stream is in an unusable state.
94
     *
95
     * @return resource|null
96
     */
97
    public function detach()
98
    {
99
        $resource       = $this->resource;
100
        $this->resource = null;
101
102
        return $resource;
103
    }
104
105
    /**
106
     * Attach a new resource to the instance.
107
     *
108
     * @param resource|string $resource Resource to attach, or a string
109
     *                                  representing the resource to attach.
110
     * @param string          $mode     If a non-resource is provided, the mode to use
111
     *                                  when creating the resource.
112
     *
113
     * @throws InvalidArgumentException If a non-resource or non-string is provided,
114
     *                                  raises an exception.
115
     */
116
    public function attach($resource, $mode = 'r')
117
    {
118
        $error = null;
119
        if (! is_resource($resource) && is_string($resource)) {
120
            set_error_handler(function ($e) use (&$error) {
121
                $error = $e;
122
            }, E_WARNING);
123
            $resource = fopen($resource, $mode);
124
            restore_error_handler();
125
        }
126
127
        if ($error) {
128
            throw new InvalidArgumentException('Invalid stream reference provided');
129
        }
130
131
        if (! is_resource($resource)) {
132
            throw new InvalidArgumentException(
133
                'Invalid stream provided; must be a string stream identifier or resource'
134
            );
135
        }
136
137
        $this->resource = $resource;
138
    }
139
140
    /**
141
     * Get the size of the stream if known.
142
     *
143
     * @return int|null Returns the size in bytes if known, or null if unknown
144
     */
145
    public function getSize()
146
    {
147
        if (null === $this->resource) {
148
            return;
149
        }
150
151
        $stats = fstat($this->resource);
152
153
        return $stats['size'];
154
    }
155
156
    /**
157
     * Returns the current position of the file read/write pointer.
158
     *
159
     * @return int|bool Position of the file pointer or false on error
160
     */
161
    public function tell()
162
    {
163
        if (! $this->resource) {
164
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::tell of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
165
        }
166
167
        return ftell($this->resource);
168
    }
169
170
    /**
171
     * Returns true if the stream is at the end of the stream.
172
     *
173
     * @return bool
174
     */
175
    public function eof()
176
    {
177
        if (! $this->resource) {
178
            return true;
179
        }
180
181
        return feof($this->resource);
182
    }
183
184
    /**
185
     * Returns whether or not the stream is seekable.
186
     *
187
     * @return bool
188
     */
189
    public function isSeekable()
190
    {
191
        if (! $this->resource) {
192
            return false;
193
        }
194
195
        $meta = stream_get_meta_data($this->resource);
196
197
        return $meta['seekable'];
198
    }
199
200
    /**
201
     * Seek to a position in the stream.
202
     *
203
     * @param int $offset Stream offset
204
     * @param int $whence Specifies how the cursor position will be calculated
205
     *                    based on the seek offset. Valid values are identical
206
     *                    to the built-in PHP $whence values for `fseek()`.
207
     *                    SEEK_SET: Set position equal to offset bytes
208
     *                    SEEK_CUR: Set position to current location plus offset
209
     *                    SEEK_END: Set position to end-of-stream plus offset
210
     *
211
     * @return bool Returns TRUE on success or FALSE on failure
212
     *
213
     * @link   http://www.php.net/manual/en/function.fseek.php
214
     */
215
    public function seek($offset, $whence = SEEK_SET)
216
    {
217
        if (! $this->resource || ! $this->isSeekable()) {
218
            return false;
219
        }
220
221
        $result = fseek($this->resource, $offset, $whence);
222
223
        return 0 === $result;
224
    }
225
226
    /**
227
     * Rewind the stream.
228
     *
229
     * @return bool Returns TRUE on success, FALSE on failure
230
     */
231
    public function rewind()
232
    {
233
        if (! $this->isSeekable()) {
234
            return false;
235
        }
236
237
        $result = fseek($this->resource, 0);
238
239
        return 0 === $result;
240
    }
241
242
    /**
243
     * Returns whether or not the stream is writable.
244
     *
245
     * @return bool
246
     */
247
    public function isWritable()
248
    {
249
        if (! $this->resource) {
250
            return false;
251
        }
252
253
        $meta = stream_get_meta_data($this->resource);
254
255
        return is_writable($meta['uri']);
256
    }
257
258
    /**
259
     * Write data to the stream.
260
     *
261
     * @param string $string The string that is to be written.
262
     *
263
     * @return int|bool Returns the number of bytes written to the stream on
264
     *                  success or FALSE on failure.
265
     */
266
    public function write($string)
267
    {
268
        if (! $this->resource) {
269
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::write of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
270
        }
271
272
        return fwrite($this->resource, $string);
273
    }
274
275
    /**
276
     * Returns whether or not the stream is readable.
277
     *
278
     * @return bool
279
     */
280
    public function isReadable()
281
    {
282
        if (! $this->resource) {
283
            return false;
284
        }
285
286
        $meta = stream_get_meta_data($this->resource);
287
        $mode = $meta['mode'];
288
289
        return strstr($mode, 'r') || strstr($mode, '+');
290
    }
291
292
    /**
293
     * Read data from the stream.
294
     *
295
     * @param int $length Read up to $length bytes from the object and return
296
     *                    them. Fewer than $length bytes may be returned if
297
     *                    underlying stream call returns fewer bytes.
298
     *
299
     * @return string|false Returns the data read from the stream; in the event
300
     *                      of an error or inability to read, can return boolean
301
     *                      false.
302
     */
303
    public function read($length)
304
    {
305
        if (! $this->resource || ! $this->isReadable()) {
306
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::read of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
307
        }
308
309
        if ($this->eof()) {
310
            return '';
311
        }
312
313
        return fread($this->resource, $length);
314
    }
315
316
    /**
317
     * Returns the remaining contents in a string, up to maxlength bytes.
318
     *
319
     * @return string
320
     */
321
    public function getContents()
322
    {
323
        if (! $this->isReadable()) {
324
            return '';
325
        }
326
327
        return stream_get_contents($this->resource);
328
    }
329
330
    /**
331
     * Retrieve metadata from the underlying stream.
332
     *
333
     * @see http://php.net/stream_get_meta_data for a description of the expected output.
334
     *
335
     * @return array
336
     */
337
    public function getMetadata($key = null)
338
    {
339
        if (null === $key) {
340
            return stream_get_meta_data($this->resource);
341
        }
342
343
        $metadata = stream_get_meta_data($this->resource);
344
        if (! array_key_exists($key, $metadata)) {
345
            return;
346
        }
347
348
        return $metadata[$key];
349
    }
350
}
351