Completed
Push — releases/v0.2 ( b175d8...547b38 )
by Luke
05:58
created

Stream::read()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2
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
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\Traits\IsReadable;
17
use CSVelte\Traits\IsWritable;
18
use CSVelte\Traits\IsSeekable;
19
20
use CSVelte\Contract\Readable;
21
use CSVelte\Contract\Writable;
22
use CSVelte\Contract\Seekable;
23
24
use \Exception;
25
use \InvalidArgumentException;
26
use CSVelte\Exception\NotYetImplementedException;
27
use CSVelte\Exception\EndOfFileException;
28
use CSVelte\Exception\IOException;
29
30
/**
31
 * CSVelte Stream.
32
 *
33
 * Represents a stream for input/output. Implements both readable and writable
34
 * interfaces so that it can be passed to either ``CSVelte\Reader`` or
35
 * ``CSVelte\Writer``.
36
 *
37
 * @package    CSVelte
38
 * @subpackage CSVelte\IO
39
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
40
 * @author     Luke Visinoni <[email protected]>
41
 * @since      v0.2
42
 */
43
class Stream implements Readable, Writable, Seekable
44
{
45
    use IsReadable, IsWritable, IsSeekable;
46
    /**
47
     * Hash of readable/writable stream open mode types.
48
     *
49
     * Mercilessly stolen from:
50
     * https://github.com/guzzle/streams/blob/master/src/Stream.php
51
     *
52
     * My kudos and sincere thanks go out to Michael Dowling and Graham Campbell
53
     * of the guzzle/streams PHP package. Thanks for the inspiration (in some cases)
54
     * and the not suing me for outright theft (in this case).
55
     *
56
     * @var array Hash of readable and writable stream types
57
     */
58
    protected static $readWriteHash = [
59
        'read' => [
60
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
61
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
62
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
63
            'x+t' => true, 'c+t' => true, 'a+' => true,
64
        ],
65
        'write' => [
66
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
67
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
68
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
69
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
70
        ],
71
    ];
72
73
    /**
74
     * @var resource An open stream resource
75
     */
76
    protected $stream;
77
78
    /**
79
     * @var int The total size (in bytes) of the stream
80
     */
81
    protected $size;
82
83
    /**
84
     * Meta data about stream resource.
85
     * Just contains the return value of stream_get_meta_data.
86
     * @var array The return value of stream_get_meta_data
87
     */
88
    protected $meta;
89
90
    /**
91
     * Is stream seekable
92
     * @var boolean True if stream is seekable, false otherwise
93
     */
94
    protected $seekable;
95
96
    /**
97
     * Is stream readable
98
     * @var boolean True if stream is readable, false otherwise
99
     */
100
    protected $readable;
101
102
    /**
103
     * Is stream writable
104
     * @var boolean True if stream is writable, false otherwise
105
     */
106
    protected $writable;
107
108
    /**
109
     * Converts object/string to a usable stream
110
     *
111
     * Mercilessly stolen from:
112
     * https://github.com/guzzle/streams/blob/master/src/Stream.php
113
     *
114
     * My kudos and sincere thanks go out to Michael Dowling and Graham Campbell
115
     * of the guzzle/streams PHP package. Thanks for the inspiration (in some cases)
116
     * and the not suing me for outright theft (in this case).
117
     *
118
     * @param object|string The string/object to convert to a stream
119
     * @param array Options to pass to the newly created stream
120
     * @return \CSVelte\IO\Stream
121
     * @throws \InvalidArgumentException
122
     */
123 15
    public static function streamize($resource = '')
124
    {
125 15
        $type = gettype($resource);
126
127 15
        if ($type == 'string') {
128 15
            $stream = self::open('php://temp', 'r+');
129 15
            if ($resource !== '') {
130 13
                fwrite($stream, $resource);
131 13
                fseek($stream, 0);
132 13
            }
133 15
            return new self($stream);
134
        }
135
136 1
        if ($type == 'object' && method_exists($resource, '__toString')) {
137 1
            return self::streamize((string) $resource);
138
        }
139
140
        throw new InvalidArgumentException('Invalid resource type: ' . $type);
141
    }
142
143
    /**
144
     * Stream Object Constructor.
145
     *
146
     * Instantiates the stream object
147
     *
148
     * @param string|object|resource $stream Either a valid stream URI or an open
149
     *     stream resource (using fopen, fsockopen, et al.)
150
     * @param string file/stream open mode as passed to native php
151
     *     ``fopen`` function
152
     * @param array Stream context options array as passed to native php
153
     *     ``stream_context_create`` function
154
     * @see http://php.net/manual/en/function.fopen.php
155
     * @see http://php.net/manual/en/function.stream-context-create.php
156
     */
157 63
    public function __construct($stream, $mode = null, $context = null)
158
    {
159 63
        $this->setMetaData($this->stream = self::open($stream, $mode, $context));
0 ignored issues
show
Bug introduced by
It seems like $stream defined by parameter $stream on line 157 can also be of type object; however, CSVelte\IO\Stream::open() does only seem to accept string|resource, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
160 59
    }
161
162
    /**
163
     * Stream Object Destructor.
164
     *
165
     * Closes stream connection.
166
     */
167 58
    public function __destruct()
168
    {
169 58
        $this->close();
170 58
    }
171
172
    /**
173
     * Reads all data from the stream into a string, from the beginning to end.
174
     *
175
     * This method MUST attempt to seek to the beginning of the stream before
176
     * reading data and read the stream until the end is reached.
177
     *
178
     * Warning: This could attempt to load a large amount of data into memory.
179
     *
180
     * This method MUST NOT raise an exception in order to conform with PHP's
181
     * string casting operations.
182
     *
183
     * Returns the internal pointer to the position it was in once it's finished
184
     *
185
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
186
     * @return string
187
     */
188 3
    public function __toString()
189
    {
190 3
        $string = '';
191
        try {
192 3
            $pos = $this->tell();
193 3
            $this->rewind();
194 3
            $string .= $this->getContents();
195 3
            $this->seek($pos);
0 ignored issues
show
Security Bug introduced by
It seems like $pos defined by $this->tell() on line 192 can also be of type false; however, CSVelte\IO\Stream::seek() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
196 3
        } catch (Exception $e) {
197
            // eat any exception that may be thrown...
198
        }
199 3
        return $string;
200
    }
201
202
    /**
203
     * Open a new stream URI and return stream resource.
204
     *
205
     * Pass in either a valid stream URI or a stream resource and this will
206
     * return a stream resource object.
207
     *
208
     * @param string|resource $stream Either stream URI or resource object
209
     * @param string $mode File/stream open mode (as passed to native php
210
     *     ``fopen`` function)
211
     * @param array $context Stream context options array as passed to native
212
     *     php ``stream_context_create`` function
213
     * @return stream resource object
214
     * @throws CSVelte\Exception\IOException on invalid stream uri/resource
215
     * @throws \InvalidArgumentException if context param is not an array
216
     * @see http://php.net/manual/en/function.fopen.php
217
     * @see http://php.net/manual/en/function.stream-context-create.php
218
     */
219 61
    protected static function open($stream, $mode = null, $context = null)
220
    {
221 61
        if (is_null($mode)) $mode = 'r+b';
222 61
        if (is_string($uri = $stream)) {
223 57
            if (is_null($context)) {
224 55
                $stream = @fopen($uri, $mode);
225 55
            } else {
226 2
                if (!is_array($context)) {
227 1
                    throw new InvalidArgumentException("Invalid argument for context. Expected array, got: " . gettype($context));
228
                }
229 1
                $context = stream_context_create($context);
230 1
                $stream = @fopen($uri, $mode, false, $context);
231
            }
232 56
            if (false === $stream) {
233 1
                throw new IOException("Invalid stream URI: " . $uri, IOException::ERR_INVALID_STREAM_URI);
234
            }
235 55
        }
236 59
        if (!is_resource($stream) || get_resource_type($stream) != 'stream') {
237 1
            throw new IOException("Expected stream resource, got: " . gettype($stream), IOException::ERR_INVALID_STREAM_RESOURCE);
238
        }
239 58
        return $stream;
240
    }
241
242
    /**
243
     * Close stream resource.
244
     *
245
     * @return boolean True on success or false on failure
246
     */
247 58
    public function close()
248
    {
249 58
        if (is_resource($this->stream)) {
250 54
            return fclose($this->stream);
251
        }
252 6
        return false;
253
    }
254
255
    /**
256
     * Set stream meta data via stream resource.
257
     *
258
     * Pass in stream resource to set this object's stream metadata as returned
259
     * by the native php function ``stream_get_meta_data``
260
     *
261
     * @param resource $stream Stream resource object
262
     * @return $this
263
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
264
     */
265 58
    protected function setMetaData($stream)
266
    {
267 58
        $this->meta = stream_get_meta_data($stream);
268 58
        $this->seekable = (bool) $this->meta['seekable'];
269 58
        $this->readable = isset(self::$readWriteHash['read'][$this->meta['mode']]);
270 58
        $this->writable = isset(self::$readWriteHash['write'][$this->meta['mode']]);
271 58
        return $this;
272
    }
273
274
    /**
275
     * Get stream metadata (all or certain value).
276
     *
277
     * Get either the entire stream metadata array or a single value from it by key.
278
     *
279
     * @param string $key If set, must be one of ``stream_get_meta_data`` array keys
280
     * @return string|array Either a single value or whole array returned by ``stream_get_meta_data``
281
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
282
     */
283 15
    public function getMetaData($key = null)
284
    {
285 15
        if (!$this->stream) return null;
286 12
        if (is_null($key)) return $this->meta;
287 11
        return (array_key_exists($key, $this->meta)) ? $this->meta[$key] : null;
288
    }
289
290
    /**
291
     * Accessor for seekability.
292
     *
293
     * Returns true if possible to seek to a certain position within this stream
294
     *
295
     * @return boolean True if stream is seekable
296
     */
297 7
    public function isSeekable()
298
    {
299 7
        return $this->seekable;
300
    }
301
302
    /**
303
     * Accessor for readability.
304
     *
305
     * Returns true if possible to read from this stream
306
     *
307
     * @return boolean True if stream is readable
308
     */
309 40
    public function isReadable()
310
    {
311 40
        return $this->readable;
312
    }
313
314
    /**
315
     * Accessor for writability.
316
     *
317
     * Returns true if possible to write to this stream
318
     *
319
     * @return boolean True if stream is writable
320
     */
321 18
    public function isWritable()
322
    {
323 18
        return $this->writable;
324
    }
325
326
    /**
327
     * Accessor for internal stream resource.
328
     *
329
     * Returns the internal stream resource pointer
330
     *
331
     * @return resource The open stream resource pointer
332
     */
333 7
    public function getResource()
334
    {
335 7
        return $this->stream;
336
    }
337
338
    /**
339
     * Accessor for stream URI.
340
     *
341
     * Returns the stream URI
342
     *
343
     * @return string The URI for the stream
344
     */
345 14
    public function getUri()
346
    {
347 14
        return $this->getMetaData('uri');
348
    }
349
350
    /**
351
     * Accessor for stream name.
352
     *
353
     * Alias for ``getUri()``
354
     *
355
     * @return string The name for this stream
356
     */
357 13
    public function getName()
358
    {
359 13
        return $this->getUri();
360
    }
361
362
    /**
363
     * Separates any underlying resources from the stream.
364
     *
365
     * After the stream has been detached, the stream is in an unusable state.
366
     *
367
     * @return resource|null Underlying PHP stream, if any
368
     */
369 4
    public function detach()
370
    {
371 4
        $stream = $this->stream;
372 4
        $this->stream = null;
373 4
        $this->seekable = $this->readable = $this->writable = false;
374 4
        return $stream;
375
    }
376
377
    /**
378
     * Get the size of the stream if known.
379
     *
380
     * @return int|null Returns the size in bytes if known, or null if unknown.
381
     */
382 2
    public function getSize()
383
    {
384 2
        if (!$this->stream) return null;
385 2
        if (is_null($this->size)) {
386 2
            $stats = fstat($this->stream);
387 2
            if (array_key_exists('size', $stats)) {
388 2
                $this->size = $stats['size'];
389 2
            }
390 2
        }
391 2
        return $this->size;
392
    }
393
394
    /**
395
     * Returns the current position of the file read/write pointer
396
     *
397
     * @return int Position of the file pointer
398
     * @throws \RuntimeException on error.
399
     */
400 4
    public function tell()
401
    {
402 4
        return $this->stream ? ftell($this->stream) : false;
403
    }
404
405
    /**
406
     * Read $length bytes from stream.
407
     *
408
     * Reads $length bytes (number of characters) from the stream
409
     *
410
     * @param int $length Number of bytes to read from stream
411
     * @return string|boolean The data read from stream or false if at end of
412
     *     file or some other problem.
413
     * @throws CSVelte\Exception\IOException
414
     */
415 39
    public function read($length)
416
    {
417 39
        $this->assertIsReadable();
418 37
        if ($this->eof()) return false;
419 37
        return fread($this->stream, $length);
420
    }
421
422
    /**
423
     * Returns the remaining contents in a string.
424
     *
425
     * Read and return the remaining contents of the stream, beginning from
426
     * wherever the stream's internal pointer is when this method is called. If
427
     * you want the ENTIRE stream's contents, use __toString() instead.
428
     *
429
     * @param void
430
     * @return string The remaining contents of the file, beginning at internal
431
     *     pointer's current location
432
     * @throws CSVelte\Exception\IOException
433
     */
434 5
    public function getContents()
435
    {
436 5
        $buffer = '';
437 5
        if ($this->isReadable()) {
438 5
            while ($chunk = $this->read(1024)) {
439 5
                $buffer .= $chunk;
440 5
            }
441 5
        }
442 5
        return $buffer;
443
    }
444
445
    /**
446
     * Is file pointer at the end of the stream?
447
     *
448
     * Returns true if internal pointer has reached the end of the stream.
449
     *
450
     * @return boolean True if end of stream has been reached
451
     */
452 37
    public function eof()
453
    {
454 37
        return !$this->stream || feof($this->stream);
455
    }
456
457
    /**
458
     * Rewind pointer to beginning of stream.
459
     *
460
     * Rewinds the stream, meaning it returns the pointer to the beginning of the
461
     * stream as if it had just been initialized.
462
     *
463
     * @return boolean True on success
464
     */
465 26
    public function rewind()
466
    {
467 26
        if (is_resource($this->stream)) {
468 26
            return rewind($this->stream);
469
        }
470 1
    }
471
472
    /**
473
     * Write to stream
474
     *
475
     * Writes a string to the stream (if it is writable)
476
     *
477
     * @param string $str The data to be written to the stream
478
     * @return int The number of bytes written to the stream
479
     * @throws CSVelte\Exception\IOException
480
     */
481 18
    public function write($str)
482
    {
483 18
        $this->assertIsWritable();
484 16
        return fwrite($this->stream, $str);
485
    }
486
487
    /**
488
     * Seek to position.
489
     *
490
     * Seek to a specific position within the stream (if seekable).
491
     *
492
     * @param int $offset The position to seek to
493
     * @param int $whence One of three native php ``SEEK_`` constants
494
     * @return boolean True on success false on failure
495
     * @throws CSVelte\Exception\IOException
496
     * @see http://php.net/manual/en/function.seek.php
497
     */
498 7
    public function seek($offset, $whence = SEEK_SET)
499
    {
500 7
        $this->assertIsSeekable();
501 6
        return fseek($this->stream, $offset, $whence) === 0;
502
    }
503
504
}
505