Completed
Pull Request — master (#263)
by
unknown
02:12
created

Stream::__debugInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.5

Importance

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