Completed
Push — master ( 6ca7fe...652926 )
by ignace nyamagana
03:13 queued 02:01
created

Document::seek()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

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