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 string|object 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
|
10 |
|
public static function streamize($resource = '') |
118
|
|
|
{ |
119
|
10 |
|
$type = gettype($resource); |
120
|
|
|
|
121
|
10 |
|
if ($type == 'string') { |
122
|
10 |
|
$stream = self::open('php://temp', 'r+'); |
123
|
10 |
|
if ($resource !== '') { |
124
|
9 |
|
fwrite($stream, $resource); |
125
|
9 |
|
fseek($stream, 0); |
126
|
9 |
|
} |
127
|
10 |
|
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
|
39 |
|
public function __construct($stream, $mode = null, $context = null) |
152
|
|
|
{ |
153
|
39 |
|
$this->setMetaData($this->stream = self::open($stream, $mode, $context)); |
154
|
36 |
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Stream Object Destructor. |
158
|
|
|
* |
159
|
|
|
* Closes stream connection. |
160
|
|
|
*/ |
161
|
34 |
|
public function __destruct() |
162
|
|
|
{ |
163
|
34 |
|
$this->close(); |
164
|
34 |
|
} |
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
|
36 |
|
protected static function open($stream, $mode = null, $context = null) |
184
|
|
|
{ |
185
|
36 |
|
if (is_null($mode)) $mode = 'r+b'; |
186
|
36 |
|
if (is_string($uri = $stream)) { |
187
|
32 |
|
if (is_null($context)) { |
188
|
32 |
|
$stream = @fopen($uri, $mode); |
189
|
32 |
|
} else { |
190
|
|
|
if (!is_array($context)) { |
191
|
|
|
throw new InvalidArgumentException("Invalid argument for context. Expected array, got: " . gettype($context)); |
192
|
|
|
} |
193
|
|
|
$context = stream_context_create($context); |
194
|
|
|
$stream = @fopen($uri, $mode, false, $context); |
195
|
|
|
} |
196
|
32 |
|
if (false === $stream) { |
197
|
1 |
|
throw new IOException("Invalid stream URI: " . $uri, IOException::ERR_INVALID_STREAM_URI); |
198
|
|
|
} |
199
|
31 |
|
} |
200
|
35 |
|
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
|
34 |
|
return $stream; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Close stream resource. |
208
|
|
|
* |
209
|
|
|
* @return boolean True on success or false on failure |
210
|
|
|
*/ |
211
|
34 |
|
public function close() |
212
|
|
|
{ |
213
|
34 |
|
if (is_resource($this->stream)) { |
214
|
34 |
|
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
|
34 |
|
protected function setMetaData($stream) |
230
|
|
|
{ |
231
|
34 |
|
$this->meta = stream_get_meta_data($stream); |
232
|
34 |
|
$this->seekable = (bool) $this->meta['seekable']; |
233
|
34 |
|
$this->readable = isset(self::$readWriteHash['read'][$this->meta['mode']]); |
234
|
34 |
|
$this->writable = isset(self::$readWriteHash['write'][$this->meta['mode']]); |
235
|
34 |
|
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
|
5 |
|
public function getMetaData($key = null) |
248
|
|
|
{ |
249
|
5 |
|
if (is_null($key)) return $this->meta; |
250
|
4 |
|
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
|
22 |
|
public function isReadable() |
273
|
|
|
{ |
274
|
22 |
|
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
|
13 |
|
public function isWritable() |
285
|
|
|
{ |
286
|
13 |
|
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
|
2 |
|
public function getResource() |
297
|
|
|
{ |
298
|
2 |
|
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
|
4 |
|
public function getUri() |
309
|
|
|
{ |
310
|
4 |
|
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
|
3 |
|
public function getName() |
321
|
|
|
{ |
322
|
3 |
|
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
|
21 |
|
public function read($length) |
336
|
|
|
{ |
337
|
21 |
|
$this->assertIsReadable(); |
338
|
20 |
|
if ($this->eof()) return false; |
|
|
|
|
339
|
20 |
|
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
|
|
|
public function getContents() |
352
|
|
|
{ |
353
|
|
|
|
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Is file pointer at the end of the stream? |
358
|
|
|
* |
359
|
|
|
* Returns true if internal pointer has reached the end of the stream. |
360
|
|
|
* |
361
|
|
|
* @return boolean True if end of stream has been reached |
362
|
|
|
*/ |
363
|
20 |
|
public function eof() |
364
|
|
|
{ |
365
|
20 |
|
return feof($this->stream); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* Rewind pointer to beginning of stream. |
370
|
|
|
* |
371
|
|
|
* Rewinds the stream, meaning it returns the pointer to the beginning of the |
372
|
|
|
* stream as if it had just been initialized. |
373
|
|
|
* |
374
|
|
|
* @return boolean True on success |
375
|
|
|
*/ |
376
|
12 |
|
public function rewind() |
377
|
|
|
{ |
378
|
12 |
|
return rewind($this->stream); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Write to stream |
383
|
|
|
* |
384
|
|
|
* Writes a string to the stream (if it is writable) |
385
|
|
|
* |
386
|
|
|
* @param string $str The data to be written to the stream |
387
|
|
|
* @return int The number of bytes written to the stream |
388
|
|
|
* @throws CSVelte\Exception\IOException |
389
|
|
|
*/ |
390
|
13 |
|
public function write($str) |
391
|
|
|
{ |
392
|
13 |
|
$this->assertIsWritable(); |
393
|
13 |
|
return fwrite($this->stream, $str); |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* Seek to position. |
398
|
|
|
* |
399
|
|
|
* Seek to a specific position within the stream (if seekable). |
400
|
|
|
* |
401
|
|
|
* @param int $offset The position to seek to |
402
|
|
|
* @param int $whence One of three native php ``SEEK_`` constants |
403
|
|
|
* @return boolean True on success false on failure |
404
|
|
|
* @throws CSVelte\Exception\IOException |
405
|
|
|
* @see http://php.net/manual/en/function.seek.php |
406
|
|
|
*/ |
407
|
2 |
|
public function seek($offset, $whence = SEEK_SET) |
408
|
|
|
{ |
409
|
2 |
|
$this->assertIsSeekable(); |
410
|
2 |
|
return fseek($this->stream, $offset, $whence) === 0; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
} |
414
|
|
|
|
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.