Completed
Push — master ( bc72c5...1b234b )
by richard
02:39
created

Stream::setOperations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace Almendra\Http\Psr\Messages;
4
5
use Psr\Http\Message\StreamInterface;
6
7
/**
8
 * Represent a message stream
9
 *
10
 * @package Almendra\Http
11
 */
12
class Stream implements StreamInterface
13
{
14
15
    /**
16
     * @var stream body / the resource
17
     */
18
    protected $body; 
19
20
    /**
21
     * @var The stream uri, if any 
22
     */
23
    protected $uri; 
24
25
    /**
26
     * @var Is the stream seekable? 
27
     */
28
    protected $seekable;
29
30
    /**
31
     * @var Is the stream readable? 
32
     */
33
    protected $readable;
34
35
    /**
36
     * @var Is the stream writable? 
37
     */
38
    protected $writable;
39
40
    /**
41
     * @var The stream metadata, if any 
42
     */
43
    protected $metaData = [];
44
45
    /**
46
     * @var The overriding options (size, uri, etc.) 
47
     */
48
    protected $options = [];
49
50
    /**
51
     * @var Default output format 
52
     */
53
    protected $defaultFormat; // 'JSON'
54
55
    /**
56
     * @var The accepted overriding options 
57
     */
58
    protected $overridingOptions = [
59
        'size',
60
        'uri',
61
        ];
62
63
    /**
64
     * Sets up the resource. 
65
     *
66
     * @param resource $stream
67
     * @param array $options         
68
     * @return void                 
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
69
     */
70
    public function __construct($stream, $metaData = [], $options = []) {
71
        if (!is_resource($stream)) {
72
            throw new \InvalidArgumentException("Stream must be a resource.");
73
        }
74
75
        // Attach the stream
76
        $this -> setBody($stream);
77
        $this -> setMetadata($stream, $metaData);
78
        $this -> setOptions($options);
79
    }
80
81
    /**
82
     * Sets the stream metadata.
83
     *
84
     * @param resource $stream         The stream
85
     * @param array $userData         The overriding user metadata
86
     * @return void                 
87
     */
88
    protected function setMetadata($stream, array $userData = []) {
89
        $meta = stream_get_meta_data($stream);
90
        $this -> metaData = array_merge($meta, $userData);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($meta, $userData) of type array is incompatible with the declared type object<Almendra\Http\Psr\Messages\The> of property $metaData.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
91
    }
92
93
    /**
94
     * Sets the options such as the size, effectively overriding those from the stream.
95
     *
96
     * @param array $options         The overriding options
97
     * @return void                 
98
     */
99
    protected function setOptions(array $options) {
100
        foreach ($this -> overridingOptions as $name) {
101
            if (isset($options[$name])) {
102
                $this -> $options[$name] = $options[$name];
103
            }
104
        }
105
    }
106
107
    /**
108
     * Reads all data from the stream into a string, from the beginning to end.
109
     *
110
     * This method MUST attempt to seek to the beginning of the stream before
111
     * reading data and read the stream until the end is reached.
112
     *
113
     * Warning: This could attempt to load a large amount of data into memory.
114
     *
115
     * This method MUST NOT raise an exception in order to conform with PHP's
116
     * string casting operations.
117
     *
118
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
119
     * @return string
120
     */
121
    public function __toString()
122
    {
123
        // protect against exception
124
        try {
125
            $this -> seek(0);
126
            $result = (string) stream_get_contents($this -> getBody());
127
            if ($this -> isJsonable() && ($this -> defaultFormat === 'JSON')) {
128
                $result = json_encode($result, JSON_PRETTY_PRINT);
129
            }
130
131
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class Almendra\Http\Psr\Messages\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
132
            $result = '';
133
        }
134
135
        return $result;
136
    }
137
138
    /**
139
     * Closes the stream and any underlying resources.
140
     *
141
     * @return void
142
     */
143
    public function close()
144
    {
145
        if (!isset($this -> body)) {
146
            return;
147
        }
148
149
        fclose($this -> body);
150
        $this -> detach();
151
    }
152
153
    /**
154
     * Separates any underlying resources from the stream.
155
     *
156
     * After the stream has been detached, the stream is in an unusable state.
157
     *
158
     * @return resource|null Underlying PHP stream, if any
159
     */
160
    public function detach()
161
    {
162
        if (!isset($this -> body)) {
163
            return null;
164
        }
165
166
        $this -> options = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type object<Almendra\Http\Psr\Messages\The> of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
167
        $this -> metaData = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type object<Almendra\Http\Psr\Messages\The> of property $metaData.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
168
        $this -> unsetOperations();
169
170
        $result = $this -> body;
171
        unset($this -> body);
172
173
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (Almendra\Http\Psr\Messages\Stream) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::detach of type resource|null.

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...
174
    }
175
176
    /**
177
     * Get the size of the stream if known.
178
     *
179
     * @return int|null Returns the size in bytes if known, or null if unknown.
180
     */
181
    public function getSize()
182
    {
183
        if (!isset($this -> options['size'])) {
184
            try {
185
                $fstat = fstat($this -> body);
186
                if (!isset($fstat['size'])) {
187
                    return null;
188
                }
189
190
                $this -> options['size'] = $fstat['size'];
191
            } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class Almendra\Http\Psr\Messages\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
192
                return null; // fails to determine the size
193
            }
194
        }
195
196
        return $this -> options['size'];
197
    }
