Completed
Push — master ( 245e94...f3213f )
by ignace nyamagana
04:10 queued 02:57
created

StreamIterator::fgets()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
ccs 4
cts 4
cp 1
crap 2
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 34
    public function __construct($resource)
107
    {
108 34
        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 32
        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 30
        if (!stream_get_meta_data($resource)['seekable']) {
117 2
            throw new RuntimeException('Argument passed must be a seekable stream resource');
118
        }
119
120 28
        $this->stream = $resource;
121 28
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126
    public function __destruct()
127
    {
128 18
        $mapper = function ($filter): bool {
129 8
            return stream_filter_remove($filter);
130 14
        };
131
132 28
        array_map($mapper, $this->filters);
133
134 28
        if ($this->should_close_stream) {
135 18
            fclose($this->stream);
136
        }
137
138 28
        $this->stream = null;
139 28
    }
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 10
    public static function createFromString(string $content): self
186
    {
187 10
        $resource = fopen('php://temp', 'r+');
188 10
        fwrite($resource, $content);
189
190 10
        $instance = new static($resource);
191 10
        $instance->should_close_stream = true;
192
193 10
        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 2
    public function setCsvControl(string $delimiter = ',', string $enclosure = '"', string $escape = '\\')
228
    {
229 2
        $this->delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
230 2
        $this->enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
231 2
        $this->escape = $this->filterControl($escape, 'escape', __METHOD__);
232 2
    }
233
234
    /**
235
     * Set CSV control
236
     *
237
     * @see http://php.net/manual/en/splfileobject.getcsvcontrol.php
238
     *
239
     * @return string[]
240
     */
241 18
    public function getCsvControl()
242
    {
243 18
        return [$this->delimiter, $this->enclosure, $this->escape];
244
    }
245
246
    /**
247
     * Set StreamIterator Flags
248
     *
249
     * @see http://php.net/manual/en/splfileobject.setflags.php
250
     *
251
     * @param int $flags
252
     */
253 22
    public function setFlags(int $flags)
254
    {
255 22
        $this->flags = $flags;
256 22
    }
257
258
    /**
259
     * Write a field array as a CSV line
260
     *
261
     * @see http://php.net/manual/en/splfileobject.fputcsv.php
262
     *
263
     * @param array  $fields
264
     * @param string $delimiter
265
     * @param string $enclosure
266
     * @param string $escape
267
     *
268
     * @return int|bool
269
     */
270 14
    public function fputcsv(array $fields, string $delimiter = ',', string $enclosure = '"', string $escape = '\\')
271
    {
272 14
        return fputcsv(
273 14
            $this->stream,
274 14
            $fields,
275 14
            $this->filterControl($delimiter, 'delimiter', __METHOD__),
276 12
            $this->filterControl($enclosure, 'enclosure', __METHOD__),
277 10
            $this->filterControl($escape, 'escape', __METHOD__)
278
        );
279
    }
280
281
    /**
282
     * Get line number
283
     *
284
     * @see http://php.net/manual/en/splfileobject.key.php
285
     *
286
     * @return int
287
     */
288 12
    public function key()
289
    {
290 12
        return $this->offset;
291
    }
292
293
    /**
294
     * Read next line
295
     *
296
     * @see http://php.net/manual/en/splfileobject.next.php
297
     *
298
     */
299 12
    public function next()
300
    {
301 12
        $this->value = false;
302 12
        $this->offset++;
303 12
    }
304
305
    /**
306
     * Rewind the file to the first line
307
     *
308
     * @see http://php.net/manual/en/splfileobject.rewind.php
309
     *
310
     */
311 22
    public function rewind()
312
    {
313 22
        rewind($this->stream);
314 22
        $this->offset = 0;
315 22
        $this->value = false;
316 22
        if ($this->flags & SplFileObject::READ_AHEAD) {
317 12
            $this->current();
318
        }
319 22
    }
320
321
    /**
322
     * Not at EOF
323
     *
324
     * @see http://php.net/manual/en/splfileobject.valid.php
325
     *
326
     * @return bool
327
     */
328 20
    public function valid()
329
    {
330 20
        if ($this->flags & SplFileObject::READ_AHEAD) {
331 12
            return $this->current() !== false;
332
        }
333
334 8
        return !feof($this->stream);
335
    }
336
337
    /**
338
     * Retrieves the current line of the file.
339
     *
340
     * @see http://php.net/manual/en/splfileobject.current.php
341
     *
342
     * @return mixed
343
     */
344 16
    public function current()
345
    {
346 16
        if (false !== $this->value) {
347 16
            return $this->value;
348
        }
349
350 16
        if (($this->flags & SplFileObject::READ_CSV) == SplFileObject::READ_CSV) {
351 12
            $this->value = $this->getCurrentRecord();
352
353 12
            return $this->value;
354
        }
355
356 4
        $this->value = $this->getCurrentLine();
357
358 4
        return $this->value;
359
    }
360
361
    /**
362
     * Retrieves the current line as a CSV Record
363
     *
364
     * @return array|bool
365
     */
366 12
    protected function getCurrentRecord()
367
    {
368
        do {
369 12
            $ret = fgetcsv($this->stream, 0, $this->delimiter, $this->enclosure, $this->escape);
370 12
        } while ($this->flags & SplFileObject::SKIP_EMPTY && $ret !== false && $ret[0] === null);
371
372 12
        return $ret;
373
    }
374
375
    /**
376
     * Retrieves the current line as a string
377
     *
378
     * @return string|bool
379
     */
380 22
    protected function getCurrentLine()
381
    {
382
        do {
383 22
            $line = fgets($this->stream);
384 22
        } while ($this->flags & SplFileObject::SKIP_EMPTY && $line !== false && rtrim($line, "\r\n") !== '');
385
386 22
        return $line;
387
    }
388
389
    /**
390
     * Seek to specified line
391
     *
392
     * @see http://php.net/manual/en/splfileobject.seek.php
393
     *
394
     * @param int $position
395
     */
396 4
    public function seek($position)
397
    {
398 4
        $pos = $this->filterMinRange((int) $position, 0, __METHOD__.'() Can\'t seek stream to negative line %d');
399 4
        foreach ($this as $key => $value) {
400 4
            if ($key === $pos || feof($this->stream)) {
401 4
                $this->offset--;
402 4
                break;
403
            }
404
        }
405
406 4
        $this->current();
407 4
    }
408
409
    /**
410
     * Gets line from file
411
     *
412
     * @see http://php.net/manual/en/splfileobject.fgets.php
413
     *
414
     * @return string|bool
415
     */
416 26
    public function fgets()
417
    {
418 26
        if (false !== $this->value) {
419 4
            $this->next();
420
        }
421
422 26
        return $this->value = $this->getCurrentLine();
423
    }
424
425
    /**
426
     * Output all remaining data on a file pointer
427
     *
428
     * @see http://php.net/manual/en/splfileobject.fpatssthru.php
429
     *
430
     * @return int
431
     */
432 2
    public function fpassthru()
433
    {
434 2
        return fpassthru($this->stream);
435
    }
436
437
    /**
438
     * Read from file
439
     *
440
     * @see http://php.net/manual/en/splfileobject.fread.php
441
     *
442
     * @param int $length The number of bytes to read
443
     *
444
     * @return string|false
445
     */
446 8
    public function fread($length)
447
    {
448 8
        return fread($this->stream, $length);
449
    }
450
451
    /**
452
     * Seek to a position
453
     *
454
     * @see http://php.net/manual/en/splfileobject.fseek.php
455
     *
456
     * @param int $offset
457
     * @param int $whence
458
     *
459
     * @return int
460
     */
461 4
    public function fseek(int $offset, int $whence = SEEK_SET)
462
    {
463 4
        return fseek($this->stream, $offset, $whence);
464
    }
465
466
    /**
467
     * Write to stream
468
     *
469
     * @see http://php.net/manual/en/splfileobject.fwrite.php
470
     *
471
     * @param string $str
472
     * @param int    $length
473
     *
474
     * @return int|bool
475
     */
476 2
    public function fwrite(string $str, int $length = 0)
477
    {
478 2
        return fwrite($this->stream, $str, $length);
479
    }
480
481
    /**
482
     * Flushes the output to a file
483
     *
484
     * @see http://php.net/manual/en/splfileobject.fwrite.php
485
     *
486
     * @return bool
487
     */
488 8
    public function fflush()
489
    {
490 8
        return fflush($this->stream);
491
    }
492
}
493