Completed
Pull Request — master (#321)
by Carsten
03:43
created

Stream::current()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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