Stream::getMetaData()   A
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
nc 7
nop 1
dl 0
loc 15
ccs 9
cts 9
cp 1
crap 5
rs 9.4555
c 0
b 0
f 0
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\Exception\IOException;
18
use CSVelte\Traits\IsReadable;
19
use CSVelte\Traits\IsSeekable;
20
21
use CSVelte\Traits\IsWritable;
22
23
use Exception;
24
25
/**
26
 * CSVelte Stream.
27
 *
28
 * Represents a stream for input/output. Implements both readable and writable
29
 * interfaces so that it can be passed to either ``CSVelte\Reader`` or
30
 * ``CSVelte\Writer``.
31
 *
32
 * @package    CSVelte
33
 * @subpackage CSVelte\IO
34
 *
35
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
36
 * @author     Luke Visinoni <[email protected]>
37
 *
38
 * @since      v0.2
39
 */
40
class Stream implements Streamable
41
{
42
    use IsReadable, IsWritable, IsSeekable;
43
44
    /**
45
     * @var StreamResource A stream resource object
46
     */
47
    protected $resource;
48
49
    /**
50
     * @var int The total size (in bytes) of the stream
51
     */
52
    protected $size;
53
54
    /**
55
     * Meta data about stream resource.
56
     * Just contains the return value of stream_get_meta_data.
57
     *
58
     * @var array The return value of stream_get_meta_data
59
     *
60
     * @todo Not sure if this belongs in this class or in Resource. I'm leaving
61
     *     it here for now, simply because I'm worried Stream will become superfluous
62
     */
63
    protected $meta;
64
65
    /**
66
     * Instantiate a stream.
67
     *
68
     * Instantiate a new stream object using a stream resource object.
69
     *
70
     * @param StreamResource $resource A stream resource object
71
     */
72 79
    public function __construct(StreamResource $resource)
73
    {
74 79
        $this->setResource($resource);
75 79
    }
76
77
    /**
78
     * Stream Object Destructor.
79
     *
80
     * Closes stream connection.
81
     */
82 79
    public function __destruct()
83
    {
84 79
        $this->close();
85 79
    }
86
87
    /**
88
     * Reads all data from the stream into a string, from the beginning to end.
89
     *
90
     * This method MUST attempt to seek to the beginning of the stream before
91
     * reading data and read the stream until the end is reached.
92
     *
93
     * Warning: This could attempt to load a large amount of data into memory.
94
     *
95
     * This method MUST NOT raise an exception in order to conform with PHP's
96
     * string casting operations.
97
     *
98
     * Returns the internal pointer to the position it was in once it's finished
99
     *
100
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
101
     *
102
     * @return string
103
     *
104
     * @todo I'm leaning towards getting rid of the code that places the cursor
105
     *     back at the position it was in... I'm not sure it's expected behavior
106
     */
107 7
    public function __toString()
108
    {
109 7
        $string = '';
110
        try {
111 7
            $pos = (int) $this->tell();
112 7
            $this->rewind();
113 7
            $string .= $this->getContents();
114 7
            $this->seek($pos);
115 7
        } catch (Exception $e) {
116
            // eat any exception that may be thrown...
117
        }
118
119 7
        return $string;
120
    }
121
122
    /**
123
     * Stream factory method.
124
     *
125
     * Pass in a URI and optionally a mode string, context params, and whether or not you want
126
     * lazy-opening, and it will give you back a Stream object.
127
     *
128
     * @param string        $uri     The stream URI you want to open
129
     * @param string        $mode    The access mode string
130
     * @param null|resource $context Stream resource context options/params
131
     * @param bool          $lazy    Whether or not you want this stream to lazy-open
132
     *
133
     * @return Stream
134
     *
135
     * @see http://php.net/manual/en/function.fopen.php
136
     * @see http://php.net/manual/en/function.stream-context-create.php
137
     */
138 71
    public static function open($uri, $mode = null, $context = null, $lazy = false)
139
    {
140 71
        $resource = (new StreamResource($uri, $mode))
141 70
            ->setContextResource($context);
142 68
        if (!$lazy) {
143 67
            $resource->connect();
144 66
        }
145
146 67
        return new self($resource);
147
    }
148
149
    /**
150
     * Close stream resource.
151
     *
152
     * @return bool True on success or false on failure
153
     */
154 79
    public function close()
155
    {
156 79
        if ($this->resource) {
157 76
            return $this->resource->disconnect();
158
        }
159 4
    }
160
161
    /**
162
     * Get stream metadata (all or certain value).
163
     *
164
     * Get either the entire stream metadata array or a single value from it by key.
165
     *
166
     * @param string $key If set, must be one of ``stream_get_meta_data`` array keys
167
     *
168
     * @return string|array Either a single value or whole array returned by ``stream_get_meta_data``
169
     *
170
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
171
     */
172 11
    public function getMetaData($key = null)
173
    {
174 11
        if ($this->resource) {
175 11
            if (is_null($this->meta)) {
176 11
                $this->meta = stream_get_meta_data($this->resource->getHandle());
177 11
            }
178
            // if a certain value was requested, return it
179
            // otherwise, return entire array
180 11
            if (is_null($key)) {
181 2
                return $this->meta;
182
            }
183
184 10
            return (array_key_exists($key, $this->meta)) ? $this->meta[$key] : null;
185
        }
186 1
    }
187
188
    /**
189
     * Accessor for seekability.
190
     *
191
     * Returns true if possible to seek to a certain position within this stream
192
     *
193
     * @return bool True if stream is seekable
194
     */
195 11
    public function isSeekable()
196
    {
197 11
        if ($this->resource) {
198 10
            return (bool) $this->getMetaData('seekable');
199
        }
200
201 2
        return false;
202
    }
203
204
    /**
205
     * Accessor for readability.
206
     *
207
     * Returns true if possible to read from this stream
208
     *
209
     * @return bool True if stream is readable
210
     */
211 53
    public function isReadable()
212
    {
213 53
        if ($this->resource) {
214 52
            return $this->resource->isReadable();
215
        }
216
217 2
        return false;
218
    }
219
220
    /**
221
     * Accessor for writability.
222
     *
223
     * Returns true if possible to write to this stream
224
     *
225
     * @return bool True if stream is writable
226
     */
227 20
    public function isWritable()
228
    {
229 20
        if ($this->resource) {
230 19
            return $this->resource->isWritable();
231
        }
232
233 2
        return false;
234
    }
235
236
    /**
237
     * Get the Resource object.
238
     *
239
     * Returns the internal StreamResource object used as a drop-in replacement for
240
     * PHP's native stream resource variables.
241
     *
242
     * @return StreamResource The Resource object
243
     */
244 32
    public function getResource()
245
    {
246 32
        return $this->resource;
247
    }
248
249
    /**
250
     * Accessor for stream URI.
251
     *
252
     * Returns the stream URI
253
     *
254
     * @return string The URI for the stream
255
     */
256 4
    public function getUri()
257
    {
258 4
        if ($this->resource) {
259 4
            return $this->resource->getUri();
260
        }
261 1
    }
262
263
    /**
264
     * Accessor for stream name.
265
     *
266
     * Alias for ``getUri()``
267
     *
268
     * @return string The name for this stream
269
     */
270 1
    public function getName()
271
    {
272 1
        return $this->getUri();
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
     *
280
     * @return StreamResource|null Underlying PHP stream, if any
281
     */
282 4
    public function detach()
283
    {
284
        // @todo I need to get a better understanding of when and why a stream
285
        // would need to be detached to properly implement this
286 4
        $resource       = $this->resource;
287 4
        $this->resource = null;
288
289 4
        return $resource;
290
    }
291
292
    /**
293
     * Get the size of the stream if known.
294
     *
295
     * @return int|null Returns the size in bytes if known, or null if unknown.
296
     */
297 4
    public function getSize()
298
    {
299 4
        if ($this->resource) {
300 4
            if (is_null($this->size)) {
301 4
                $stats = fstat($this->resource->getHandle());
302 4
                if (array_key_exists('size', $stats)) {
303 4
                    $this->size = $stats['size'];
304 4
                }
305 4
            }
306
307 4
            return $this->size;
308
        }
309 1
    }
310
311
    /**
312
     * Returns the current position of the file read/write pointer.
313
     *
314
     * @throws \RuntimeException on error.
315
     *
316
     * @return int Position of the file pointer
317
     */
318 8
    public function tell()
319
    {
320 8
        return $this->resource ? ftell($this->resource->getHandle()) : false;
321
    }
322
323
    /**
324
     * Read $length bytes from stream.
325
     *
326
     * Reads $length bytes (number of characters) from the stream
327
     *
328
     * @param int $length Number of bytes to read from stream
329
     *
330
     * @throws IOException if stream not readable
331
     *
332
     * @return string|false The data read from stream or false if at end of
333
     *                      file or some other problem.
334
     */
335 51
    public function read($length)
336
    {
337 51
        $this->assertIsReadable();
338 49
        if ($this->eof()) {
339 6
            return false;
340
        }
341
342 49
        return fread($this->resource->getHandle(), $length);
343
    }
344
345
    /**
346
     * Returns the remaining contents in a string.
347
     *
348
     * Read and return the remaining contents of the stream, beginning from
349
     * wherever the stream's internal pointer is when this method is called. If
350
     * you want the ENTIRE stream's contents, use __toString() instead.
351
     *
352
     * @throws IOException
353
     *
354
     * @return string The remaining contents of the file, beginning at internal
355
     *                pointer's current location
356
     */
357 9
    public function getContents()
358
    {
359 9
        $buffer = '';
360 9
        if ($this->isReadable()) {
361 9
            while ($chunk = $this->read(1024)) {
362 9
                $buffer .= $chunk;
363 9
            }
364 9
        }
365
366 9
        return $buffer;
367
    }
368
369
    /**
370
     * Is file pointer at the end of the stream?
371
     *
372
     * Returns true if internal pointer has reached the end of the stream.
373
     *
374
     * @return bool True if end of stream has been reached
375
     */
376 49
    public function eof()
377
    {
378 49
        return !$this->resource || feof($this->resource->getHandle());
379
    }
380
381
    /**
382
     * Rewind pointer to beginning of stream.
383
     *
384
     * Rewinds the stream, meaning it returns the pointer to the beginning of the
385
     * stream as if it had just been initialized.
386
     */
387 29
    public function rewind()
388
    {
389 29
        if ($this->resource) {
390 29
            rewind($this->resource->getHandle());
391 29
        }
392 29
    }
393
394
    /**
395
     * Write to stream.
396
     *
397
     * Writes a string to the stream (if it is writable)
398
     *
399
     * @param string $str The data to be written to the stream
400
     *
401
     * @throws IOException
402
     *
403
     * @return int The number of bytes written to the stream
404
     */
405 19
    public function write($str)
406
    {
407 19
        $this->assertIsWritable();
408
409 17
        return fwrite($this->resource->getHandle(), $str);
410
    }
411
412
    /**
413
     * Seek to position.
414
     *
415
     * Seek to a specific position within the stream (if seekable).
416
     *
417
     * @param int $offset The position to seek to
418
     * @param int $whence One of three native php ``SEEK_`` constants
419
     *
420
     * @throws IOException
421
     *
422
     * @return bool True on success false on failure
423
     *
424
     * @see http://php.net/manual/en/function.seek.php
425
     */
426 11
    public function seek($offset, $whence = SEEK_SET)
427
    {
428 11
        $this->assertIsSeekable();
429
430 10
        return fseek($this->resource->getHandle(), $offset, $whence) === 0;
431
    }
432
433
    /**
434
     * Set stream resource object.
435
     *
436
     * @param StreamResource $resource A stream resource object
437
     */
438 79
    protected function setResource(StreamResource $resource)
439
    {
440 79
        $this->resource = $resource;
441 79
    }
442
}
443