Completed
Push — master ( ecd0f8...3ad52b )
by ignace nyamagana
15:08
created

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