Completed
Push — master ( 80587d...45c62e )
by ignace nyamagana
24:50 queued 22:41
created

AbstractCsv   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 401
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 401
ccs 110
cts 110
cp 1
rs 8.8
c 0
b 0
f 0
wmc 36
lcom 1
cbo 4

23 Methods

Rating   Name   Duplication   Size   Complexity  
A supportsStreamFilter() 0 4 1
A hasStreamFilter() 0 4 1
A __construct() 0 4 1
A __clone() 0 4 1
A createFromFileObject() 0 12 2
A createFromStream() 0 4 1
A createFromString() 0 7 1
A createFromPath() 0 8 2
A getDelimiter() 0 4 1
A getEnclosure() 0 4 1
A getEscape() 0 4 1
A getOutputBOM() 0 4 1
A getInputBOM() 0 11 3
A __toString() 0 4 1
A chunk() 0 18 3
A output() 0 20 3
A setDelimiter() 0 10 2
A resetProperties() 0 3 1
A setEnclosure() 0 10 2
A setEscape() 0 11 2
A setOutputBOM() 0 6 1
A addStreamFilter() 0 12 2
A __destruct() 0 12 2
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 Generator;
18
use League\Csv\Exception\LogicException;
19
use League\Csv\Exception\RuntimeException;
20
use SplFileObject;
21
use function League\Csv\bom_match;
22
23
/**
24
 *  An abstract class to enable CSV document loading.
25
 *
26
 * @package League.csv
27
 * @since   4.0.0
28
 * @author  Ignace Nyamagana Butera <[email protected]>
29
 *
30
 */
