IteratorStream   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 3

Test Coverage

Coverage 97.62%

Importance

Changes 0
Metric Value
dl 0
loc 327
ccs 82
cts 84
cp 0.9762
rs 9.84
c 0
b 0
f 0
wmc 32
lcom 3
cbo 3

17 Methods

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