AbstractCsv   B
last analyzed

Coupling/Cohesion

Components 1
Dependencies 3

Complexity

Total Complexity 37

Size/Duplication

Total Lines 409
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 409
ccs 111
cts 111
cp 1
rs 8.6
c 0
b 0
f 0
wmc 37
lcom 1
cbo 3

24 Methods

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