StreamIterator   B
last analyzed

Coupling/Cohesion

Components 2
Dependencies 3

Complexity

Total Complexity 44

Size/Duplication

Total Lines 444
Duplicated Lines 0 %

Test Coverage

Coverage 99.07%

Importance

Changes 0
Metric Value
wmc 44
lcom 2
cbo 3
dl 0
loc 444
ccs 106
cts 107
cp 0.9907
rs 8.3396
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 8 2
A __construct() 0 16 4
A __clone() 0 4 1
A createFromPath() 0 17 3
A createFromString() 0 10 1
A appendFilter() 0 9 2
A setCsvControl() 0 6 1
A setFlags() 0 4 1
A fputcsv() 0 10 1
A key() 0 4 1
A next() 0 5 1
A rewind() 0 9 2
A valid() 0 8 2
A current() 0 16 3
A getCurrentRecord() 0 8 4
A getCurrentLine() 0 8 4
A seek() 0 12 4
A fgets() 0 8 2
A fpassthru() 0 4 1
A fread() 0 4 1
A fseek() 0 4 1
A fwrite() 0 4 1
A fflush() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like StreamIterator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StreamIterator, and based on these observations, apply Extract Interface, too.

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 seekable 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 StreamIterator 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 StreamIterator
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 32
    public function __construct($resource)
