Completed
Push — master ( 13aeaf...4bfe5f )
by ignace nyamagana
06:04 queued 04:52
created

StreamIterator::createFromPath()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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