|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This file is part of the League.csv library |
|
4
|
|
|
* |
|
5
|
|
|
* @license http://opensource.org/licenses/MIT |
|
6
|
|
|
* @link https://github.com/thephpleague/csv/ |
|
7
|
|
|
* @version 9.0.0 |
|
8
|
|
|
* @package League.csv |
|
9
|
|
|
* |
|
10
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
11
|
|
|
* file that was distributed with this source code. |
|
12
|
|
|
*/ |
|
13
|
|
|
declare(strict_types=1); |
|
14
|
|
|
|
|
15
|
|
|
namespace League\Csv; |
|
16
|
|
|
|
|
17
|
|
|
use RuntimeException; |
|
18
|
|
|
use SeekableIterator; |
|
19
|
|
|
use SplFileObject; |
|
20
|
|
|
use TypeError; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* An object oriented API for a CSV stream resource. |
|
24
|
|
|
* |
|
25
|
|
|
* @package League.csv |
|
26
|
|
|
* @since 8.2.0 |
|
27
|
|
|
* @author Ignace Nyamagana Butera <[email protected]> |
|
28
|
|
|
* @internal used internally to iterate over a stream resource |
|
29
|
|
|
* |
|
30
|
|
|
*/ |
|
31
|
|
|
class Stream implements SeekableIterator |
|
32
|
|
|
{ |
|
33
|
|
|
use ValidatorTrait; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* Attached filters |
|
37
|
|
|
* |
|
38
|
|
|
* @var resource[] |
|
39
|
|
|
*/ |
|
40
|
|
|
protected $filters = []; |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* stream resource |
|
44
|
|
|
* |
|
45
|
|
|
* @var resource |
|
46
|
|
|
*/ |
|
47
|
|
|
protected $stream; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* Tell whether the stream should be closed on object destruction |
|
51
|
|
|
* |
|
52
|
|
|
* @var bool |
|
53
|
|
|
*/ |
|
54
|
|
|
protected $should_close_stream = false; |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* Current iterator value |
|
58
|
|
|
* |
|
59
|
|
|
* @var mixed |
|
60
|
|
|
*/ |
|
61
|
|
|
protected $value; |
|
62
|
|
|
|
|
63
|
|
|
/** |
|
64
|
|
|
* Current iterator key |
|
65
|
|
|
* |
|
66
|
|
|
* @var int |
|
67
|
|
|
*/ |
|
68
|
|
|
protected $offset; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* Flags for the Document |
|
72
|
|
|
* |
|
73
|
|
|
* @var int |
|
74
|
|
|
*/ |
|
75
|
|
|
protected $flags = 0; |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* the field delimiter (one character only) |
|
79
|
|
|
* |
|
80
|
|
|
* @var string |
|
81
|
|
|
*/ |
|
82
|
|
|
protected $delimiter = ','; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* the field enclosure character (one character only) |
|
86
|
|
|
* |
|
87
|
|
|
* @var string |
|
88
|
|
|
*/ |
|
89
|
|
|
protected $enclosure = '"'; |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* the field escape character (one character only) |
|
93
|
|
|
* |
|
94
|
|
|
* @var string |
|
95
|
|
|
*/ |
|
96
|
|
|
protected $escape = '\\'; |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* New instance |
|
100
|
|
|
* |
|
101
|
|
|
* @param resource $resource stream type resource |
|
102
|
|
|
* |
|
103
|
|
|
* @throws RuntimeException if the argument passed is not a seeakable stream resource |
|
104
|
|
|
*/ |
|
105
|
34 |
|
public function __construct($resource) |
|
106
|
|
|
{ |
|
107
|
34 |
|
if (!is_resource($resource)) { |
|
108
|
2 |
|
throw new TypeError(sprintf('Argument passed must be a seekable stream resource, %s given', gettype($resource))); |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
32 |
|
if ('stream' !== ($type = get_resource_type($resource))) { |
|
112
|
2 |
|
throw new TypeError(sprintf('Argument passed must be a seekable stream resource, %s resource given', $type)); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
30 |
|
if (!stream_get_meta_data($resource)['seekable']) { |
|
116
|
2 |
|
throw new Exception('Argument passed must be a seekable stream resource'); |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
28 |
|
$this->stream = $resource; |
|
120
|
28 |
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* Create a resource. |
|
124
|
|
|
* |
|
125
|
|
|
* @param string $url file url |
|
126
|
|
|
* @param string $open_mode the file open mode flag |
|
127
|
|
|
* @param null $context the resource context |
|
128
|
|
|
* |
|
129
|
|
|
* @throws Exception if the stream resource can not be created |
|
130
|
|
|
* |
|
131
|
|
|
* @return resource |
|
132
|
|
|
*/ |
|
133
|
10 |
|
private static function createResource(string $url, string $open_mode, $context) |
|
134
|
|
|
{ |
|
135
|
10 |
|
$args = [$url, $open_mode]; |
|
136
|
10 |
|
if (null !== $context) { |
|
137
|
|
|
$args[] = false; |
|
138
|
|
|
$args[] = $context; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
10 |
|
if (!$resource = @fopen(...$args)) { |
|
142
|
2 |
|
throw new Exception(error_get_last()['message']); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
8 |
|
return $resource; |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* @inheritdoc |
|
150
|
|
|
*/ |
|
151
|
|
|
public function __destruct() |
|
152
|
|
|
{ |
|
153
|
28 |
|
$walker = function ($filter): bool { |
|
154
|
8 |
|
return stream_filter_remove($filter); |
|
155
|
28 |
|
}; |
|
156
|
|
|
|
|
157
|
28 |
|
array_walk_recursive($this->filters, $walker); |
|
158
|
|
|
|
|
159
|
28 |
|
if ($this->should_close_stream) { |
|
160
|
18 |
|
fclose($this->stream); |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
28 |
|
unset($this->stream); |
|
164
|
28 |
|
} |
|
165
|
|
|
|
|
166
|
|
|
/** |
|
167
|
|
|
* @inheritdoc |
|
168
|
|
|
*/ |
|
169
|
2 |
|
public function __clone() |
|
170
|
|
|
{ |
|
171
|
2 |
|
throw new Exception(sprintf('An object of class %s cannot be cloned', get_class($this))); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
/** |
|
175
|
|
|
* @inheritdoc |
|
176
|
|
|
*/ |
|
177
|
2 |
|
public function __debugInfo() |
|
178
|
|
|
{ |
|
179
|
2 |
|
return stream_get_meta_data($this->stream) + [ |
|
180
|
2 |
|
'delimiter' => $this->delimiter, |
|
181
|
2 |
|
'enclosure' => $this->enclosure, |
|
182
|
2 |
|
'escape' => $this->escape, |
|
183
|
2 |
|
'stream_filters' => array_keys($this->filters), |
|
184
|
|
|
]; |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Return a new instance from a file path |
|
189
|
|
|
* |
|
190
|
|
|
* @param string $path file path |
|
191
|
|
|
* @param string $open_mode the file open mode flag |
|
192
|
|
|
* @param resource|null $context the resource context |
|
193
|
|
|
* |
|
194
|
|
|
* @throws Exception if the stream resource can not be created |
|
195
|
|
|
* |
|
196
|
|
|
* @return static |
|
197
|
|
|
*/ |
|
198
|
12 |
|
public static function createFromPath(string $path, string $open_mode = 'r', $context = null): self |
|
199
|
|
|
{ |
|
200
|
12 |
|
$resource = self::createResource($path, $open_mode, $context); |
|
|
|
|
|
|
201
|
|
|
|
|
202
|
10 |
|
$instance = new static($resource); |
|
203
|
10 |
|
$instance->should_close_stream = true; |
|
204
|
|
|
|
|
205
|
10 |
|
return $instance; |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
/** |
|
209
|
|
|
* Return a new instance from a file url. |
|
210
|
|
|
* |
|
211
|
|
|
* @param string $url file url |
|
212
|
|
|
* @param string $open_mode the file open mode flag |
|
213
|
|
|
* @param null $context the resource context |
|
214
|
|
|
* |
|
215
|
|
|
* @see http://php.net/manual/es/wrappers.php |
|
216
|
|
|
* |
|
217
|
|
|
* @throws Exception if the stream resource can not be created |
|
218
|
|
|
* |
|
219
|
|
|
* @return static |
|
220
|
|
|
*/ |
|
221
|
|
|
public static function createFromUrl(string $url, string $open_mode = 'r', $context = null): self |
|
222
|
|
|
{ |
|
223
|
|
|
$resourceOrigin = self::createResource($url, $open_mode, $context); |
|
224
|
|
|
rewind($resourceOrigin); |
|
225
|
|
|
|
|
226
|
|
|
$resourceDestination = fopen('php://temp', 'r+'); |
|
227
|
|
|
stream_copy_to_stream($resourceOrigin, $resourceDestination); |
|
228
|
|
|
rewind($resourceDestination); |
|
229
|
|
|
|
|
230
|
|
|
$instance = new static($resourceDestination); |
|
231
|
|
|
$instance->should_close_stream = true; |
|
232
|
|
|
|
|
233
|
|
|
return $instance; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* Return a new instance from a string |
|
238
|
|
|
* |
|
239
|
|
|
* @param string $content the CSV document as a string |
|
240
|
|
|
* |
|
241
|
|
|
* @return static |
|
242
|
|
|
*/ |
|
243
|
10 |
|
public static function createFromString(string $content): self |
|
244
|
|
|
{ |
|
245
|
10 |
|
$resource = fopen('php://temp', 'r+'); |
|
246
|
10 |
|
fwrite($resource, $content); |
|
247
|
|
|
|
|
248
|
10 |
|
$instance = new static($resource); |
|
249
|
10 |
|
$instance->should_close_stream = true; |
|
250
|
|
|
|
|
251
|
10 |
|
return $instance; |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
/** |
|
255
|
|
|
* append a filter |
|
256
|
|
|
* |
|
257
|
|
|
* @see http://php.net/manual/en/function.stream-filter-append.php |
|
258
|
|
|
* |
|
259
|
|
|
* @param string $filtername |
|
260
|
|
|
* @param int $read_write |
|
261
|
|
|
* @param mixed $params |
|
262
|
|
|
* |
|
263
|
|
|
* @throws Exception if the filter can not be appended |
|
264
|
|
|
*/ |
|
265
|
10 |
|
public function appendFilter(string $filtername, int $read_write, $params = null) |
|
266
|
|
|
{ |
|
267
|
10 |
|
$res = @stream_filter_append($this->stream, $filtername, $read_write, $params); |
|
268
|
10 |
|
if (is_resource($res)) { |
|
269
|
8 |
|
$this->filters[$filtername][] = $res; |
|
270
|
|
|
|
|
271
|
8 |
|
return; |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
2 |
|
throw new Exception(error_get_last()['message']); |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* Set CSV control |
|
279
|
|
|
* |
|
280
|
|
|
* @see http://php.net/manual/en/splfileobject.setcsvcontrol.php |
|
281
|
|
|
* |
|
282
|
|
|
* @param string $delimiter |
|
283
|
|
|
* @param string $enclosure |
|
284
|
|
|
* @param string $escape |
|
285
|
|
|
*/ |
|
286
|
22 |
|
public function setCsvControl(string $delimiter = ',', string $enclosure = '"', string $escape = '\\') |
|
287
|
|
|
{ |
|
288
|
22 |
|
$this->delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__); |
|
289
|
22 |
|
$this->enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__); |
|
290
|
22 |
|
$this->escape = $this->filterControl($escape, 'escape', __METHOD__); |
|
291
|
22 |
|
} |
|
292
|
|
|
|
|
293
|
|
|
/** |
|
294
|
|
|
* Set CSV control |
|
295
|
|
|
* |
|
296
|
|
|
* @see http://php.net/manual/en/splfileobject.getcsvcontrol.php |
|
297
|
|
|
* |
|
298
|
|
|
* @return string[] |
|
299
|
|
|
*/ |
|
300
|
28 |
|
public function getCsvControl() |
|
301
|
|
|
{ |
|
302
|
28 |
|
return [$this->delimiter, $this->enclosure, $this->escape]; |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
/** |
|
306
|
|
|
* Set CSV stream flags |
|
307
|
|
|
* |
|
308
|
|
|
* @see http://php.net/manual/en/splfileobject.setflags.php |
|
309
|
|
|
* |
|
310
|
|
|
* @param int $flags |
|
311
|
|
|
*/ |
|
312
|
22 |
|
public function setFlags(int $flags) |
|
313
|
|
|
{ |
|
314
|
22 |
|
$this->flags = $flags; |
|
315
|
22 |
|
} |
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Write a field array as a CSV line |
|
319
|
|
|
* |
|
320
|
|
|
* @see http://php.net/manual/en/splfileobject.fputcsv.php |
|
321
|
|
|
* |
|
322
|
|
|
* @param array $fields |
|
323
|
|
|
* @param string $delimiter |
|
324
|
|
|
* @param string $enclosure |
|
325
|
|
|
* @param string $escape |
|
326
|
|
|
* |
|
327
|
|
|
* @return int|bool |
|
328
|
|
|
*/ |
|
329
|
14 |
|
public function fputcsv(array $fields, string $delimiter = ',', string $enclosure = '"', string $escape = '\\') |
|
330
|
|
|
{ |
|
331
|
14 |
|
return fputcsv( |
|
332
|
14 |
|
$this->stream, |
|
333
|
14 |
|
$fields, |
|
334
|
14 |
|
$this->filterControl($delimiter, 'delimiter', __METHOD__), |
|
335
|
12 |
|
$this->filterControl($enclosure, 'enclosure', __METHOD__), |
|
336
|
10 |
|
$this->filterControl($escape, 'escape', __METHOD__) |
|
337
|
|
|
); |
|
338
|
|
|
} |
|
339
|
|
|
|
|
340
|
|
|
/** |
|
341
|
|
|
* Get line number |
|
342
|
|
|
* |
|
343
|
|
|
* @see http://php.net/manual/en/splfileobject.key.php |
|
344
|
|
|
* |
|
345
|
|
|
* @return int |
|
346
|
|
|
*/ |
|
347
|
12 |
|
public function key() |
|
348
|
|
|
{ |
|
349
|
12 |
|
return $this->offset; |
|
350
|
|
|
} |
|
351
|
|
|
|
|
352
|
|
|
/** |
|
353
|
|
|
* Read next line |
|
354
|
|
|
* |
|
355
|
|
|
* @see http://php.net/manual/en/splfileobject.next.php |
|
356
|
|
|
* |
|
357
|
|
|
*/ |
|
358
|
12 |
|
public function next() |
|
359
|
|
|
{ |
|
360
|
12 |
|
$this->value = false; |
|
361
|
12 |
|
$this->offset++; |
|
362
|
12 |
|
} |
|
363
|
|
|
|
|
364
|
|
|
/** |
|
365
|
|
|
* Rewind the file to the first line |
|
366
|
|
|
* |
|
367
|
|
|
* @see http://php.net/manual/en/splfileobject.rewind.php |
|
368
|
|
|
* |
|
369
|
|
|
*/ |
|
370
|
22 |
|
public function rewind() |
|
371
|
|
|
{ |
|
372
|
22 |
|
rewind($this->stream); |
|
373
|
22 |
|
$this->offset = 0; |
|
374
|
22 |
|
$this->value = false; |
|
375
|
22 |
|
if ($this->flags & SplFileObject::READ_AHEAD) { |
|
376
|
12 |
|
$this->current(); |
|
377
|
|
|
} |
|
378
|
22 |
|
} |
|
379
|
|
|
|
|
380
|
|
|
/** |
|
381
|
|
|
* Not at EOF |
|
382
|
|
|
* |
|
383
|
|
|
* @see http://php.net/manual/en/splfileobject.valid.php |
|
384
|
|
|
* |
|
385
|
|
|
* @return bool |
|
386
|
|
|
*/ |
|
387
|
20 |
|
public function valid() |
|
388
|
|
|
{ |
|
389
|
20 |
|
if ($this->flags & SplFileObject::READ_AHEAD) { |
|
390
|
12 |
|
return $this->current() !== false; |
|
391
|
|
|
} |
|
392
|
|
|
|
|
393
|
8 |
|
return !feof($this->stream); |
|
394
|
|
|
} |
|
395
|
|
|
|
|
396
|
|
|
/** |
|
397
|
|
|
* Retrieves the current line of the file. |
|
398
|
|
|
* |
|
399
|
|
|
* @see http://php.net/manual/en/splfileobject.current.php |
|
400
|
|
|
* |
|
401
|
|
|
* @return mixed |
|
402
|
|
|
*/ |
|
403
|
26 |
|
public function current() |
|
404
|
|
|
{ |
|
405
|
26 |
|
if (false !== $this->value) { |
|
406
|
16 |
|
return $this->value; |
|
407
|
|
|
} |
|
408
|
|
|
|
|
409
|
26 |
|
$this->value = $this->getCurrentRecord(); |
|
410
|
|
|
|
|
411
|
26 |
|
return $this->value; |
|
412
|
|
|
} |
|
413
|
|
|
|
|
414
|
|
|
/** |
|
415
|
|
|
* Retrieves the current line as a CSV Record |
|
416
|
|
|
* |
|
417
|
|
|
* @return array|bool |
|
418
|
|
|
*/ |
|
419
|
22 |
|
protected function getCurrentRecord() |
|
420
|
|
|
{ |
|
421
|
|
|
do { |
|
422
|
22 |
|
$ret = fgetcsv($this->stream, 0, $this->delimiter, $this->enclosure, $this->escape); |
|
423
|
22 |
|
} while ($this->flags & SplFileObject::SKIP_EMPTY && $ret !== false && $ret[0] === null); |
|
424
|
|
|
|
|
425
|
22 |
|
return $ret; |
|
426
|
|
|
} |
|
427
|
|
|
|
|
428
|
|
|
/** |
|
429
|
|
|
* Seek to specified line |
|
430
|
|
|
* |
|
431
|
|
|
* @see http://php.net/manual/en/splfileobject.seek.php |
|
432
|
|
|
* |
|
433
|
|
|
* |
|
434
|
|
|
* @param int $position |
|
435
|
|
|
* @throws Exception if the position is negative |
|
436
|
|
|
*/ |
|
437
|
8 |
|
public function seek($position) |
|
438
|
|
|
{ |
|
439
|
8 |
|
if ($position < 0) { |
|
440
|
2 |
|
throw new Exception(sprintf('%s() can\'t seek stream to negative line %d', __METHOD__, $position)); |
|
441
|
|
|
} |
|
442
|
|
|
|
|
443
|
6 |
|
$this->rewind(); |
|
444
|
6 |
|
while ($this->key() !== $position && $this->valid()) { |
|
445
|
2 |
|
$this->current(); |
|
446
|
2 |
|
$this->next(); |
|
447
|
|
|
} |
|
448
|
|
|
|
|
449
|
6 |
|
$this->offset--; |
|
450
|
6 |
|
$this->current(); |
|
451
|
6 |
|
} |
|
452
|
|
|
|
|
453
|
|
|
/** |
|
454
|
|
|
* Output all remaining data on a file pointer |
|
455
|
|
|
* |
|
456
|
|
|
* @see http://php.net/manual/en/splfileobject.fpatssthru.php |
|
457
|
|
|
* |
|
458
|
|
|
* @return int |
|
459
|
|
|
*/ |
|
460
|
2 |
|
public function fpassthru() |
|
461
|
|
|
{ |
|
462
|
2 |
|
return fpassthru($this->stream); |
|
463
|
|
|
} |
|
464
|
|
|
|
|
465
|
|
|
/** |
|
466
|
|
|
* Read from file |
|
467
|
|
|
* |
|
468
|
|
|
* @see http://php.net/manual/en/splfileobject.fread.php |
|
469
|
|
|
* |
|
470
|
|
|
* @param int $length The number of bytes to read |
|
471
|
|
|
* |
|
472
|
|
|
* @return string|false |
|
473
|
|
|
*/ |
|
474
|
8 |
|
public function fread($length) |
|
475
|
|
|
{ |
|
476
|
8 |
|
return fread($this->stream, $length); |
|
477
|
|
|
} |
|
478
|
|
|
|
|
479
|
|
|
/** |
|
480
|
|
|
* Seek to a position |
|
481
|
|
|
* |
|
482
|
|
|
* @see http://php.net/manual/en/splfileobject.fseek.php |
|
483
|
|
|
* |
|
484
|
|
|
* @param int $offset |
|
485
|
|
|
* @param int $whence |
|
486
|
|
|
* |
|
487
|
|
|
* @return int |
|
488
|
|
|
*/ |
|
489
|
10 |
|
public function fseek(int $offset, int $whence = SEEK_SET) |
|
490
|
|
|
{ |
|
491
|
10 |
|
return fseek($this->stream, $offset, $whence); |
|
492
|
|
|
} |
|
493
|
|
|
|
|
494
|
|
|
/** |
|
495
|
|
|
* Write to stream |
|
496
|
|
|
* |
|
497
|
|
|
* @see http://php.net/manual/en/splfileobject.fwrite.php |
|
498
|
|
|
* |
|
499
|
|
|
* @param string $str |
|
500
|
|
|
* @param int $length |
|
501
|
|
|
* |
|
502
|
|
|
* @return int|bool |
|
503
|
|
|
*/ |
|
504
|
2 |
|
public function fwrite(string $str, int $length = 0) |
|
505
|
|
|
{ |
|
506
|
2 |
|
return fwrite($this->stream, $str, $length); |
|
507
|
|
|
} |
|
508
|
|
|
|
|
509
|
|
|
/** |
|
510
|
|
|
* Flushes the output to a file |
|
511
|
|
|
* |
|
512
|
|
|
* @see http://php.net/manual/en/splfileobject.fwrite.php |
|
513
|
|
|
* |
|
514
|
|
|
* @return bool |
|
515
|
|
|
*/ |
|
516
|
2 |
|
public function fflush() |
|
517
|
|
|
{ |
|
518
|
2 |
|
return fflush($this->stream); |
|
519
|
|
|
} |
|
520
|
|
|
} |
|
521
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.