107
    {
108 32
        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 30
        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 28
        if (!stream_get_meta_data($resource)['seekable']) {
117 2
            throw new RuntimeException('Argument passed must be a seekable stream resource');
118
        }
119
120 26
        $this->stream = $resource;
121 26
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126 26
    public function __destruct()
127
    {
128 26
        if ($this->should_close_stream) {
129 16
            fclose($this->stream);
130
        }
131
132 26
        $this->stream = null;
133 26
    }
134
135
    /**
136
     * @inheritdoc
137
     */
138 2
    public function __clone()
139
    {
140 2
        throw new LogicException(sprintf('An object of class %s cannot be cloned', get_class($this)));
141
    }
142
143
    /**
144
     * Return a new instance from a file path
145
     *
146
     * @param string        $path      file path
147
     * @param string        $open_mode the file open mode flag
148
     * @param resource|null $context   the resource context
149
     *
150
     * @throws RuntimeException if the stream resource can not be created
151
     *
152
     * @return static
153
     */
154 12
    public static function createFromPath(string $path, string $open_mode = 'r', $context = null): self
155
    {
156 12
        $args = [$path, $open_mode];
157 12
        if (null !== $context) {
158 2
            $args[] = false;
159 2
            $args[] = $context;
160
        }
161
162 12
        if (!$resource = @fopen(...$args)) {
163 2
            throw new RuntimeException(error_get_last()['message']);
164
        }
165
166 10
        $instance = new static($resource);
167 10
        $instance->should_close_stream = true;
168
169 10
        return $instance;
170
    }
171
172
    /**
173
     * Return a new instance from a string
174
     *
175
     * @param string $content the CSV document as a string
176
     *
177
     * @return static
178
     */
179 8
    public static function createFromString(string $content): self
180
    {
181 8
        $resource = fopen('php://temp', 'r+');
182 8
        fwrite($resource, $content);
183
184 8
        $instance = new static($resource);
185 8
        $instance->should_close_stream = true;
186
187 8
        return $instance;
188
    }
189
190
    /**
191
     * append a filter
192
     *
193
     * @see http://php.net/manual/en/function.stream-filter-append.php
194
     *
195
     * @param string $filter_name
196
     * @param int    $read_write
197
     * @param mixed  $params
198
     *
199
     * @throws RuntimeException if the filter can not be appended
200
     *
201
     * @return resource
202
     */
203 10
    public function appendFilter(string $filter_name, int $read_write, $params = null)
204
    {
205 10
        $res = @stream_filter_append($this->stream, $filter_name, $read_write, $params);
206 10
        if (is_resource($res)) {
207 8
            return $res;
208
        }
209
210 2
        throw new RuntimeException(error_get_last()['message']);
211
    }
212
213
    /**
214
     * Set CSV control
215
     *
216
     * @see http://php.net/manual/en/splfileobject.setcsvcontrol.php
217
     *
218
     * @param string $delimiter
219
     * @param string $enclosure
220
     * @param string $escape
221
     */
222 12
    public function setCsvControl(string $delimiter = ',', string $enclosure = '"', string $escape = '\\')
223
    {
224 12
        $this->delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
225 12
        $this->enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
226 12
        $this->escape = $this->filterControl($escape, 'escape', __METHOD__);
227 12
    }
228
229
    /**
230
     * Set StreamIterator Flags
231
     *
232
     * @see http://php.net/manual/en/splfileobject.setflags.php
233
     *
234
     * @param int $flags
235
     */
236 22
    public function setFlags(int $flags)
237
    {
238 22
        $this->flags = $flags;
239 22
    }
240
241
    /**
242
     * Write a field array as a CSV line
243
     *
244
     * @see http://php.net/manual/en/splfileobject.fputcsv.php
245
     *
246
     * @param array  $fields
247
     * @param string $delimiter
248
     * @param string $enclosure
249
     * @param string $escape
250
     *
251
     * @return int|bool
252
     */
253 14
    public function fputcsv(array $fields, string $delimiter = ',', string $enclosure = '"', string $escape = '\\')
254
    {
255 14
        return fputcsv(
256 14
            $this->stream,
257
            $fields,
258 14
            $this->filterControl($delimiter, 'delimiter', __METHOD__),
259 12
            $this->filterControl($enclosure, 'enclosure', __METHOD__),
260 10
            $this->filterControl($escape, 'escape', __METHOD__)
261
        );
262
    }
263
264
    /**
265
     * Get line number
266
     *
267
     * @see http://php.net/manual/en/splfileobject.key.php
268
     *
269
     * @return int
270
     */
271 12
    public function key()
272
    {
273 12
        return $this->offset;
274
    }
275
276
    /**
277
     * Read next line
278
     *
279
     * @see http://php.net/manual/en/splfileobject.next.php
280
     *
281
     */
282 12
    public function next()
283
    {
284 12
        $this->value = false;
285 12
        $this->offset++;
286 12
    }
287
288
    /**
289
     * Rewind the file to the first line
290
     *
291
     * @see http://php.net/manual/en/splfileobject.rewind.php
292
     *
293
     */
294 22
    public function rewind()
295
    {
296 22
        rewind($this->stream);
297 22
        $this->offset = 0;
298 22
        $this->value = false;
299 22
        if ($this->flags & SplFileObject::READ_AHEAD) {
300 12
            $this->current();
301
        }
302 22
    }
303
304
    /**
305
     * Not at EOF
306
     *
307
     * @see http://php.net/manual/en/splfileobject.valid.php
308
     *
309
     * @return bool
310
     */
311 20
    public function valid()
312
    {
313 20
        if ($this->flags & SplFileObject::READ_AHEAD) {
314 12
            return $this->current() !== false;
315
        }
316
317 8
        return !feof($this->stream);
318
    }
319
320
    /**
321
     * Retrieves the current line of the file.
322
     *
323
     * @see http://php.net/manual/en/splfileobject.current.php
324
     *
325
     * @return mixed
326
     */
327 16
    public function current()
328
    {
329 16
        if (false !== $this->value) {
330 16
            return $this->value;
331
        }
332
333 16
        if (($this->flags & SplFileObject::READ_CSV) == SplFileObject::READ_CSV) {
334 12
            $this->value = $this->getCurrentRecord();
335
336 12
            return $this->value;
337
        }
338
339 4
        $this->value = $this->getCurrentLine();
340
341 4
        return $this->value;
342
    }
343
344
    /**
345
     * Retrieves the current line as a CSV Record
346
     *
347
     * @return array|bool
348
     */
349 12
    protected function getCurrentRecord()
350
    {
351
        do {
352 12
            $ret = fgetcsv($this->stream, 0, $this->delimiter, $this->enclosure, $this->escape);
353 12
        } while ($this->flags & SplFileObject::SKIP_EMPTY && $ret !== false && $ret[0] === null);
354
355 12
        return $ret;
356
    }
357
358
    /**
359
     * Retrieves the current line as a string
360
     *
361
     * @return string|bool
362
     */
363 22
    protected function getCurrentLine()
364
    {
365
        do {
366 22
            $line = fgets($this->stream);
367 22
        } while ($this->flags & SplFileObject::SKIP_EMPTY && $line !== false && rtrim($line, "\r\n") !== '');
368
369 22
        return $line;
370
    }
371
372
    /**
373
     * Seek to specified line
374
     *
375
     * @see http://php.net/manual/en/splfileobject.seek.php
376
     *
377
     * @param int $position
378
     */
379 4
    public function seek($position)
380
    {
381 4
        $pos = $this->filterMinRange((int) $position, 0, __METHOD__.'() Can\'t seek stream to negative line %d');
382 4
        foreach ($this as $key => $value) {
383 4
            if ($key === $pos || feof($this->stream)) {
384 4
                $this->offset--;
385 4
                break;
386
            }
387
        }
388
389 4
        $this->current();
390 4
    }
391
392
    /**
393
     * Gets line from file
394
     *
395
     * @see http://php.net/manual/en/splfileobject.fgets.php
396
     *
397
     * @return string|bool
398
     */
399 26
    public function fgets()
400
    {
401 26
        if (false !== $this->value) {
402 4
            $this->next();
403
        }
404
405 26
        return $this->value = $this->getCurrentLine();
406
    }
407
408
    /**
409
     * Output all remaining data on a file pointer
410
     *
411
     * @see http://php.net/manual/en/splfileobject.fpatssthru.php
412
     *
413
     * @return int
414
     */
415 2
    public function fpassthru()
416
    {
417 2
        return fpassthru($this->stream);
418
    }
419
420
    /**
421
     * Read from file
422
     *
423
     * @see http://php.net/manual/en/splfileobject.fread.php
424
     *
425
     * @param int $length The number of bytes to read
426
     *
427
     * @return string|false
428
     */
429 8
    public function fread($length)
430
    {
431 8
        return fread($this->stream, $length);
432
    }
433
434
    /**
435
     * Seek to a position
436
     *
437
     * @see http://php.net/manual/en/splfileobject.fseek.php
438
     *
439
     * @param int $offset
440
     * @param int $whence
441
     *
442
     * @return int
443
     */
444 4
    public function fseek(int $offset, int $whence = SEEK_SET)
445
    {
446 4
        return fseek($this->stream, $offset, $whence);
447
    }
448
449
    /**
450
     * Write to stream
451
     *
452
     * @see http://php.net/manual/en/splfileobject.fwrite.php
453
     *
454
     * @param string $str
455
     * @param int    $length
456
     *
457
     * @return int|bool
458
     */
459 2
    public function fwrite(string $str, int $length = 0)
460
    {
461 2
        return fwrite($this->stream, $str, $length);
462
    }
463
464
    /**
465
     * Flushes the output to a file
466
     *
467
     * @see http://php.net/manual/en/splfileobject.fwrite.php
468
     *
469
     * @return bool
470
     */
471 8
    public function fflush()
472
    {
473 8
        return fflush($this->stream);
474
    }
475
}
476