Completed
Push — master ( 4b6f1b...73fa83 )
by ignace nyamagana
02:47
created

AbstractCsv   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 414
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 100%

Importance

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

24 Methods

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