Completed
Push — releases/v0.2.1 ( e10be0...d94e86 )
by Luke
03:17
created

IteratorStream::isReadable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * CSVelte: Slender, elegant CSV for PHP.
4
 *
5
 * Inspired by Python's CSV module and Frictionless Data and the W3C's CSV
6
 * standardization efforts, CSVelte was written in an effort to take all the
7
 * suck out of working with CSV.
8
 *
9
 * @version   v0.2.1
10
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
11
 * @author    Luke Visinoni <[email protected]>
12
 * @license   https://github.com/deni-zen/csvelte/blob/master/LICENSE The MIT License (MIT)
13
 */
14
namespace CSVelte\IO;
15
16
use \Iterator;
17
use CSVelte\IO\BufferStream;
18
use CSVelte\Traits\IsReadable;
19
use CSVelte\Traits\IsWritable;
20
use CSVelte\Contract\Streamable;
21
22
/**
23
 * Iterator Stream.
24
 *
25
 * A read-only stream that uses an iterable to continuously fill up a buffer as
26
 * read operations deplete it.
27
 *
28
 * @package    CSVelte
29
 * @subpackage CSVelte\IO
30
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
31
 * @author     Luke Visinoni <[email protected]>
32
 * @since      v0.2.1
33
 */
34
class IteratorStream implements Streamable
35
{
36
    use IsReadable, IsWritable;
37
38
    /**
39
     * Buffer stream
40
     * @var \CSVelte\IO\BufferStream A BufferStream object
41
     */
42
    protected $buffer;
43
44
    protected $overflow;
45
46
    /**
47
     * Is stream readable?
48
     *
49
     * @var boolean Whether stream is readable
50
     */
51
    protected $readable = true;
52
53
    /**
54
     * Is stream writable?
55
     *
56
     * @var boolean Whether stream is writable
57
     */
58
    protected $writable = false;
59
60
    /**
61
     * Is stream seekable?
62
     *
63
     * @var boolean Whether stream is seekable
64
     */
65
    protected $seekable = false;
66
67
    /**
68
     * @var array Any additional options / meta data
69
     */
70
    protected $meta = [
71
72
    ];
73
74
    /**
75
     * Instantiate an iterator stream
76
     *
77
     * Instantiate a new iterator stream. The iterator is used to continually
78
     * refill a buffer as it is drained by read operations.
79
     *
80
     * @param \Iterator The iterator to stream data from
81
     * @param \CSVelte\IO\BufferIterator|null Either a buffer or null (to use
82
     *     default buffer)
83
     */
84 14
    public function __construct(Iterator $iter, $buffer = null)
85
    {
86 14
        $this->iter = $iter;
0 ignored issues
show
Bug introduced by
The property iter does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
87 14
        if (!($buffer instanceof BufferStream)) {
88 9
            $buffer = new BufferStream();
89 9
        }
90 14
        $this->buffer = $buffer;
91 14
    }
92
93
    /**
94
     * Readability accessor.
95
     *
96
     * Despite the fact that any class that implements this interface must also
97
     * define methods such as read and readLine, that is no guarantee that an
98
     * object will necessarily be readable. This method should tell the user
99
     * whether a stream is, in fact, readable.
100
     *
101
     * @return boolean True if readable, false otherwise
102
     */
103 1
    public function isReadable()
104
    {
105 1
        return $this->readable;
106
    }
107
108 10
    public function read($bytes)
109
    {
110 10
        $data = '';
111 10
        while (strlen($data) < $bytes) {
112 10
            if ($this->buffer->isEmpty()) {
113 10
                $this->inflateBuffer();
114 10
            }
115 10
            if (!$read = $this->buffer->read($bytes - strlen($data))) {
116 4
                break;
117
            }
118 10
            $data .= $read;
119 10
        }
120 10
        return $data;
121
    }
122
123 10
    protected function inflateBuffer()
124
    {
125 10
        while (!$this->buffer->isFull() && $this->iter->valid()) {
126 10
            $data = $this->iter->current();
127 10
            $this->buffer->write($data);
128 10
            $this->iter->next();
129 10
        }
130 10
    }
131
132
    /**
133
     * Read the entire stream, beginning to end.
134
     *
135
     * In most stream implementations, __toString() differs from getContents()
136
     * in that it returns the entire stream rather than just the remainder, but
137
     * due to the way this stream works (sort of like a conveyor belt), this
138
     * method is an alias to getContents()
139
     *
140
     * @return string The entire stream, beginning to end
141
     */
142 3
    public function __toString()
143
    {
144 3
        $this->rewind();
145 3
        return $this->getContents();
146
    }
147
148
    /**
149
     * Read the remainder of the stream
150
     *
151
     * @return string The remainder of the stream
152
     */
153 4
    public function getContents()
154
    {
155 4
        $contents = '';
156 4
        while (!$this->eof()) {
157 4
            $contents .= $this->read(
158
                // kind of arbitrary... we have to specify something for the
159
                // chunk length, so I just used the buffer's "high water mark"
160 4
                $this->buffer->getMetadata('hwm')
161 4
            );
162 4
        }
163 4
        return $contents;
164
    }
165
166
    /**
167
     * Return the size (in bytes) of this stream (if known).
168
     *
169
     * @return int|null Size (in bytes) of this stream
170
     */
171 1
    public function getSize()
172
    {
173
        // no way to know so return null
174 1
    }
175
176
    /**
177
     * Return the current position within the stream/readable
178
     *
179
     * @return int The current position within readable
180
     */
181
    public function tell()
182
    {
183
        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 CSVelte\Contract\Streamable::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...
184
    }
185
186
    /**
187
     * Determine whether the end of the stream has been reached
188
     *
189
     * @return boolean Whether we're at the end of the stream
190
     */
191 4
    public function eof()
192
    {
193
        return (
194 4
            !$this->iter->valid() &&
195 4
            $this->buffer->eof()
196 4
        );
197
    }
198
199
    /**
200
     * Rewind to beginning of stream
201
     */
202 3
    public function rewind()
203
    {
204 3
        $this->iter->rewind();
205 3
        $this->buffer->rewind();
206 3
    }
207
208
    /**
209
     * Get stream metadata as an associative array or retrieve a specific key.
210
     *
211
     * The keys returned are identical to the keys returned from PHP's
212
     * stream_get_meta_data() function.
213
     *
214
     * @param string $key Specific metadata to retrieve.
215
     * @return array|mixed|null Returns an associative array if no key is
216
     *     provided. Returns a specific key value if a key is provided and the
217
     *     value is found, or null if the key is not found.
218
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
219
     */
220 2
    public function getMetadata($key = null)
221
    {
222 2
        if (!is_null($key)) {
223 1
            return isset($this->meta[$key]) ? $this->meta[$key] : null;
224
        }
225 1
        return $this->meta;
226
    }
227
228
    /**
229
     * Closes the stream and any underlying resources.
230
     *
231
     * @return void
232
     */
233
    public function close()
234
    {
235
        $buff = $this->buffer->close();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $buff is correct as $this->buffer->close() (which targets CSVelte\IO\BufferStream::close()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
236
        $iter = true;
237
        if (method_exists($this->iter, 'close')) {
238
            $iter = $this->iter->close();
239
        }
240
        return $buff && $iter;
241
    }
242
243
    /**
244
     * Separates any underlying resources from the stream.
245
     *
246
     * After the stream has been detached, the stream is in an unusable state.
247
     *
248
     * @return string|null Underlying PHP stream, if any
249
     * @todo I'm not sure what detach is for so I don't know whether what I'm
250
     *     doing here is right. The reason I have the method at all is because
251
     *     psr7 StreamInterface has one.f
252
     */
253
    public function detach()
254
    {
255
        $buffer = $this->buffer;
256
        $iter = $this->iter;
257
        $this->buffer = null;
258
        $this->iter = null;
259
        return [$iter, $buffer];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($iter, $buffer); (array) is incompatible with the return type declared by the interface CSVelte\Contract\Streamable::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...
260
    }
261
262
    /**
263
     * Writability accessor.
264
     *
265
     * Despite the fact that any class that implements this interface must also
266
     * define methods such as write and writeLine, that is no guarantee that an
267
     * object will necessarily be writable. This method should tell the user
268
     * whether a stream is, in fact, writable.
269
     *
270
     * @return boolean True if writable, false otherwise
271
     */
272 1
    public function isWritable()
273
    {
274 1
        return $this->writable;
275
    }
276
277
    /**
278
     * Write data to the output.
279
     *
280
     * @param string The data to write
281
     * @return int The number of bytes written
282
     */
283
    public function write($data)
284
    {
285
        return $this->writable;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->writable; (boolean) is incompatible with the return type declared by the interface CSVelte\Contract\Streamable::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...
286
    }
287
288
     /**
289
      * Seekability accessor.
290
      *
291
      * Despite the fact that any class that implements this interface must also
292
      * define methods such as seek, that is no guarantee that an
293
      * object will necessarily be seekable. This method should tell the user
294
      * whether a stream is, in fact, seekable.
295
      *
296
      * @return boolean True if seekable, false otherwise
297
      */
298 1
    public function isSeekable()
299
    {
300 1
        return $this->seekable;
301
    }
302
303
    /**
304
     * Seek to specified offset.
305
     *
306
     * @param integer Offset to seek to
307
     * @param integer Position from whence the offset should be applied
308
     * @return boolean True if seek was successful
309
     */
310 1
    public function seek($offset, $whence = SEEK_SET)
311
    {
312 1
        return $this->seekable;
313
    }
314
315
}
316