Completed
Branch releases/v0.2 (d913c4)
by Luke
02:17
created

Stream   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 98.73%

Importance

Changes 21
Bugs 0 Features 0
Metric Value
wmc 35
c 21
b 0
f 0
lcom 2
cbo 4
dl 0
loc 376
ccs 78
cts 79
cp 0.9873
rs 9

19 Methods

Rating   Name   Duplication   Size   Complexity  
B streamize() 0 19 5
A __construct() 0 4 1
A __destruct() 0 4 1
A close() 0 7 2
A setMetaData() 0 8 1
A getMetaData() 0 5 3
A isSeekable() 0 4 1
A isReadable() 0 4 1
A isWritable() 0 4 1
A getResource() 0 4 1
A getUri() 0 4 1
A getName() 0 4 1
A read() 0 6 2
C open() 0 22 8
A getContents() 0 8 2
A eof() 0 4 1
A rewind() 0 4 1
A write() 0 5 1
A seek() 0 5 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 13
    public static function streamize($resource = '')
118
    {
119 13
        $type = gettype($resource);
120
121 13
        if ($type == 'string') {
122 13
            $stream = self::open('php://temp', 'r+');
123 13
            if ($resource !== '') {
124 12
                fwrite($stream, $resource);
125 12
                fseek($stream, 0);
126 12
            }
127 13
            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 47
    public function __construct($stream, $mode = null, $context = null)
152
    {
153 47
        $this->setMetaData($this->stream = self::open($stream, $mode, $context));
154 43
    }
155
156
    /**
157
     * Stream Object Destructor.
158
     *
159
     * Closes stream connection.
160
     */
161 42
    public function __destruct()
162
    {
163 42
        $this->close();
164 42
    }
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 45
    protected static function open($stream, $mode = null, $context = null)
184
    {
185 45
        if (is_null($mode)) $mode = 'r+b';
186 45
        if (is_string($uri = $stream)) {
187 41
            if (is_null($context)) {
188 39
                $stream = @fopen($uri, $mode);
189 39
            } 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 40
            if (false === $stream) {
197 1
                throw new IOException("Invalid stream URI: " . $uri, IOException::ERR_INVALID_STREAM_URI);
198
            }
199 39
        }
200 43
        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 42
        return $stream;
204
    }
205
206
    /**
207
     * Close stream resource.
208
     *
209
     * @return boolean True on success or false on failure
210
     */
211 42
    public function close()
212
    {
213 42
        if (is_resource($this->stream)) {
214 42
            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 42
    protected function setMetaData($stream)
230
    {
231 42
        $this->meta = stream_get_meta_data($stream);
232 42
        $this->seekable = (bool) $this->meta['seekable'];
233 42
        $this->readable = isset(self::$readWriteHash['read'][$this->meta['mode']]);
234 42
        $this->writable = isset(self::$readWriteHash['write'][$this->meta['mode']]);
235 42
        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 8
    public function getMetaData($key = null)
248
    {
249 8
        if (is_null($key)) return $this->meta;
250 7
        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 28
    public function isReadable()
273
    {
274 28
        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 7
    public function getUri()
309
    {
310 7
        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 6
    public function getName()
321
    {
322 6
        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 27
    public function read($length)
336
    {
337 27
        $this->assertIsReadable();
338 26
        if ($this->eof()) return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface CSVelte\Contract\Readable::read of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
339 26
        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 26
    public function eof()
368
    {
369 26
        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 17
    public function rewind()
381
    {
382 17
        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