Completed
Pull Request — master (#241)
by
unknown
02:29
created

Stream::valid()   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 RuntimeException;
18
use SeekableIterator;
19
use SplFileObject;
20
use TypeError;
21
22
/**
23
 * An object oriented API for a CSV stream resource.
24
 *
25
 * @package  League.csv
26
 * @since    8.2.0
27
 * @author   Ignace Nyamagana Butera <[email protected]>
28
 * @internal used internally to iterate over a stream resource
29
 *
30
 */
31
class Stream implements SeekableIterator
32
{
33
    use ValidatorTrait;
34
35
    /**
36
     * Attached filters
37
     *
38
     * @var resource[]
39
     */
40
    protected $filters = [];
41
42
    /**
43
     * stream resource
44
     *
45
     * @var resource
46
     */
47
    protected $stream;
48
49
    /**
50
     * Tell whether the stream should be closed on object destruction
51
     *
52
     * @var bool
53
     */
54
    protected $should_close_stream = false;
55
56
    /**
57
     * Current iterator value
58
     *
59
     * @var mixed
60
     */
61
    protected $value;
62
63
    /**
64
     * Current iterator key
65
     *
66
     * @var int
67
     */
68
    protected $offset;
69
70
    /**
71
     * Flags for the Document
72
     *
73
     * @var int
74
     */
75
    protected $flags = 0;
76
77
    /**
78
     * the field delimiter (one character only)
79
     *
80
     * @var string
81
     */
82
    protected $delimiter = ',';
83
84
    /**
85
     * the field enclosure character (one character only)
86
     *
87
     * @var string
88
     */
89
    protected $enclosure = '"';
90
91
    /**
92
     * the field escape character (one character only)
93
     *
94
     * @var string
95
     */
96
    protected $escape = '\\';
97
98
    /**
99
     * New instance
100
     *
101
     * @param resource $resource stream type resource
102
     *
103
     * @throws RuntimeException if the argument passed is not a seeakable stream resource
104
     */
105 34
    public function __construct($resource)
106
    {
107 34
        if (!is_resource($resource)) {
108 2
            throw new TypeError(sprintf('Argument passed must be a seekable stream resource, %s given', gettype($resource)));
109
        }
110
111 32
        if ('stream' !== ($type = get_resource_type($resource))) {
112 2
            throw new TypeError(sprintf('Argument passed must be a seekable stream resource, %s resource given', $type));
113
        }
114
115 30
        if (!stream_get_meta_data($resource)['seekable']) {
116 2
            throw new Exception('Argument passed must be a seekable stream resource');
117
        }
118
119 28
        $this->stream = $resource;
120 28
    }
121
122
    /**
123
     * Create a resource.
124
     *
125
     * @param string $url       file url
126
     * @param string $open_mode the file open mode flag
127
     * @param null   $context   the resource context
128
     *
129
     * @throws Exception if the stream resource can not be created
130
     *
131
     * @return resource
132
     */
133 10
    private static function createResource(string $url, string $open_mode, $context)
134
    {
135 10
        $args = [$url, $open_mode];
136 10
        if (null !== $context) {
137
            $args[] = false;
138
            $args[] = $context;
139
        }
140
141 10
        if (!$resource = @fopen(...$args)) {
142 2
            throw new Exception(error_get_last()['message']);
143
        }
144
145 8
        return $resource;
146
    }
147
148
    /**
149
     * @inheritdoc
150
     */
151
    public function __destruct()
152
    {
153 28
        $walker = function ($filter): bool {
154 8
            return stream_filter_remove($filter);
155 28
        };
156
157 28
        array_walk_recursive($this->filters, $walker);
158
159 28
        if ($this->should_close_stream) {
160 18
            fclose($this->stream);
161
        }
162
163 28
        unset($this->stream);
164 28
    }
165
166
    /**
167
     * @inheritdoc
168
     */
169 2
    public function __clone()
170
    {
171 2
        throw new Exception(sprintf('An object of class %s cannot be cloned', get_class($this)));
172
    }
173
174
    /**
175
     * @inheritdoc
176
     */
177 2
    public function __debugInfo()
178
    {
179 2
        return stream_get_meta_data($this->stream) + [
180 2
            'delimiter'      => $this->delimiter,
181 2
            'enclosure'      => $this->enclosure,
182 2
            'escape'         => $this->escape,
183 2
            'stream_filters' => array_keys($this->filters),
184
        ];
185
    }
186
187
    /**
188
     * Return a new instance from a file path
189
     *
190
     * @param string        $path      file path
191
     * @param string        $open_mode the file open mode flag
192
     * @param resource|null $context   the resource context
193
     *
194
     * @throws Exception if the stream resource can not be created
195
     *
196
     * @return static
197
     */
198 12
    public static function createFromPath(string $path, string $open_mode = 'r', $context = null): self
199
    {
200 12
        $resource = self::createResource($path, $open_mode, $context);
0 ignored issues
show
Bug introduced by
It seems like $context defined by parameter $context on line 198 can also be of type resource; however, League\Csv\Stream::createResource() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
201
202 10
        $instance = new static($resource);
203 10
        $instance->should_close_stream = true;
204
205 10
        return $instance;
206
    }
207
208
    /**
209
     * Return a new instance from a file url.
210
     *
211
     * @param string $url       file url
212
     * @param string $open_mode the file open mode flag
213
     * @param null   $context   the resource context
214
     *
215
     * @see http://php.net/manual/es/wrappers.php
216
     *
217
     * @throws Exception if the stream resource can not be created
218
     *
219
     * @return static
220
     */
221
    public static function createFromUrl(string $url, string $open_mode = 'r', $context = null): self
222
    {
223
        $resourceOrigin = self::createResource($url, $open_mode, $context);
224
        rewind($resourceOrigin);
225
226
        $resourceDestination = fopen('php://temp', 'r+');
227
        stream_copy_to_stream($resourceOrigin, $resourceDestination);
228
        rewind($resourceDestination);
229
230
        $instance = new static($resourceDestination);
231
        $instance->should_close_stream = true;
232
233
        return $instance;
234
    }
235
236
    /**
237
     * Return a new instance from a string
238
     *
239
     * @param string $content the CSV document as a string
240
     *
241
     * @return static
242
     */
243 10
    public static function createFromString(string $content): self
244
    {
245 10
        $resource = fopen('php://temp', 'r+');
246 10
        fwrite($resource, $content);
247
248 10
        $instance = new static($resource);
249 10
        $instance->should_close_stream = true;
250
251 10
        return $instance;
252
    }
253
254
    /**
255
     * append a filter
256
     *
257
     * @see http://php.net/manual/en/function.stream-filter-append.php
258
     *
259
     * @param string $filtername
260
     * @param int    $read_write
261
     * @param mixed  $params
262
     *
263
     * @throws Exception if the filter can not be appended
264
     */
265 10
    public function appendFilter(string $filtername, int $read_write, $params = null)
266
    {
267 10
        $res = @stream_filter_append($this->stream, $filtername, $read_write, $params);
268 10
        if (is_resource($res)) {
269 8
            $this->filters[$filtername][] = $res;
270
271 8
            return;
272
        }
273
274 2
        throw new Exception(error_get_last()['message']);
275
    }
276
277
    /**
278
     * Set CSV control
279
     *
280
     * @see http://php.net/manual/en/splfileobject.setcsvcontrol.php
281
     *
282
     * @param string $delimiter
283
     * @param string $enclosure
284
     * @param string $escape
285
     */
286 22
    public function setCsvControl(string $delimiter = ',', string $enclosure = '"', string $escape = '\\')
287
    {
288 22
        $this->delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
289 22
        $this->enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
290 22
        $this->escape = $this->filterControl($escape, 'escape', __METHOD__);
291 22
    }
292
293
    /**
294
     * Set CSV control
295
     *
296
     * @see http://php.net/manual/en/splfileobject.getcsvcontrol.php
297
     *
298
     * @return string[]
299
     */
300 28
    public function getCsvControl()
301
    {
302 28
        return [$this->delimiter, $this->enclosure, $this->escape];
303
    }
304
305
    /**
306
     * Set CSV stream flags
307
     *
308
     * @see http://php.net/manual/en/splfileobject.setflags.php
309
     *
310
     * @param int $flags
311
     */
312 22
    public function setFlags(int $flags)
313
    {
314 22
        $this->flags = $flags;
315 22
    }
316
317
    /**
318
     * Write a field array as a CSV line
319
     *
320
     * @see http://php.net/manual/en/splfileobject.fputcsv.php
321
     *
322
     * @param array  $fields
323
     * @param string $delimiter
324
     * @param string $enclosure
325
     * @param string $escape
326
     *
327
     * @return int|bool
328
     */
329 14
    public function fputcsv(array $fields, string $delimiter = ',', string $enclosure = '"', string $escape = '\\')
330
    {
331 14
        return fputcsv(
332 14
            $this->stream,
333 14
            $fields,
334 14
            $this->filterControl($delimiter, 'delimiter', __METHOD__),
335 12
            $this->filterControl($enclosure, 'enclosure', __METHOD__),
336 10
            $this->filterControl($escape, 'escape', __METHOD__)
337
        );
338
    }
339
340
    /**
341
     * Get line number
342
     *
343
     * @see http://php.net/manual/en/splfileobject.key.php
344
     *
345
     * @return int
346
     */
347 12
    public function key()
348
    {
349 12
        return $this->offset;
350
    }
351
352
    /**
353
     * Read next line
354
     *
355
     * @see http://php.net/manual/en/splfileobject.next.php
356
     *
357
     */
358 12
    public function next()
359
    {
360 12
        $this->value = false;
361 12
        $this->offset++;
362 12
    }
363
364
    /**
365
     * Rewind the file to the first line
366
     *
367
     * @see http://php.net/manual/en/splfileobject.rewind.php
368
     *
369
     */
370 22
    public function rewind()
371
    {
372 22
        rewind($this->stream);
373 22
        $this->offset = 0;
374 22
        $this->value = false;
375 22
        if ($this->flags & SplFileObject::READ_AHEAD) {
376 12
            $this->current();
377
        }
378 22
    }
379
380
    /**
381
     * Not at EOF
382
     *
383
     * @see http://php.net/manual/en/splfileobject.valid.php
384
     *
385
     * @return bool
386
     */
387 20
    public function valid()
388
    {
389 20
        if ($this->flags & SplFileObject::READ_AHEAD) {
390 12
            return $this->current() !== false;
391
        }
392
393 8
        return !feof($this->stream);
394
    }
395
396
    /**
397
     * Retrieves the current line of the file.
398
     *
399
     * @see http://php.net/manual/en/splfileobject.current.php
400
     *
401
     * @return mixed
402
     */
403 26
    public function current()
404
    {
405 26
        if (false !== $this->value) {
406 16
            return $this->value;
407
        }
408
409 26
        $this->value = $this->getCurrentRecord();
410
411 26
        return $this->value;
412
    }
413
414
    /**
415
     * Retrieves the current line as a CSV Record
416
     *
417
     * @return array|bool
418
     */
419 22
    protected function getCurrentRecord()
420
    {
421
        do {
422 22
            $ret = fgetcsv($this->stream, 0, $this->delimiter, $this->enclosure, $this->escape);
423 22
        } while ($this->flags & SplFileObject::SKIP_EMPTY && $ret !== false && $ret[0] === null);
424
425 22
        return $ret;
426
    }
427
428
    /**
429
     * Seek to specified line
430
     *
431
     * @see http://php.net/manual/en/splfileobject.seek.php
432
     *
433
     *
434
     * @param  int       $position
435
     * @throws Exception if the position is negative
436
     */
437 8
    public function seek($position)
438
    {
439 8
        if ($position < 0) {
440 2
            throw new Exception(sprintf('%s() can\'t seek stream to negative line %d', __METHOD__, $position));
441
        }
442
443 6
        $this->rewind();
444 6
        while ($this->key() !== $position && $this->valid()) {
445 2
            $this->current();
446 2
            $this->next();
447
        }
448
449 6
        $this->offset--;
450 6
        $this->current();
451 6
    }
452
453
    /**
454
     * Output all remaining data on a file pointer
455
     *
456
     * @see http://php.net/manual/en/splfileobject.fpatssthru.php
457
     *
458
     * @return int
459
     */
460 2
    public function fpassthru()
461
    {
462 2
        return fpassthru($this->stream);
463
    }
464
465
    /**
466
     * Read from file
467
     *
468
     * @see http://php.net/manual/en/splfileobject.fread.php
469
     *
470
     * @param int $length The number of bytes to read
471
     *
472
     * @return string|false
473
     */
474 8
    public function fread($length)
475
    {
476 8
        return fread($this->stream, $length);
477
    }
478
479
    /**
480
     * Seek to a position
481
     *
482
     * @see http://php.net/manual/en/splfileobject.fseek.php
483
     *
484
     * @param int $offset
485
     * @param int $whence
486
     *
487
     * @return int
488
     */
489 10
    public function fseek(int $offset, int $whence = SEEK_SET)
490
    {
491 10
        return fseek($this->stream, $offset, $whence);
492
    }
493
494
    /**
495
     * Write to stream
496
     *
497
     * @see http://php.net/manual/en/splfileobject.fwrite.php
498
     *
499
     * @param string $str
500
     * @param int    $length
501
     *
502
     * @return int|bool
503
     */
504 2
    public function fwrite(string $str, int $length = 0)
505
    {
506 2
        return fwrite($this->stream, $str, $length);
507
    }
508
509
    /**
510
     * Flushes the output to a file
511
     *
512
     * @see http://php.net/manual/en/splfileobject.fwrite.php
513
     *
514
     * @return bool
515
     */
516 2
    public function fflush()
517
    {
518 2
        return fflush($this->stream);
519
    }
520
}
521