Completed
Push — releases/v0.2 ( c1372d...b175d8 )
by Luke
05:30
created

Stream::open()   C

Complexity

Conditions 8
Paths 18

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 22
ccs 16
cts 16
cp 1
rs 6.6037
cc 8
eloc 15
nc 18
nop 3
crap 8
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 15
    public static function streamize($resource = '')
118
    {
119 15
        $type = gettype($resource);
120
121 15
        if ($type == 'string') {
122 15
            $stream = self::open('php://temp', 'r+');
123 15
            if ($resource !== '') {
124 13
                fwrite($stream, $resource);
125 13
                fseek($stream, 0);
126 13
            }
127 15
            return new self($stream);
128
        }
129
130 1
        if ($type == 'object' && method_exists($resource, '__toString')) {
131 1
            return self::streamize((string) $resource);
132
        }
133
134
        throw new InvalidArgumentException('Invalid resource type: ' . $type);
135
    }
136
137
    /**
138
     * Stream Object Constructor.
139
     *
140
     * Instantiates the stream object
141
     *
142
     * @param string|resource $stream Either a valid stream URI or an open
143
     *     stream resource (using fopen, fsockopen, et al.)
144
     * @param string file/stream open mode as passed to native php
145
     *     ``fopen`` function
146
     * @param array Stream context options array as passed to native php
147
     *     ``stream_context_create`` function
148
     * @see http://php.net/manual/en/function.fopen.php
149
     * @see http://php.net/manual/en/function.stream-context-create.php
150
     */
151 50
    public function __construct($stream, $mode = null, $context = null)
152
    {
153 50
        $this->setMetaData($this->stream = self::open($stream, $mode, $context));
154 46
    }
155
156
    /**
157
     * Stream Object Destructor.
158
     *
159
     * Closes stream connection.
160
     */
161 45
    public function __destruct()
162
    {
163 45
        $this->close();
164 45
    }
165
166
    /**
167
     * Open a new stream URI and return stream resource.
168
     *
169
     * Pass in either a valid stream URI or a stream resource and this will
170
     * return a stream resource object.
171
     *
172
     * @param string|resource $stream Either stream URI or resource object
173
     * @param string $mode File/stream open mode (as passed to native php
174
     *     ``fopen`` function)
175
     * @param array $context Stream context options array as passed to native
176
     *     php ``stream_context_create`` function
177
     * @return stream resource object
178
     * @throws CSVelte\Exception\IOException on invalid stream uri/resource
179
     * @throws \InvalidArgumentException if context param is not an array
180
     * @see http://php.net/manual/en/function.fopen.php
181
     * @see http://php.net/manual/en/function.stream-context-create.php
182
     */
183 48
    protected static function open($stream, $mode = null, $context = null)
184
    {
185 48
        if (is_null($mode)) $mode = 'r+b';
186 48
        if (is_string($uri = $stream)) {
187 44
            if (is_null($context)) {
188 42
                $stream = @fopen($uri, $mode);
189 42
            } else {
190 2
                if (!is_array($context)) {
191 1
                    throw new InvalidArgumentException("Invalid argument for context. Expected array, got: " . gettype($context));
192
                }
193 1
                $context = stream_context_create($context);
194 1
                $stream = @fopen($uri, $mode, false, $context);
195
            }
196 43
            if (false === $stream) {
197 1
                throw new IOException("Invalid stream URI: " . $uri, IOException::ERR_INVALID_STREAM_URI);
198
            }
199 42
        }
200 46
        if (!is_resource($stream) || get_resource_type($stream) != 'stream') {
201 1
            throw new IOException("Expected stream resource, got: " . gettype($stream), IOException::ERR_INVALID_STREAM_RESOURCE);
202
        }
203 45
        return $stream;
204
    }
205
206
    /**
207
     * Close stream resource.
208
     *
209
     * @return boolean True on success or false on failure
210
     */
211 45
    public function close()
212
    {
213 45
        if (is_resource($this->stream)) {
214 45
            return fclose($this->stream);
215
        }
216 2
        return false;
217
    }
218
219
    /**
220
     * Set stream meta data via stream resource.
221
     *
222
     * Pass in stream resource to set this object's stream metadata as returned
223
     * by the native php function ``stream_get_meta_data``
224
     *
225
     * @param resource $stream Stream resource object
226
     * @return $this
227
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
228
     */
229 45
    protected function setMetaData($stream)
230
    {
231 45
        $this->meta = stream_get_meta_data($stream);
232 45
        $this->seekable = (bool) $this->meta['seekable'];
233 45
        $this->readable = isset(self::$readWriteHash['read'][$this->meta['mode']]);
234 45
        $this->writable = isset(self::$readWriteHash['write'][$this->meta['mode']]);
235 45
        return $this;
236
    }
