Completed
Push — master ( 4b6f1b...73fa83 )
by ignace nyamagana
04:07 queued 02:49
created

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