Completed
Push — master ( 5dcfcf...ea4e66 )
by ignace nyamagana
02:54
created

AbstractCsv   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 384
ccs 89
cts 89
cp 1
rs 9
c 0
b 0
f 0
wmc 35
lcom 2
cbo 4

24 Methods

Rating   Name   Duplication   Size   Complexity  
A setOutputBOM() 0 6 1
A __construct() 0 5 1
A __destruct() 0 4 1
A __clone() 0 4 1
A createFromFileObject() 0 4 1
A createFromStream() 0 4 1
A createFromString() 0 4 1
A createFromPath() 0 4 1
A getDelimiter() 0 4 1
A getEnclosure() 0 4 1
A getEscape() 0 4 1
A getOutputBOM() 0 4 1
A getInputBOM() 0 12 3
A getStreamFilterMode() 0 4 1
A supportsStreamFilter() 0 4 1
A hasStreamFilter() 0 4 1
A __toString() 0 9 2
A chunk() 0 16 4
A output() 0 15 2
A setDelimiter() 0 8 2
A resetProperties() 0 3 1
A setEnclosure() 0 8 2
A setEscape() 0 8 2
A addStreamFilter() 0 13 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\OutOfRangeException;
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 implements ByteSequence
32
{
33
    use ValidatorTrait;
34
35
    /**
36
     * The stream filter mode (read or write)
37
     *
38
     * @var int
39
     */
40
    protected $stream_filter_mode;
41
42
43
    /**
44
     * collection of stream filters
45
     *
46
     * @var bool[]
47
     */
48
    protected $stream_filters = [];
49
50
    /**
51
     * The CSV document BOM sequence
52
     *
53
     * @var string|null
54
     */
55
    protected $input_bom = null;
56
57
    /**
58
     * The Output file BOM character
59
     *
60
     * @var string
61
     */
62
    protected $output_bom = '';
63
64
    /**
65
     * the field delimiter (one character only)
66
     *
67
     * @var string
68
     */
69
    protected $delimiter = ',';
70
71
    /**
72
     * the field enclosure character (one character only)
73
     *
74
     * @var string
75
     */
76
    protected $enclosure = '"';
77
78
    /**
79
     * the field escape character (one character only)
80
     *
81
     * @var string
82
     */
83
    protected $escape = '\\';
84
85
    /**
86
     * The CSV document
87
     *
88
     * @var Document|SplFileObject
89
     */
90
    protected $document;
91
92
    /**
93
     * New instance
94
     *
95
     * @param SplFileObject|Document $document The CSV Object instance
96
     */
97 22
    protected function __construct($document)
98
    {
99 22
        $this->document = $document;
100 22
        list($this->delimiter, $this->enclosure, $this->escape) = $this->document->getCsvControl();
101 22
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106 24
    public function __destruct()
107
    {
108 24
        $this->document = null;
109 24
    }
110
111
    /**
112
     * @inheritdoc
113
     */
114 2
    public function __clone()
115
    {
116 2
        throw new LogicException(sprintf('An object of class %s cannot be cloned', get_class($this)));
117
    }
118
119
    /**
120
     * Return a new instance from a SplFileObject
121
     *
122
     * @param SplFileObject $file
123
     *
124
     * @return static
125
     */
126 24
    public static function createFromFileObject(SplFileObject $file): self
127
    {
128 24
        return new static($file);
129
    }
130
131
    /**
132
     * Return a new instance from a PHP resource stream
133
     *
134
     * @param resource $stream
135
     *
136
     * @return static
137
     */
138 4
    public static function createFromStream($stream): self
139
    {
140 4
        return new static(new Document($stream));
141
    }
142
143
    /**
144
     * Return a new instance from a string
145
     *
146
     * @param string $content the CSV document as a string
147
     *
148
     * @return static
149
     */
150 4
    public static function createFromString(string $content): self
151
    {
152 4
        return new static(Document::createFromString($content));
153
    }
154
155
    /**
156
     * Return a new instance from a file path
157
     *
158
     * @param string        $path      file path
159
     * @param string        $open_mode the file open mode flag
160
     * @param resource|null $context   the resource context
161
     *
162
     * @return static
163
     */
164 4
    public static function createFromPath(string $path, string $open_mode = 'r+', $context = null): self
165
    {
166 4
        return new static(Document::createFromPath($path, $open_mode, $context));
167
    }
168
169
    /**
170
     * Returns the current field delimiter
171
     *
172
     * @return string
173
     */
174 10
    public function getDelimiter(): string
175
    {
176 10
        return $this->delimiter;
177
    }
178
179
    /**
180
     * Returns the current field enclosure
181
     *
182
     * @return string
183
     */
184 2
    public function getEnclosure(): string
185
    {
186 2
        return $this->enclosure;
187
    }
188
189
    /**
190
     * Returns the current field escape character
191
     *
192
     * @return string
193
     */
194 2
    public function getEscape(): string
195
    {
196 2
        return $this->escape;
197
    }
198
199
    /**
200
     * Returns the BOM sequence in use on Output methods
201
     *
202
     * @return string
203
     */
204 2
    public function getOutputBOM(): string
205
    {
206 2
        return $this->output_bom;
207
    }
208
209
    /**
210
     * Returns the BOM sequence of the given CSV
211
     *
212
     * @return string
213
     */
214 12
    public function getInputBOM(): string
215
    {
216 12
        if (null === $this->input_bom) {
217 12
            $this->document->setFlags(SplFileObject::READ_CSV);
218 12
            $this->document->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
219 12
            $this->document->rewind();
220 12
            $line = implode('', (array) $this->document->current());
221 12
            $this->input_bom = false === $line ? '' : bom_match($line);
222
        }
223
224 12
        return $this->input_bom;
225
    }
226
227
    /**
228
     * Returns the stream filter mode
229
     *
230
     * @return int
231
     */
232 2
    public function getStreamFilterMode(): int
233
    {
234 2
        return $this->stream_filter_mode;
235
    }
236
237
    /**
238
     * Tells whether the stream filter capabilities can be used
239
     *
240
     * @return bool
241
     */
242 4
    public function supportsStreamFilter(): bool
243
    {
244 4
        return $this->document instanceof Document;
245
    }
246
247
    /**
248
     * Tell whether the specify stream filter is attach to the current stream
249
     *
250
     * @param string $filtername
251
     *
252
     * @return bool
253
     */
254 2
    public function hasStreamFilter(string $filtername): bool
255
    {
256 2
        return $this->stream_filters[$filtername] ?? false;
257
    }
258
259
    /**
260
     * Retrieves the CSV content
261
     *
262
     * @return string
263
     */
264 12
    public function __toString(): string
265
    {
266 12
        $raw = '';
267 12
        foreach ($this->chunk(8192) as $chunk) {
268 12
            $raw .= $chunk;
269
        }
270
271 12
        return $raw;
272
    }
273
274
    /**
275
     * Retuns the CSV document as a Generator of string chunk
276
     *
277
     * @param int $length number of bytes read
278
     *
279
     * @return Generator
280
     */
281 10
    public function chunk(int $length): Generator
282
    {
283 10
        if ($length < 1) {
284 2
            throw new OutOfRangeException(sprintf('%s() expects the length to be a positive integer %d given', __METHOD__, $length));
285
        }
286
287 8
        $this->document->rewind();
288 8
        $this->document->fseek(strlen($this->getInputBOM()));
289 8
        foreach (str_split($this->output_bom.$this->document->fread($length), $length) as $chunk) {
1 ignored issue
show
Bug introduced by
The method fread does only exist in League\Csv\Document, 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...
290 8
            yield $chunk;
291
        }
292
293 8
        while ($this->document->valid()) {
294 6
            yield $this->document->fread($length);
295
        }
296 8
    }
297
298
    /**
299
     * Outputs all data on the CSV file
300
     *
301
     * @param string $filename CSV downloaded name if present adds extra headers
302
     *
303
     * @return int Returns the number of characters read from the handle
304
     *             and passed through to the output.
305
     */
306 4
    public function output(string $filename = null): int
307
    {
308 4
        if (null !== $filename) {
309 4
            header('Content-Type: text/csv');
310 4
            header('Content-Transfer-Encoding: binary');
311 4
            header('Content-Description: File Transfer');
312 4
            header('Content-Disposition: attachment; filename="'.rawurlencode($filename).'"');
313
        }
314
315 4
        $this->document->rewind();
316 4
        $this->document->fseek(strlen($this->getInputBOM()));
317 4
        echo $this->output_bom;
318
319 4
        return strlen($this->output_bom) + $this->document->fpassthru();
320
    }
321
322
    /**
323
     * Sets the field delimiter
324
     *
325
     * @param string $delimiter
326
     *
327
     * @return static
328
     */
329 10
    public function setDelimiter(string $delimiter): self
330
    {
331 10
        if ($delimiter != $this->delimiter) {
332 8
            $this->delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
333
        }
334
335 10
        return $this;
336
    }
337
338
    /**
339
     * Reset dynamic object properties to improve performance
340
     */
341 2
    protected function resetProperties()
342
    {
343 2
    }
344
345
    /**
346
     * Sets the field enclosure
347
     *
348
     * @param string $enclosure
349
     *
350
     * @return static
351
     */
352 2
    public function setEnclosure(string $enclosure): self
353
    {
354 2
        if ($enclosure != $this->enclosure) {
355 2
            $this->enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
356
        }
357
358 2
        return $this;
359
    }
360
361
    /**
362
     * Sets the field escape character
363
     *
364
     * @param string $escape
365
     *
366
     * @return static
367
     */
368 2
    public function setEscape(string $escape): self
369
    {
370 2
        if ($escape != $this->escape) {
371 2
            $this->escape = $this->filterControl($escape, 'escape', __METHOD__);
372
        }
373
374 2
        return $this;
375
    }
376
377
    /**
378
     * Sets the BOM sequence to prepend the CSV on output
379
     *
380
     * @param string $str The BOM sequence
381
     *
382
     * @return static
383
     */
384 6
    public function setOutputBOM(string $str): self
385
    {
386 6
        $this->output_bom = $str;
387
388 6
        return $this;
389
    }
390
391
    /**
392
     * append a stream filter
393
     *
394
     * @param string $filtername a string or an object that implements the '__toString' method
395
     * @param mixed  $params     additional parameters for the filter
396
     *
397
     * @throws LogicException If the stream filter API can not be used
398
     *
399
     * @return static
400
     */
401 10
    public function addStreamFilter(string $filtername, $params = null): self
402
    {
403 10
        if (!$this->document instanceof Document) {
404 2
            throw new LogicException('The stream filter API can not be used');
405
        }
406
407 8
        $this->document->appendFilter($filtername, $this->stream_filter_mode, $params);
408 6
        $this->stream_filters[$filtername] = true;
409 6
        $this->resetProperties();
410 6
        $this->input_bom = null;
411
412 6
        return $this;
413
    }
414
}
415