AbstractCsv   B
last analyzed

↳ Parent: Project

Coupling/Cohesion

Components 1
Dependencies 4

Complexity

Total Complexity 37

Size/Duplication

Total Lines 407
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 407
ccs 113
cts 113
cp 1
rs 8.6
c 0
b 0
f 0
wmc 37
lcom 1
cbo 4

23 Methods

Rating   Name   Duplication   Size   Complexity  
A supportsStreamFilter() 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 hasStreamFilter() 0 4 1
A __toString() 0 9 2
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 [email protected] 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 [email protected] 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 [email protected] AbstractCsv} from a string
143
     *
144
     * @param string $str the CSV document as a 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 [email protected] 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
     * @param  string $filtername
244
     * @return bool
245
     */
246 4
    public function hasStreamFilter(string $filtername): bool
247
    {
248 4
        return isset($this->stream_filters[$filtername]);
249
    }
250
251
    /**
252
     * Retrieves the CSV content
253
     *
254
     * @return string
255
     */
256 32
    public function __toString(): string
257
    {
258 32
        $raw = '';
259 32
        foreach ($this->chunk(8192) as $chunk) {
260 32
            $raw .= $chunk;
261
        }
262
263 32
        return $raw;
264
    }
265
266
    /**
267
     * Retuns the CSV document as a Generator of string chunk
268
     *
269
     * @param int $length number of bytes read
270
     *
271
     * @throws InvalidArgumentException If the length is invalid
272
     *
273
     * @return Generator
274
     */
275 36
    public function chunk(int $length): Generator
276
    {
277 36
        $length = $this->filterMinRange($length, 1, 'The length must be a positive integer');
278 34
        $input_bom = $this->getInputBOM();
279 34
        $this->document->rewind();
280 34
        if ($input_bom != $this->output_bom) {
281 6
            $this->document->fseek(strlen($input_bom));
282 6
            $chunk = $this->output_bom.$this->document->fread($length);
283 6
            $this->document->fflush();
284 6
            yield $chunk;
285
        }
286
287 34
        while ($this->document->valid()) {
288 34
            $chunk = $this->document->fread($length);
289 34
            $this->document->fflush();
290 34
            yield $chunk;
291
        }
292 34
    }
293
294
    /**
295
     * Outputs all data on the CSV file
296
     *
297
     * @param string $filename CSV downloaded name if present adds extra headers
298
     *
299
     * @return int Returns the number of characters read from the handle
300
     *             and passed through to the output.
301
     */
302 4
    public function output(string $filename = null): int
303
    {
304 4
        if (null !== $filename) {
305 4
            header('content-type: text/csv');
306 4
            header('content-transfer-encoding: binary');
307 4
            header('content-description: File Transfer');
308 4
            header('content-disposition: attachment; filename="'.rawurlencode($filename).'"');
309
        }
310
311 4
        $res = 0;
312 4
        $input_bom = $this->getInputBOM();
313 4
        $this->document->rewind();
314 4
        if ($input_bom != $this->output_bom) {
315 2
            $res = strlen($this->output_bom);
316 2
            $this->document->fseek(mb_strlen($input_bom));
317 2
            echo $this->output_bom;
318
        }
319
320 4
        return $res + $this->document->fpassthru();
321
    }
322
323
    /**
324
     * Sets the field delimiter
325
     *
326
     * @param string $delimiter
327
     *
328
     * @return static
329
     */
330 10
    public function setDelimiter(string $delimiter): self
331
    {
332 10
        $delimiter = $this->filterControl($delimiter, 'delimiter');
333 10
        if ($delimiter != $this->delimiter) {
334 10
            $this->delimiter = $delimiter;
335 10
            $this->resetProperties();
336
        }
337
338 10
        return $this;
339
    }
340
341
    /**
342
     * Reset dynamic object properties to improve performance
343
     */
344 4
    protected function resetProperties()
345
    {
346 4
    }
347
348
    /**
349
     * Sets the field enclosure
350
     *
351
     * @param string $enclosure
352
     *
353
     * @return static
354
     */
355 2
    public function setEnclosure(string $enclosure): self
356
    {
357 2
        $enclosure = $this->filterControl($enclosure, 'enclosure');
358 2
        if ($enclosure != $this->enclosure) {
359 2
            $this->enclosure = $enclosure;
360 2
            $this->resetProperties();
361
        }
362
363 2
        return $this;
364
    }
365
366
    /**
367
     * Sets the field escape character
368
     *
369
     * @param string $escape
370
     *
371
     * @return static
372
     */
373 2
    public function setEscape(string $escape): self
374
    {
375 2
        $escape = $this->filterControl($escape, 'escape');
376 2
        if ($escape != $this->escape) {
377 2
            $this->escape = $escape;
378 2
            $this->resetProperties();
379
        }
380
381
382 2
        return $this;
383
    }
384
385
    /**
386
     * Sets the BOM sequence to prepend the CSV on output
387
     *
388
     * @param string $str The BOM sequence
389
     *
390
     * @return static
391
     */
392 10
    public function setOutputBOM(string $str): self
393
    {
394 10
        $this->output_bom = $str;
395
396 10
        return $this;
397
    }
398
399
    /**
400
     * append a stream filter
401
     *
402
     * @param string $filtername a string or an object that implements the '__toString' method
403
     * @param mixed  $params     additional parameters for the filter
404
     *
405
     * @throws LogicException If the stream filter API can not be used
406
     *
407
     * @return static
408
     */
409 16
    public function addStreamFilter(string $filtername, $params = null): self
410
    {
411 16
        if (!$this->document instanceof StreamIterator) {
412 2
            throw new LogicException('The stream filter API can not be used');
413
        }
414
415 14
        $this->stream_filters[$filtername][] = $this->document->appendFilter($filtername, $this->stream_filter_mode, $params);
416 14
        $this->resetProperties();
417 14
        $this->input_bom = null;
418
419 14
        return $this;
420
    }
421
422
    /**
423
     * The destructor
424
     */
425 198
    public function __destruct()
426
    {
427 198
        if ($this->document instanceof StreamIterator) {
428 36
            $walker = function ($filter): bool {
429 14
                return $this->document->removeFilter($filter);
430 29
            };
431
432 58
            array_walk_recursive($this->stream_filters, $walker);
433
        }
434
435 198
        $this->document = null;
436 198
    }
437
}
438