198
199
    /**
200
     * Returns the current position of the file read/write pointer
201
     *
202
     * @return int Position of the file pointer
203
     * @throws \RuntimeException on error.
204
     */
205
    public function tell()
206
    {
207
        if (!$result = ftell($this -> body)) {
208
            throw new \RuntimeException("Cannot determine the file pointer position.");
209
        }
210
211
        return $result;
212
    }
213
214
    /**
215
     * Returns true if the stream is at the end of the stream.
216
     *
217
     * @return bool
218
     */
219
    public function eof()
220
    {
221
        // should use feof
222
        return (!$this->body || feof($this->body));
223
    }
224
225
    /**
226
     * Returns whether or not the stream is seekable.
227
     *
228
     * @return bool
229
     */
230
    public function isSeekable()
231
    {
232
        return $this -> seekable;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->seekable; (Almendra\Http\Psr\Messages\Is) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::isSeekable of type boolean.

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...
233
    }
234
235
    /**
236
     * Seek to a position in the stream.
237
     *
238
     * @link http://www.php.net/manual/en/function.fseek.php
239
     * @param int $offset Stream offset
240
     * @param int $whence Specifies how the cursor position will be calculated
241
     *     based on the seek offset. Valid values are identical to the built-in
242
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
243
     *     offset bytes SEEK_CUR: Set position to current location plus offset
244
     *     SEEK_END: Set position to end-of-stream plus offset.
245
     * @throws \RuntimeException on failure.
246
     */
247
    public function seek($offset, $whence = SEEK_SET)
248
    {
249
        if (!$this -> isSeekable()) { 
250
            throw new \RuntimeException("The stream is not seekable.");
251
        }
252
253
        if (fseek($this -> body, $offset, $whence) === -1) {
254
            throw new \RuntimeException("Could not seek stream position.");   
255
        }
256
    }
257
258
    /**
259
     * Seek to the beginning of the stream.
260
     *
261
     * If the stream is not seekable, this method will raise an exception;
262
     * otherwise, it will perform a seek(0).
263
     *
264
     * @see seek()
265
     * @link http://www.php.net/manual/en/function.fseek.php
266
     * @throws \RuntimeException on failure.
267
     */
268
    public function rewind()
269
    {
270
        $this -> seek(0);
271
    }
272
273
    /**
274
     * Returns whether or not the stream is writable.
275
     *
276
     * @return bool
277
     */
278
    public function isWritable()
279
    {
280
        return $this -> writable;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->writable; (Almendra\Http\Psr\Messages\Is) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::isWritable of type boolean.

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...
281
    }
282
283
    /**
284
     * Write data to the stream.
285
     *
286
     * @param string $string The string that is to be written.
287
     * @return int Returns the number of bytes written to the stream.
288
     * @throws \RuntimeException on failure.
289
     */
290
    public function write($string)
291
    {
292
        if (!$this -> isWritable()) {
293
            throw new \RuntimeException("Stream is not writable.");
294
        }
295
296
        $this -> options['size'] = null;
297
        if (!($result = fwrite($this -> body, $string))) {
298
            throw new \RuntimeException("Error while writing to stream.");
299
        }
300
301
        return $result;
302
    }
