Completed
Push — master ( 5c1aea...9ef1c4 )
by Luke
03:03
created

Stream::getMetaData()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

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