31
abstract class AbstractCsv
32
{
33
    use ValidatorTrait;
34
35
    /**
36
     * The CSV document
37
     *
38
     * @var StreamIterator|SplFileObject
39
     */
40
    protected $document;
41
42
    /**
43
     * the field delimiter (one character only)
44
     *
45
     * @var string
46
     */
47
    protected $delimiter = ',';
48
49
    /**
50
     * the field enclosure character (one character only)
51
     *
52
     * @var string
53
     */
54
    protected $enclosure = '"';
55
56
    /**
57
     * the field escape character (one character only)
58
     *
59
     * @var string
60
     */
61
    protected $escape = '\\';
62
63
    /**
64
     * The CSV document BOM sequence
65
     *
66
     * @var string|null
67
     */
68
    protected $input_bom = null;
69
70
    /**
71
     * The Output file BOM character
72
     *
73
     * @var string
74
     */
75
    protected $output_bom = '';
76
77
    /**
78
     * collection of stream filters
79
     *
80
     * @var array
81
     */
82
    protected $stream_filters = [];
83
84
    /**
85
     * The stream filter mode (read or write)
86
     *
87
     * @var int
88
     */
89
    protected $stream_filter_mode;
90
91
    /**
92
     * New instance
93
     *
94
     * @param SplFileObject|StreamIterator $document The CSV Object instance
95
     */
96 198
    protected function __construct($document)
97
    {
98 198
        $this->document = $document;
99 198
    }
100
101
    /**
102
     * @inheritdoc
103
     */
104 2
    public function __clone()
105
    {
106 2
        throw new LogicException('An object of class '.get_class($this).' cannot be cloned');
107
    }
108
109
    /**
110
     * Return a new {@link AbstractCsv} from a SplFileObject
111
     *
112
     * @param SplFileObject $file
113
     *
114
     * @return static
115
     */
116 176
    public static function createFromFileObject(SplFileObject $file): self
117
    {
118 176
        $csv = new static($file);
119 176
        $controls = $file->getCsvControl();
120 176
        $csv->delimiter = $controls[0];
121 176
        $csv->enclosure = $controls[1];
122 176
        if (isset($controls[2])) {
123 176
            $csv->escape = $controls[2];
124
        }
125
126 176
        return $csv;
127
    }
128
129
    /**
130
     * Return a new {@link AbstractCsv} from a PHP resource stream
131
     *
132
     * @param resource $stream
133
     *
134
     * @return static
135
     */
136 12
    public static function createFromStream($stream): self
137
    {
138 12
        return new static(new StreamIterator($stream));
139
    }
140
141
    /**
142
     * Return a new {@link AbstractCsv} from a string
143
     *
144
     * @param string $str the string
145
     *
146
     * @return static
147
     */
148 24
    public static function createFromString(string $str): self
149
    {
150 24
        $stream = fopen('php://temp', 'r+');
151 24
        fwrite($stream, $str);
152
153 24
        return new static(new StreamIterator($stream));
154
    }
155
156
    /**
157
     * Return a new {@link AbstractCsv} from a file path
158
     *
159
     * @param string $path      file path
160
     * @param string $open_mode the file open mode flag
161
     *
162
     * @return static
163
     */
164 26
    public static function createFromPath(string $path, string $open_mode = 'r+'): self
165
    {
166 26
        if (!$stream = @fopen($path, $open_mode)) {
167 2
            throw new RuntimeException(error_get_last()['message']);
168
        }
169
170 24
        return new static(new StreamIterator($stream));
171
    }
172
173
    /**
174
     * Returns the current field delimiter
175
     *
176
     * @return string
177
     */
178 4
    public function getDelimiter(): string
179
    {
180 4
        return $this->delimiter;
181
    }
182
183
    /**
184
     * Returns the current field enclosure
185
     *
186
     * @return string
187
     */
188 4
    public function getEnclosure(): string
189
    {
190 4
        return $this->enclosure;
191
    }
192
193
    /**
194
     * Returns the current field escape character
195
     *
196
     * @return string
197
     */
198 4
    public function getEscape(): string
199
    {
200 4
        return $this->escape;
201
    }
202
203
    /**
204
     * Returns the BOM sequence in use on Output methods
205
     *
206
     * @return string
207
     */
208 2
    public function getOutputBOM(): string
209
    {
210 2
        return $this->output_bom;
211
    }
212
213
    /**
214
     * Returns the BOM sequence of the given CSV
215
     *
216
     * @return string
217
     */
218 144
    public function getInputBOM(): string
219
    {
220 144
        if (null === $this->input_bom) {
221 144
            $this->document->setFlags(SplFileObject::READ_CSV);
222 144
            $this->document->rewind();
223 144
            $line = $this->document->fgets();
224 144
            $this->input_bom = false === $line ? '' : bom_match($line);
225
        }
226
227 144
        return $this->input_bom;
228
    }
229
230
    /**
231
     * Tells whether the stream filter capabilities can be used
232
     *
233
     * @return bool
234
     */
235 4
    public function supportsStreamFilter(): bool
236
    {
237 4
        return $this->document instanceof StreamIterator;
238
    }
239
240
    /**
241
     * Tell whether the specify stream filter is attach to the current stream
242
     *
243
     * @return bool
244
     */
245 4
    public function hasStreamFilter(string $filtername): bool
246
    {
247 4
        return isset($this->stream_filters[$filtername]);
248
    }
249
250
    /**
251
     * Retrieves the CSV content
252
     *
253
     * @return string
254
     */
255 32
    public function __toString(): string
256
    {
257 32
        return implode('', iterator_to_array($this->chunk(8192), false));
258
    }
259
260
    /**
261
     * Retuns the CSV document as a Generator of string chunk
262
     *
263
     * @param int $length number of bytes read
264
     *
265
     * @throws InvalidArgumentException If the length is invalid
266
     *
267
     * @return Generator
268
     */
269 36
    public function chunk(int $length): Generator
270
    {
271 36
        $length = $this->filterMinRange($length, 1, 'The length must be a positive integer');
272 34
        $input_bom = $this->getInputBOM();
273 34
        $this->document->rewind();
274 34
        if ($input_bom != $this->output_bom) {
275 6
            $this->document->fseek(strlen($input_bom));
276 6
            $chunk = $this->output_bom.$this->document->fread($length);
1 ignored issue
show
Bug introduced by
The method fread does only exist in League\Csv\StreamIterator, but not in SplFileObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
277 6
            $this->document->fflush();
278 6
            yield $chunk;
279
        }
280
281 34
        while ($this->document->valid()) {
282 34
            $chunk = $this->document->fread($length);
283 34
            $this->document->fflush();
284 34
            yield $chunk;
285
        }
286 34
    }
287
288
    /**
289
     * Outputs all data on the CSV file
290
     *
291
     * @param string $filename CSV downloaded name if present adds extra headers
292
     *
293
     * @return int Returns the number of characters read from the handle
294
     *             and passed through to the output.
295
     */
296 4
    public function output(string $filename = null): int
297
    {
298 4
        if (null !== $filename) {
299 4
            header('content-type: text/csv');
300 4
            header('content-transfer-encoding: binary');
301 4
            header('content-description: File Transfer');
302 4
            header('content-disposition: attachment; filename="'.rawurlencode($filename).'"');
303
        }
304
305 4
        $res = 0;
306 4
        $input_bom = $this->getInputBOM();
307 4
        $this->document->rewind();
308 4
        if ($input_bom != $this->output_bom) {
309 2
            $res = strlen($this->output_bom);
310 2
            $this->document->fseek(mb_strlen($input_bom));
311 2
            echo $this->output_bom;
312
        }
313
314 4
        return $res + $this->document->fpassthru();
315
    }
316
317
    /**
318
     * Sets the field delimiter
319
     *
320
     * @param string $delimiter
321
     *
322
     * @return static
323
     */
324 10
    public function setDelimiter(string $delimiter): self
325
    {
326 10
        $delimiter = $this->filterControl($delimiter, 'delimiter');
327 10
        if ($delimiter != $this->delimiter) {
328 10
            $this->delimiter = $delimiter;
329 10
            $this->resetProperties();
330
        }
331
332 10
        return $this;
333
    }
334
335
    /**
336
     * Reset dynamic CSV document properties to improve performance
337
     */
338 4
    protected function resetProperties()
339
    {
340 4
    }
341
342
    /**
343
     * Sets the field enclosure
344
     *
345
     * @param string $enclosure
346
     *
347
     * @return static
348
     */
349 2
    public function setEnclosure(string $enclosure): self
350
    {
351 2
        $enclosure = $this->filterControl($enclosure, 'enclosure');
352 2
        if ($enclosure != $this->enclosure) {
353 2
            $this->enclosure = $enclosure;
354 2
            $this->resetProperties();
355
        }
356
357 2
        return $this;
358
    }
359
360
    /**
361
     * Sets the field escape character
362
     *
363
     * @param string $escape
364
     *
365
     * @return static
366
     */
367 2
    public function setEscape(string $escape): self
368
    {
369 2
        $escape = $this->filterControl($escape, 'escape');
370 2
        if ($escape != $this->escape) {
371 2
            $this->escape = $escape;
372 2
            $this->resetProperties();
373
        }
374
375
376 2
        return $this;
377
    }
378
379
    /**
380
     * Sets the BOM sequence to prepend the CSV on output
381
     *
382
     * @param string $str The BOM sequence
383
     *
384
     * @return static
385
     */
386 10
    public function setOutputBOM(string $str): self
387
    {
388 10
        $this->output_bom = $str;
389
390 10
        return $this;
391
    }
392
393
    /**
394
     * append a stream filter
395
     *
396
     * @param string $filtername a string or an object that implements the '__toString' method
397
     * @param mixed  $params     additional parameters for the filter
398
     *
399
     * @throws LogicException If the stream filter API can not be used
400
     *
401
     * @return static
402
     */
403 16
    public function addStreamFilter(string $filtername, $params = null): self
404
    {
405 16
        if (!$this->document instanceof StreamIterator) {
406 2
            throw new LogicException('The stream filter API can not be used');
407
        }
408
409 14
        $this->stream_filters[$filtername][] = $this->document->appendFilter($filtername, $this->stream_filter_mode, $params);
410 14
        $this->resetProperties();
411 14
        $this->input_bom = null;
412
413 14
        return $this;
414
    }
415
416
    /**
417
     * The destructor
418
     */
419 198
    public function __destruct()
420
    {
421 198
        if ($this->document instanceof StreamIterator) {
422 36
            $walker = function ($filter): bool {
423 14
                return $this->document->removeFilter($filter);
1 ignored issue
show
Bug introduced by
The method removeFilter does only exist in League\Csv\StreamIterator, but not in SplFileObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
424 29
            };
425
426 58
            array_walk_recursive($this->stream_filters, $walker);
427
        }
428
429 198
        $this->document = null;
430 198
    }
431
}
432