Completed
Push — releases/v0.2.1 ( d424a1...b63a3e )
by Luke
03:04
created

IteratorStream::close()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
nc 4
nop 0
dl 0
loc 10
ccs 6
cts 8
cp 0.75
crap 3.1406
rs 9.4285
c 1
b 0
f 0
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 InvalidArgumentException;
17
use \Iterator;
18
use CSVelte\IO\BufferStream;
19
use CSVelte\Traits\IsReadable;
20
use CSVelte\Traits\IsWritable;
21
use CSVelte\Contract\Streamable;
22
use Traversable;
23
use function CSVelte\getvalue;
24
25
/**
26
 * Iterator Stream.
27
 *
28
 * A read-only stream that uses an iterable to continuously fill up a buffer as
29
 * read operations deplete it.
30
 *
31
 * @package    CSVelte
32
 * @subpackage CSVelte\IO
33
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
34
 * @author     Luke Visinoni <[email protected]>
35
 * @since      v0.2.1
36
 */
37
class IteratorStream implements Streamable
38
{
39
    use IsReadable, IsWritable;
40
41
    /**
42
     * Buffer stream
43
     * @var \CSVelte\IO\BufferStream A BufferStream object
44
     */
45
    protected $buffer;
46
47
    protected $overflow;
48
49
    /**
50
     * Is stream readable?
51
     *
52
     * @var boolean Whether stream is readable
53
     */
54
    protected $readable = true;
55
56
    /**
57
     * Is stream writable?
58
     *
59
     * @var boolean Whether stream is writable
60
     */
61
    protected $writable = false;
62
63
    /**
64
     * Is stream seekable?
65
     *
66
     * @var boolean Whether stream is seekable
67
     */
68
    protected $seekable = false;
69
70
    /**
71
     * @var array Any additional options / meta data
72
     */
73
    protected $meta = [
74
75
    ];
76
77
    /**
78
     * Instantiate an iterator stream
79
     *
80
     * Instantiate a new iterator stream. The iterator is used to continually
81
     * refill a buffer as it is drained by read operations.
82
     *
83
     * @param \Iterator The iterator to stream data from
84
     * @param \CSVelte\IO\BufferIterator|null Either a buffer or null (to use
85
     *     default buffer)
86
     * @todo this should expect a BufferInterface or a Bufferable rather than
87
     * a BufferStream
88
     */
89 21
    public function __construct(Traversable $iter, $buffer = null)
90
    {
91 21
        $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...
92 21
        if (is_null($buffer)) {
93 14
            $buffer = new BufferStream;
94 14
        }
95 21
        if (!($buffer instanceof BufferStream)) {
96 1
            throw new InvalidArgumentException(sprintf(
97 1
                "%s expected %s as second argument, got: %s",
98 1
                __CLASS__,
99 1
                BufferStream::class,
100 1
                is_object($buffer) ? get_class($buffer) : gettype($buffer)
101 1
            ));
102
        }
103 20
        $this->buffer = $buffer;
104 20
    }
105
106
    /**
107
     * Readability accessor.
108
     *
109
     * Despite the fact that any class that implements this interface must also
110
     * define methods such as read and readLine, that is no guarantee that an
111
     * object will necessarily be readable. This method should tell the user
112
     * whether a stream is, in fact, readable.
113
     *
114
     * @return boolean True if readable, false otherwise
115
     */
116 2
    public function isReadable()
117
    {
118 2
        return $this->readable;
119
    }
120
121 13
    public function read($bytes)
122
    {
123 13
        if ($this->buffer) {
124 11
            $data = '';
125 11
            while (strlen($data) < $bytes) {
126 11
                if ($this->buffer->isEmpty()) {
127 11
                    $this->inflateBuffer();
128 11
                }
129
130 11
                if (!$read = $this->buffer->read($bytes - strlen($data))) {
131 4
                    break;
132
                }
133 11
                $data .= $read;
134 11
            }
135 11
            return $data;
136
        }
137 2
        return false;
138
    }
139
140 11
    protected function inflateBuffer()
141
    {
142 11
        while (!$this->buffer->isFull() && $this->iter->valid()) {
143 11
            $data = $this->iter->current();
144 11
            $this->buffer->write($data);
145 11
            $this->iter->next();
146 11
        }
147 11
    }
148
149
    /**
150
     * Read the entire stream, beginning to end.
151
     *
152
     * In most stream implementations, __toString() differs from getContents()
153
     * in that it returns the entire stream rather than just the remainder, but
154
     * due to the way this stream works (sort of like a conveyor belt), this
155
     * method is an alias to getContents()
156
     *
157
     * @return string The entire stream, beginning to end
158
     */
159 3
    public function __toString()
160
    {
161 3
        $this->rewind();
162 3
        return $this->getContents();
163
    }
164
165
    /**
166
     * Read the remainder of the stream
167
     *
168
     * @return string The remainder of the stream
169
     */
170 4
    public function getContents()
171
    {
172 4
        $contents = '';
173 4
        while (!$this->eof()) {
174 4
            $contents .= $this->read(
175
                // kind of arbitrary... we have to specify something for the
176
                // chunk length, so I just used the buffer's "high water mark"
177 4
                $this->buffer->getMetadata('hwm')
178 4
            );
179 4
        }
180 4
        return $contents;
181
    }
182
183
    /**
184
     * Return the size (in bytes) of this stream (if known).
185
     *
186
     * @return int|null Size (in bytes) of this stream
187
     */
188 1
    public function getSize()
189
    {
190
        // no way to know so return null
191 1
    }
192
193
    /**
194
     * Return the current position within the stream/readable
195
     *
196
     * I can't decide whether there is any meaningful way to "tell" the
197
     * current position within this type of stream. For now I'm just
198
     * going to return false because the nature of this type of stream
199
     * means that it technically has no definitive beginning or end and
200
     * therefor no absolute position. If a "tell" is truly needed, I
201
     * suppose I could keep track of how many bytes have been read over
202
     * the lifetime of the object, but I don't think that is meaningful
203
     * and/or useful.
204
     *
205
     * @return int The current position within readable
206
     */
207 1
    public function tell()
208
    {
209 1
        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...
210
    }
211
212
    /**
213
     * Determine whether the end of the stream has been reached
214
     *
215
     * @return boolean Whether we're at the end of the stream
216
     */
217 4
    public function eof()
218
    {
219
        return (
220 4
            !$this->iter->valid() &&
221 4
            $this->buffer->eof()
222 4
        );
223
    }
224
225
    /**
226
     * Rewind to beginning of stream
227
     */
228 3
    public function rewind()
229
    {
230 3
        $this->iter->rewind();
231 3
        $this->buffer->rewind();
232 3
    }
233
234
    /**
235
     * Get stream metadata as an associative array or retrieve a specific key.
236
     *
237
     * The keys returned are identical to the keys returned from PHP's
238
     * stream_get_meta_data() function.
239
     *
240
     * @param string $key Specific metadata to retrieve.
241
     * @return array|mixed|null Returns an associative array if no key is
242
     *     provided. Returns a specific key value if a key is provided and the
243
     *     value is found, or null if the key is not found.
244
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
245
     */
246 2
    public function getMetadata($key = null)
247
    {
248 2
        if (!is_null($key)) {
249 1
            return isset($this->meta[$key]) ? $this->meta[$key] : null;
250
        }
251 1
        return $this->meta;
252
    }
253
254
    /**
255
     * Closes the stream and any underlying resources.
256
     *
257
     * @return void
258
     */
259 1
    public function close()
260
    {
261 1
        $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...
262 1
        $iter = true;
263 1
        if (method_exists($this->iter, 'close')) {
264
            $iter = $this->iter->close();
265
        }
266 1
        $this->buffer = null;
267 1
        return $buff && $iter;
268
    }
269
270
    /**
271
     * Separates any underlying resources from the stream.
272
     *
273
     * After the stream has been detached, the stream is in an unusable state.
274
     *
275
     * @return string|null Underlying PHP stream, if any
276
     * @todo I'm not sure what detach is for so I don't know whether what I'm
277
     *     doing here is right. The reason I have the method at all is because
278
     *     psr7 StreamInterface has one.f
279
     */
280 1
    public function detach()
281
    {
282 1
        $buffer = $this->buffer;
283 1
        $iter = $this->iter;
284 1
        $this->buffer = null;
285 1
        $this->iter = null;
286 1
        $this->readable = false;
287 1
        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...
288
    }
289
290
    /**
291
     * Writability accessor.
292
     *
293
     * Despite the fact that any class that implements this interface must also
294
     * define methods such as write and writeLine, that is no guarantee that an
295
     * object will necessarily be writable. This method should tell the user
296
     * whether a stream is, in fact, writable.
297
     *
298
     * @return boolean True if writable, false otherwise
299
     */
300 1
    public function isWritable()
301
    {
302 1
        return $this->writable;
303
    }
304
305
    /**
306
     * Write data to the output.
307
     *
308
     * @param string The data to write
309
     * @return int The number of bytes written
310
     */
311 2
    public function write($data)
312
    {
313 2
        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...
314
    }
315
316
     /**
317
      * Seekability accessor.
318
      *
319
      * Despite the fact that any class that implements this interface must also
320
      * define methods such as seek, that is no guarantee that an
321
      * object will necessarily be seekable. This method should tell the user
322
      * whether a stream is, in fact, seekable.
323
      *
324
      * @return boolean True if seekable, false otherwise
325
      */
326 2
    public function isSeekable()
327
    {
328 2
        return $this->seekable;
329
    }
330
331
    /**
332
     * Seek to specified offset.
333
     *
334
     * @param integer Offset to seek to
335
     * @param integer Position from whence the offset should be applied
336
     * @return boolean True if seek was successful
337
     */
338 1
    public function seek($offset, $whence = SEEK_SET)
339
    {
340 1
        return $this->seekable;
341
    }
342
343
}
344