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

IteratorStream::isSeekable()   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 15
    public function __construct(Iterator $iter, $buffer = null)
85
    {
86 15
        $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 15
        if (!($buffer instanceof BufferStream)) {
88 9
            $buffer = new BufferStream();
89 9
        }
90 15
        $this->buffer = $buffer;
91 15
    }
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 2
    public function isReadable()
104
    {
105 2
        return $this->readable;
106
    }
107
108 11
    public function read($bytes)
109
    {
110 11
        if (is_null($this->buffer) || is_null($this->iter)) {
111 1
            return false;
112
        }
113 10
        $data = '';
114 10
        while (strlen($data) < $bytes) {
115 10
            if ($this->buffer->isEmpty()) {
116 10
                $this->inflateBuffer();
117 10
            }
118 10
            if (!$read = $this->buffer->read($bytes - strlen($data))) {
119 4
                break;
120
            }
121 10
            $data .= $read;
122 10
        }
123 10
        return $data;
124
    }
125
126 10
    protected function inflateBuffer()
127
    {
128 10
        while (!$this->buffer->isFull() && $this->iter->valid()) {
129 10
            $data = $this->iter->current();
130 10
            $this->buffer->write($data);
131 10
            $this->iter->next();
132 10
        }
133 10
    }
134
135
    /**
136
     * Read the entire stream, beginning to end.
137
     *
138
     * In most stream implementations, __toString() differs from getContents()
139
     * in that it returns the entire stream rather than just the remainder, but
140
     * due to the way this stream works (sort of like a conveyor belt), this
141
     * method is an alias to getContents()
142
     *
143
     * @return string The entire stream, beginning to end
144
     */
145 3
    public function __toString()
146
    {
147 3
        $this->rewind();
148 3
        return $this->getContents();
149
    }
150
151
    /**
152
     * Read the remainder of the stream
153
     *
154
     * @return string The remainder of the stream
155
     */
156 4
    public function getContents()
157
    {
158 4
        $contents = '';
159 4
        while (!$this->eof()) {
160 4
            $contents .= $this->read(
161
                // kind of arbitrary... we have to specify something for the
162
                // chunk length, so I just used the buffer's "high water mark"
163 4
                $this->buffer->getMetadata('hwm')
164 4
            );
165 4
        }
166 4
        return $contents;
167
    }
168
169
    /**
170
     * Return the size (in bytes) of this stream (if known).
171
     *
172
     * @return int|null Size (in bytes) of this stream
173
     */
174 1
    public function getSize()
175
    {
176
        // no way to know so return null
177 1
    }
178
179
    /**
180
     * Return the current position within the stream/readable
181
     *
182
     * @return int The current position within readable
183
     */
184
    public function tell()
185
    {
186
        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...
187
    }
188
189
    /**
190
     * Determine whether the end of the stream has been reached
191
     *
192
     * @return boolean Whether we're at the end of the stream
193
     */
194 4
    public function eof()
195
    {
196
        return (
197 4
            !$this->iter->valid() &&
198 4
            $this->buffer->eof()
199 4
        );
200
    }
201
202
    /**
203
     * Rewind to beginning of stream
204
     */
205 3
    public function rewind()
206
    {
207 3
        $this->iter->rewind();
208 3
        $this->buffer->rewind();
209 3
    }
210
211
    /**
212
     * Get stream metadata as an associative array or retrieve a specific key.
213
     *
214
     * The keys returned are identical to the keys returned from PHP's
215
     * stream_get_meta_data() function.
216
     *
217
     * @param string $key Specific metadata to retrieve.
218
     * @return array|mixed|null Returns an associative array if no key is
219
     *     provided. Returns a specific key value if a key is provided and the
220
     *     value is found, or null if the key is not found.
221
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
222
     */
223 2
    public function getMetadata($key = null)
224
    {
225 2
        if (!is_null($key)) {
226 1
            return isset($this->meta[$key]) ? $this->meta[$key] : null;
227
        }
228 1
        return $this->meta;
229
    }
230
231
    /**
232
     * Closes the stream and any underlying resources.
233
     *
234
     * @return void
235
     */
236
    public function close()
237
    {
238
        $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...
239
        $iter = true;
240
        if (method_exists($this->iter, 'close')) {
241
            $iter = $this->iter->close();
242
        }
243
        return $buff && $iter;
244
    }
245
246
    /**
247
     * Separates any underlying resources from the stream.
248
     *
249
     * After the stream has been detached, the stream is in an unusable state.
250
     *
251
     * @return string|null Underlying PHP stream, if any
252
     * @todo I'm not sure what detach is for so I don't know whether what I'm
253
     *     doing here is right. The reason I have the method at all is because
254
     *     psr7 StreamInterface has one.f
255
     */
256 1
    public function detach()
257
    {
258 1
        $buffer = $this->buffer;
259 1
        $iter = $this->iter;
260 1
        $this->buffer = null;
261 1
        $this->iter = null;
262 1
        $this->readable = false;
263 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...
264
    }
265
266
    /**
267
     * Writability accessor.
268
     *
269
     * Despite the fact that any class that implements this interface must also
270
     * define methods such as write and writeLine, that is no guarantee that an
271
     * object will necessarily be writable. This method should tell the user
272
     * whether a stream is, in fact, writable.
273
     *
274
     * @return boolean True if writable, false otherwise
275
     */
276 1
    public function isWritable()
277
    {
278 1
        return $this->writable;
279
    }
280
281
    /**
282
     * Write data to the output.
283
     *
284
     * @param string The data to write
285
     * @return int The number of bytes written
286
     */
287
    public function write($data)
288
    {
289
        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...
290
    }
291
292
     /**
293
      * Seekability accessor.
294
      *
295
      * Despite the fact that any class that implements this interface must also
296
      * define methods such as seek, that is no guarantee that an
297
      * object will necessarily be seekable. This method should tell the user
298
      * whether a stream is, in fact, seekable.
299
      *
300
      * @return boolean True if seekable, false otherwise
301
      */
302 1
    public function isSeekable()
303
    {
304 1
        return $this->seekable;
305
    }
306
307
    /**
308
     * Seek to specified offset.
309
     *
310
     * @param integer Offset to seek to
311
     * @param integer Position from whence the offset should be applied
312
     * @return boolean True if seek was successful
313
     */
314 1
    public function seek($offset, $whence = SEEK_SET)
315
    {
316 1
        return $this->seekable;
317
    }
318
319
}
320