303
304
    /**
305
     * Returns whether or not the stream is readable.
306
     *
307
     * @return bool
308
     */
309
    public function isReadable()
310
    {
311
        return $this -> readable;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->readable; (Almendra\Http\Psr\Messages\Is) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::isReadable of type boolean.

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...
312
    }
313
314
    /**
315
     * Read data from the stream.
316
     *
317
     * @param int $length Read up to $length bytes from the object and return
318
     *     them. Fewer than $length bytes may be returned if underlying stream
319
     *     call returns fewer bytes.
320
     * @return string Returns the data read from the stream, or an empty string
321
     *     if no bytes are available.
322
     * @throws \RuntimeException if an error occurs.
323
     */
324
    public function read($length)
325
    {
326
        if (!$this -> isReadable()) {
327
            throw new \RuntimeException("The stream is not readable.");
328
        }
329
330
        return fread($this -> body, $length);
331
    }
332
333
    /**
334
     * Returns the remaining contents in a string
335
     *
336
     * @return string
337
     * @throws \RuntimeException if unable to read or an error occurs while
338
     *     reading.
339
     */
340
    public function getContents()
341
    {
342
        if (!$result = fread($this -> body, $this -> getSize())) {
343
            throw new \RuntimeException("Unable to read or error while reading the stream.");
344
        }
345
346
        return $result;
347
    }
348
349
    /**
350
     * Get stream metadata as an associative array or retrieve a specific key.
351
     *
352
     * The keys returned are identical to the keys returned from PHP's
353
     * stream_get_meta_data() function.
354
     *
355
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
356
     * @param string $key Specific metadata to retrieve.
357
     * @return array|mixed|null Returns an associative array if no key is
358
     *     provided. Returns a specific key value if a key is provided and the
359
     *     value is found, or null if the key is not found.
360
     */
361
    public function getMetadata($key = null)
362
    {
363
        if (!$key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
364
            return [];
365
        }
366
367
        if (isset($this -> metaData)) {
368
            $meta = $this -> metaData;
369
        } else {
370
            $meta = stream_get_meta_data($this -> getBody());
371
        }
372
373
        return isset($meta[$key]) ? $meta[$key] : null;
374
    }
375
376
    /**
377
     * Gets the value of body.
378
     *
379
     * @return mixed
380
     */
381
    public function getBody()
382
    {
383
        if (!isset($this -> body)) {
384
            return null;
385
        }
386
387
        return $this->body;
388
    }
389
390
    /**
391
     * Sets the value of body.
392
     *
393
     * @param mixed $body the body
394
     *
395
     * @return self
396
     */
397
    protected function setBody($body)
398
    {
399
        $this->body = $body;
400
401
        return $this;
402
    }
403
404
    /**
405
     * Is the stream JSONable? 
406
     *
407
     * @return boolean                 
408
     */
409
    protected function isJsonable() {
410
        $contents = $this -> getBody();
411
        try {
412
            $contents = json_encode($contents, JSON_PRETTY_PRINT);
413
            if ($contents === null ||
414
                $contents === '') {
415
                return false;
416
            }
417
        } catch (Exception $e) { // Invalid operation
0 ignored issues
show
Bug introduced by
The class Almendra\Http\Psr\Messages\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
418
            return false;
419
        }
420
        
421
        return true;
422
    }
423
424
    /**
425
     * Refresh the stream options such as size.
426
     *
427
     * @param type name         description
428
     * @return type                 description
429
     */
430
    // protected function refresh($which = null) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
431
    //     if (isset($which) && null !== $which) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
432
    //         $this -> 
433
    //     }
434
    // }
435
436
    protected function setOperations() {
437
        // write
438
        // read
439
        // seek
440
        // ...
441
    }
442
443
    protected function unsetOperations() {
444
        $this -> writable = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type object<Almendra\Http\Psr\Messages\Is> of property $writable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
445
        $this -> readable = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type object<Almendra\Http\Psr\Messages\Is> of property $readable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
446
        $this -> seekable = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type object<Almendra\Http\Psr\Messages\Is> of property $seekable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
447
    }
448
449
    
450
451
    /**
452
     * Free resources
453
     *
454
     * @return void                 
455
     */
456
    public function __destruct()
457
    {
458
        $this -> close();
459
    }
460
}
461