237
238
    /**
239
     * Get stream metadata (all or certain value).
240
     *
241
     * Get either the entire stream metadata array or a single value from it by key.
242
     *
243
     * @param string $key If set, must be one of ``stream_get_meta_data`` array keys
244
     * @return string|array Either a single value or whole array returned by ``stream_get_meta_data``
245
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
246
     */
247 9
    public function getMetaData($key = null)
248
    {
249 9
        if (is_null($key)) return $this->meta;
250 8
        return (array_key_exists($key, $this->meta)) ? $this->meta[$key] : null;
251
    }
252
253
    /**
254
     * Accessor for seekability.
255
     *
256
     * Returns true if possible to seek to a certain position within this stream
257
     *
258
     * @return boolean True if stream is seekable
259
     */
260 2
    public function isSeekable()
261
    {
262 2
        return $this->seekable;
263
    }
264
265
    /**
266
     * Accessor for readability.
267
     *
268
     * Returns true if possible to read from this stream
269
     *
270
     * @return boolean True if stream is readable
271
     */
272 31
    public function isReadable()
273
    {
274 31
        return $this->readable;
275
    }
276
277
    /**
278
     * Accessor for writability.
279
     *
280
     * Returns true if possible to write to this stream
281
     *
282
     * @return boolean True if stream is writable
283
     */
284 16
    public function isWritable()
285
    {
286 16
        return $this->writable;
287
    }
288
289
    /**
290
     * Accessor for internal stream resource.
291
     *
292
     * Returns the internal stream resource pointer
293
     *
294
     * @return resource The open stream resource pointer
295
     */
296 3
    public function getResource()
297
    {
298 3
        return $this->stream;
299
    }
300
301
    /**
302
     * Accessor for stream URI.
303
     *
304
     * Returns the stream URI
305
     *
306
     * @return string The URI for the stream
307
     */
308 8
    public function getUri()
309
    {
310 8
        return $this->getMetaData('uri');
311
    }
312
313
    /**
314
     * Accessor for stream name.
315
     *
316
     * Alias for ``getUri()``
317
     *
318
     * @return string The name for this stream
319
     */
320 7
    public function getName()
321
    {
322 7
        return $this->getUri();
323
    }
324
325
    /**
326
     * Read $length bytes from stream.
327
     *
328
     * Reads $length bytes (number of characters) from the stream
329
     *
330
     * @param int $length Number of bytes to read from stream
331
     * @return string|boolean The data read from stream or false if at end of
332
     *     file or some other problem.
333
     * @throws CSVelte\Exception\IOException
334
     */
335 30
    public function read($length)
336
    {
337 30
        $this->assertIsReadable();
338 29
        if ($this->eof()) return false;
339 29
        return fread($this->stream, $length);
340
    }
341
342
    /**
343
     * Read the entire contents of file/stream.
344
     *
345
     * Read and return the entire contents of the stream.
346
     *
347
     * @param void
348
     * @return string The entire file contents
349
     * @throws CSVelte\Exception\IOException
350
     */
351 1
    public function getContents()
352
    {
353 1
        $buffer = '';
354 1
        while ($chunk = $this->read(1024)) {
355 1
            $buffer .= $chunk;
356 1
        }
357 1
        return $buffer;
358
    }
359
360
    /**
361
     * Is file pointer at the end of the stream?
362
     *
363
     * Returns true if internal pointer has reached the end of the stream.
364
     *
365
     * @return boolean True if end of stream has been reached
366
     */
367 29
    public function eof()
368
    {
369 29
        return feof($this->stream);
370
    }
371
372
    /**
373
     * Rewind pointer to beginning of stream.
374
     *
375
     * Rewinds the stream, meaning it returns the pointer to the beginning of the
376
     * stream as if it had just been initialized.
377
     *
378
     * @return boolean True on success
379
     */
380 19
    public function rewind()
381
    {
382 19
        return rewind($this->stream);
383
    }
384
385
    /**
386
     * Write to stream
387
     *
388
     * Writes a string to the stream (if it is writable)
389
     *
390
     * @param string $str The data to be written to the stream
391
     * @return int The number of bytes written to the stream
392
     * @throws CSVelte\Exception\IOException
393
     */
394 16
    public function write($str)
395
    {
396 16
        $this->assertIsWritable();
397 15
        return fwrite($this->stream, $str);
398
    }
399
400
    /**
401
     * Seek to position.
402
     *
403
     * Seek to a specific position within the stream (if seekable).
404
     *
405
     * @param int $offset The position to seek to
406
     * @param int $whence One of three native php ``SEEK_`` constants
407
     * @return boolean True on success false on failure
408
     * @throws CSVelte\Exception\IOException
409
     * @see http://php.net/manual/en/function.seek.php
410
     */
411 2
    public function seek($offset, $whence = SEEK_SET)
412
    {
413 2
        $this->assertIsSeekable();
414 2
        return fseek($this->stream, $offset, $whence) === 0;
415
    }
416
417
}
418