Completed
Pull Request — master (#309)
by ignace nyamagana
01:59
created

Stream::fgets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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