Completed
Push — releases/v0.2 ( f0dc0e...325f78 )
by Luke
02:21
created

Stream::tell()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 0
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 \SplFileObject;
25
26
use \Exception;
27
use \InvalidArgumentException;
28
use CSVelte\Exception\NotYetImplementedException;
29
use CSVelte\Exception\EndOfFileException;
30
use CSVelte\Exception\IOException;
31
32
/**
33
 * CSVelte Stream.
34
 *
35
 * Represents a stream for input/output. Implements both readable and writable
36
 * interfaces so that it can be passed to either ``CSVelte\Reader`` or
37
 * ``CSVelte\Writer``.
38
 *
39
 * @package    CSVelte
40
 * @subpackage CSVelte\IO
41
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
42
 * @author     Luke Visinoni <[email protected]>
43
 * @since      v0.2
44
 */
45
class Stream implements Readable, Writable, Seekable
46
{
47
    use IsReadable, IsWritable, IsSeekable;
48
    /**
49
     * Hash of readable/writable stream open mode types.
50
     *
51
     * Mercilessly stolen from:
52
     * https://github.com/guzzle/streams/blob/master/src/Stream.php
53
     *
54
     * My kudos and sincere thanks go out to Michael Dowling and Graham Campbell
55
     * of the guzzle/streams PHP package. Thanks for the inspiration (in some cases)
56
     * and the not suing me for outright theft (in this case).
57
     *
58
     * @var array Hash of readable and writable stream types
59
     */
60
    protected static $readWriteHash = [
61
        'read' => [
62
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
63
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
64
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
65
            'x+t' => true, 'c+t' => true, 'a+' => true,
66
        ],
67
        'write' => [
68
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
69
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
70
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
71
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
72
        ],
73
    ];
74
75
    /**
76
     * @var resource An open stream resource
77
     */
78
    protected $stream;
79
80
    /**
81
     * @var int The total size (in bytes) of the stream
82
     */
83
    protected $size;
84
85
    /**
86
     * Meta data about stream resource.
87
     * Just contains the return value of stream_get_meta_data.
88
     * @var array The return value of stream_get_meta_data
89
     */
90
    protected $meta;
91
92
    /**
93
     * Is stream seekable
94
     * @var boolean True if stream is seekable, false otherwise
95
     */
96
    protected $seekable;
97
98
    /**
99
     * Is stream readable
100
     * @var boolean True if stream is readable, false otherwise
101
     */
102
    protected $readable;
103
104
    /**
105
     * Is stream writable
106
     * @var boolean True if stream is writable, false otherwise
107
     */
108
    protected $writable;
109
110
    /**
111
     * Converts object/string to a usable stream
112
     *
113
     * Mercilessly stolen from:
114
     * https://github.com/guzzle/streams/blob/master/src/Stream.php
115
     *
116
     * My kudos and sincere thanks go out to Michael Dowling and Graham Campbell
117
     * of the guzzle/streams PHP package. Thanks for the inspiration (in some cases)
118
     * and the not suing me for outright theft (in this case).
119
     *
120
     * @param object|string|SplFileObject The string/object to convert to a stream
121
     * @param array Options to pass to the newly created stream
122
     * @return \CSVelte\IO\Stream
123
     * @throws \InvalidArgumentException
124
     * @todo Write an IO\AccessMode class like what I talked about in issue #114
125
     */
126 20
    public static function streamize($resource = '')
127
    {
128 20
        if ($resource instanceof SplFileObject) {
129 1
            return new self($resource->getPathName());
130
        }
131
132 19
        $type = gettype($resource);
133
134 19
        if ($type == 'string') {
135 17
            $stream = self::open('php://temp', 'r+');
136 17
            if ($resource !== '') {
137 14
                fwrite($stream, $resource);
138 14
                fseek($stream, 0);
139 14
            }
140 17
            return new self($stream);
141
        }
142
143 4
        if ($type == 'object' && method_exists($resource, '__toString')) {
144 2
            return self::streamize((string) $resource);
145
        }
146
147 2
        throw new InvalidArgumentException('Invalid resource type: ' . $type);
148
    }
149
150
    /**
151
     * Stream Object Constructor.
152
     *
153
     * Instantiates the stream object
154
     *
155
     * @param string|object|resource $stream Either a valid stream URI or an open
156
     *     stream resource (using fopen, fsockopen, et al.)
157
     * @param string $mode file/stream open mode as passed to native php
158
     *     ``fopen`` function
159
     * @param array $context Stream context options array as passed to native php
160
     *     ``stream_context_create`` function
161
     * @see http://php.net/manual/en/function.fopen.php
162
     * @see http://php.net/manual/en/function.stream-context-create.php
163
     */
164 65
    public function __construct($stream, $mode = null, $context = null)
165
    {
166 65
        $this->setMetaData($this->stream = self::open($stream, $mode, $context));
167 61
    }
168
169
    /**
170
     * Stream Object Destructor.
171
     *
172
     * Closes stream connection.
173
     */
174 60
    public function __destruct()
175
    {
176 60
        $this->close();
177 60
    }
178
179
    /**
180
     * Reads all data from the stream into a string, from the beginning to end.
181
     *
182
     * This method MUST attempt to seek to the beginning of the stream before
183
     * reading data and read the stream until the end is reached.
184
     *
185
     * Warning: This could attempt to load a large amount of data into memory.
186
     *
187
     * This method MUST NOT raise an exception in order to conform with PHP's
188
     * string casting operations.
189
     *
190
     * Returns the internal pointer to the position it was in once it's finished
191
     *
192
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
193
     * @return string
194
     */
195 6
    public function __toString()
196
    {
197 6
        $string = '';
198
        try {
199 6
            $pos = (int) $this->tell();
200 6
            $this->rewind();
201 6
            $string .= $this->getContents();
202 6
            $this->seek($pos);
203 6
        } catch (Exception $e) {
204
            // eat any exception that may be thrown...
205
        }
206 6
        return $string;
207
    }
208
209
    /**
210
     * Open a new stream URI and return stream resource.
211
     *
212
     * Pass in either a valid stream URI or a stream resource and this will
213
     * return a stream resource object.
214
     *
215
     * @param string|resource|object $stream Either stream URI or resource object
216
     * @param string $mode File/stream open mode (as passed to native php
217
     *     ``fopen`` function)
218
     * @param array $context Stream context options array as passed to native
219
     *     php ``stream_context_create`` function
220
     * @return resource Stream resource object
221
     * @throws CSVelte\Exception\IOException on invalid stream uri/resource
222
     * @throws \InvalidArgumentException if context param is not an array
223
     * @see http://php.net/manual/en/function.fopen.php
224
     * @see http://php.net/manual/en/function.stream-context-create.php
225
     */
226 63
    protected static function open($stream, $mode = null, $context = null)
227
    {
228 63
        if (is_null($mode)) $mode = 'r+b';
229 63
        if (is_string($uri = $stream)) {
230 59
            if (is_null($context)) {
231 57
                $stream = @fopen($uri, $mode);
232 57
            } else {
233 2
                if (!is_array($context)) {
234 1
                    throw new InvalidArgumentException("Invalid argument for context. Expected array, got: " . gettype($context));
235
                }
236 1
                $context = stream_context_create($context);
237 1
                $stream = @fopen($uri, $mode, false, $context);
238
            }
239 58
            if (false === $stream) {
240 1
                throw new IOException("Invalid stream URI: " . $uri, IOException::ERR_INVALID_STREAM_URI);
241
            }
242 57
        }
243 61
        if (!is_resource($stream) || get_resource_type($stream) != 'stream') {
244 1
            throw new IOException("Expected stream resource, got: " . gettype($stream), IOException::ERR_INVALID_STREAM_RESOURCE);
245
        }
246 60
        return $stream;
247
    }
248
249
    /**
250
     * Close stream resource.
251
     *
252
     * @return boolean True on success or false on failure
253
     */
254 60
    public function close()
255
    {
256 60
        if (is_resource($this->stream)) {
257 56
            return fclose($this->stream);
258
        }
259 6
        return false;
260
    }
261
262
    /**
263
     * Set stream meta data via stream resource.
264
     *
265
     * Pass in stream resource to set this object's stream metadata as returned
266
     * by the native php function ``stream_get_meta_data``
267
     *
268
     * @param resource $stream Stream resource object
269
     * @return $this
270
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
271
     */
272 60
    protected function setMetaData($stream)
273
    {
274 60
        $this->meta = stream_get_meta_data($stream);
275 60
        $this->seekable = (bool) $this->meta['seekable'];
276 60
        $this->readable = isset(self::$readWriteHash['read'][$this->meta['mode']]);
277 60
        $this->writable = isset(self::$readWriteHash['write'][$this->meta['mode']]);
278 60
        return $this;
279
    }
280
281
    /**
282
     * Get stream metadata (all or certain value).
283
     *
284
     * Get either the entire stream metadata array or a single value from it by key.
285
     *
286
     * @param string $key If set, must be one of ``stream_get_meta_data`` array keys
287
     * @return string|array Either a single value or whole array returned by ``stream_get_meta_data``
288
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
289
     */
290 15
    public function getMetaData($key = null)
291
    {
292 15
        if (!$this->stream) return null;
293 12
        if (is_null($key)) return $this->meta;
294 11
        return (array_key_exists($key, $this->meta)) ? $this->meta[$key] : null;
295
    }
296
297
    /**
298
     * Accessor for seekability.
299
     *
300
     * Returns true if possible to seek to a certain position within this stream
301
     *
302
     * @return boolean True if stream is seekable
303
     */
304 10
    public function isSeekable()
305
    {
306 10
        return $this->seekable;
307
    }
308
309
    /**
310
     * Accessor for readability.
311
     *
312
     * Returns true if possible to read from this stream
313
     *
314
     * @return boolean True if stream is readable
315
     */
316 42
    public function isReadable()
317
    {
318 42
        return $this->readable;
319
    }
320
321
    /**
322
     * Accessor for writability.
323
     *
324
     * Returns true if possible to write to this stream
325
     *
326
     * @return boolean True if stream is writable
327
     */
328 19
    public function isWritable()
329
    {
330 19
        return $this->writable;
331
    }
332
333
    /**
334
     * Accessor for internal stream resource.
335
     *
336
     * Returns the internal stream resource pointer
337
     *
338
     * @return resource The open stream resource pointer
339
     */
340 7
    public function getResource()
341
    {
342 7
        return $this->stream;
343
    }
344
345
    /**
346
     * Accessor for stream URI.
347
     *
348
     * Returns the stream URI
349
     *
350
     * @return string The URI for the stream
351
     */
352 14
    public function getUri()
353
    {
354 14
        return $this->getMetaData('uri');
355
    }
356
357
    /**
358
     * Accessor for stream name.
359
     *
360
     * Alias for ``getUri()``
361
     *
362
     * @return string The name for this stream
363
     */
364 13
    public function getName()
365
    {
366 13
        return $this->getUri();
367
    }
368
369
    /**
370
     * Separates any underlying resources from the stream.
371
     *
372
     * After the stream has been detached, the stream is in an unusable state.
373
     *
374
     * @return resource|null Underlying PHP stream, if any
375
     */
376 4
    public function detach()
377
    {
378 4
        $stream = $this->stream;
379 4
        $this->stream = null;
380 4
        $this->seekable = $this->readable = $this->writable = false;
381 4
        return $stream;
382
    }
383
384
    /**
385
     * Get the size of the stream if known.
386
     *
387
     * @return int|null Returns the size in bytes if known, or null if unknown.
388
     */
389 2
    public function getSize()
390
    {
391 2
        if (!$this->stream) return null;
392 2
        if (is_null($this->size)) {
393 2
            $stats = fstat($this->stream);
394 2
            if (array_key_exists('size', $stats)) {
395 2
                $this->size = $stats['size'];
396 2
            }
397 2
        }
398 2
        return $this->size;
399
    }
400
401
    /**
402
     * Returns the current position of the file read/write pointer
403
     *
404
     * @return int Position of the file pointer
405
     * @throws \RuntimeException on error.
406
     */
407 7
    public function tell()
408
    {
409 7
        return $this->stream ? ftell($this->stream) : false;
410
    }
411
412
    /**
413
     * Read $length bytes from stream.
414
     *
415
     * Reads $length bytes (number of characters) from the stream
416
     *
417
     * @param int $length Number of bytes to read from stream
418
     * @return string|false The data read from stream or false if at end of
419
     *     file or some other problem.
420
     * @throws CSVelte\Exception\IOException if stream not readable
421
     */
422 41
    public function read($length)
423
    {
424 41
        $this->assertIsReadable();
425 39
        if ($this->eof()) return false;
426 39
        return fread($this->stream, $length);
427
    }
428
429
    /**
430
     * Returns the remaining contents in a string.
431
     *
432
     * Read and return the remaining contents of the stream, beginning from
433
     * wherever the stream's internal pointer is when this method is called. If
434
     * you want the ENTIRE stream's contents, use __toString() instead.
435
     *
436
     * @param void
437
     * @return string The remaining contents of the file, beginning at internal
438
     *     pointer's current location
439
     * @throws CSVelte\Exception\IOException
440
     */
441 8
    public function getContents()
442
    {
443 8
        $buffer = '';
444 8
        if ($this->isReadable()) {
445 8
            while ($chunk = $this->read(1024)) {
446 8
                $buffer .= $chunk;
447 8
            }
448 8
        }
449 8
        return $buffer;
450
    }
451
452
    /**
453
     * Is file pointer at the end of the stream?
454
     *
455
     * Returns true if internal pointer has reached the end of the stream.
456
     *
457
     * @return boolean True if end of stream has been reached
458
     */
459 39
    public function eof()
460
    {
461 39
        return !$this->stream || feof($this->stream);
462
    }
463
464
    /**
465
     * Rewind pointer to beginning of stream.
466
     *
467
     * Rewinds the stream, meaning it returns the pointer to the beginning of the
468
     * stream as if it had just been initialized.
469
     */
470 28
    public function rewind()
471
    {
472 28
        if (is_resource($this->stream)) {
473 28
            rewind($this->stream);
474 28
        }
475 28
    }
476
477
    /**
478
     * Write to stream
479
     *
480
     * Writes a string to the stream (if it is writable)
481
     *
482
     * @param string $str The data to be written to the stream
483
     * @return int The number of bytes written to the stream
484
     * @throws CSVelte\Exception\IOException
485
     */
486 19
    public function write($str)
487
    {
488 19
        $this->assertIsWritable();
489 17
        return fwrite($this->stream, $str);
490
    }
491
492
    /**
493
     * Seek to position.
494
     *
495
     * Seek to a specific position within the stream (if seekable).
496
     *
497
     * @param int $offset The position to seek to
498
     * @param int $whence One of three native php ``SEEK_`` constants
499
     * @return boolean True on success false on failure
500
     * @throws CSVelte\Exception\IOException
501
     * @see http://php.net/manual/en/function.seek.php
502
     */
503 10
    public function seek($offset, $whence = SEEK_SET)
504
    {
505 10
        $this->assertIsSeekable();
506 9
        return fseek($this->stream, $offset, $whence) === 0;
507
    }
508
509
}
510