Completed
Push — master ( 652926...5dcfcf )
by ignace nyamagana
02:58
created

AbstractCsv::chunk()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 5
nop 1
dl 0
loc 17
ccs 11
cts 11
cp 1
crap 4
rs 9.2
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 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
        $input_bom = $this->getInputBOM();
288 8
        $this->document->rewind();
289 8
        $this->document->fseek(strlen($input_bom));
290 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...
291 8
            yield $chunk;
292
        }
293
294 8
        while ($this->document->valid()) {
295 6
            yield $this->document->fread($length);
296
        }
297 8
    }
298
299
    /**
300
     * Outputs all data on the CSV file
301
     *
302
     * @param string $filename CSV downloaded name if present adds extra headers
303
     *
304
     * @return int Returns the number of characters read from the handle
305
     *             and passed through to the output.
306
     */
307 4
    public function output(string $filename = null): int
308
    {
309 4
        if (null !== $filename) {
310 4
            header('Content-Type: text/csv');
311 4
            header('Content-Transfer-Encoding: binary');
312 4
            header('Content-Description: File Transfer');
313 4
            header('Content-Disposition: attachment; filename="'.rawurlencode($filename).'"');
314
        }
315
316 4
        $res = 0;
317 4
        $input_bom = $this->getInputBOM();
318 4
        $this->document->rewind();
319 4
        if ($input_bom != $this->output_bom) {
320 2
            $res = strlen($this->output_bom);
321 2
            $this->document->fseek(mb_strlen($input_bom));
322 2
            echo $this->output_bom;
323
        }
324
325 4
        return $res + $this->document->fpassthru();
326
    }
327
328
    /**
329
     * Sets the field delimiter
330
     *
331
     * @param string $delimiter
332
     *
333
     * @return static
334
     */
335 10
    public function setDelimiter(string $delimiter): self
336
    {
337 10
        if ($delimiter != $this->delimiter) {
338 8
            $this->delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
339
        }
340
341 10
        return $this;
342
    }
343
344
    /**
345
     * Reset dynamic object properties to improve performance
346
     */
347 2
    protected function resetProperties()
348
    {
349 2
    }
350
351
    /**
352
     * Sets the field enclosure
353
     *
354
     * @param string $enclosure
355
     *
356
     * @return static
357
     */
358 2
    public function setEnclosure(string $enclosure): self
359
    {
360 2
        if ($enclosure != $this->enclosure) {
361 2
            $this->enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
362
        }
363
364 2
        return $this;
365
    }
366
367
    /**
368
     * Sets the field escape character
369
     *
370
     * @param string $escape
371
     *
372
     * @return static
373
     */
374 2
    public function setEscape(string $escape): self
375
    {
376 2
        if ($escape != $this->escape) {
377 2
            $this->escape = $this->filterControl($escape, 'escape', __METHOD__);
378
        }
379
380 2
        return $this;
381
    }
382
383
    /**
384
     * Sets the BOM sequence to prepend the CSV on output
385
     *
386
     * @param string $str The BOM sequence
387
     *
388
     * @return static
389
     */
390 6
    public function setOutputBOM(string $str): self
391
    {
392 6
        $this->output_bom = $str;
393
394 6
        return $this;
395
    }
396
397
    /**
398
     * append a stream filter
399
     *
400
     * @param string $filtername a string or an object that implements the '__toString' method
401
     * @param mixed  $params     additional parameters for the filter
402
     *
403
     * @throws LogicException If the stream filter API can not be used
404
     *
405
     * @return static
406
     */
407 10
    public function addStreamFilter(string $filtername, $params = null): self
408
    {
409 10
        if (!$this->document instanceof Document) {
410 2
            throw new LogicException('The stream filter API can not be used');
411
        }
412
413 8
        $this->document->appendFilter($filtername, $this->stream_filter_mode, $params);
414 6
        $this->stream_filters[$filtername] = true;
415 6
        $this->resetProperties();
416 6
        $this->input_bom = null;
417
418 6
        return $this;
419
    }
420
}
421