Completed
Push — master ( d65c9c...907617 )
by ignace nyamagana
02:56
created

Document::fflush()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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