Completed
Push — releases/v0.2 ( 2d3c30...c1372d )
by Luke
03:48 queued 01:35
created

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