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