Completed
Push — master ( a327ab...c6c647 )
by ignace nyamagana
55:37 queued 54:07
created

Stream::fwrite()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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