Completed
Push — master ( 5c1aea...9ef1c4 )
by Luke
03:03
created

IteratorStream   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 315
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 3

Test Coverage

Coverage 97.65%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 315
ccs 83
cts 85
cp 0.9765
rs 9.6
c 1
b 0
f 0
wmc 32
lcom 3
cbo 3

17 Methods

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