AbstractCsv   A
last analyzed

Coupling/Cohesion

Components 2
Dependencies 4

Complexity

Total Complexity 34

Size/Duplication

Total Lines 390
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 390
ccs 94
cts 94
cp 1
rs 9.2
c 0
b 0
f 0
wmc 34
lcom 2
cbo 4

24 Methods

Rating   Name   Duplication   Size   Complexity  
A setOutputBOM() 0 6 1
A resetProperties() 0 3 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 addStreamFilter() 0 13 2
A getInputBOM() 0 13 2
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 setDelimiter() 0 9 2
A setEnclosure() 0 9 2
A setEscape() 0 9 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 16
    public function getInputBOM(): string
215
    {
216 16
        if (null !== $this->input_bom) {
217 2
            return $this->input_bom;
218
        }
219
220 16
        $this->document->setFlags(SplFileObject::READ_CSV);
221 16
        $this->document->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
222 16
        $this->document->rewind();
223 16
        $this->input_bom = bom_match(implode(',', (array) $this->document->current()));
224
225 16
        return $this->input_bom;
226
    }
227
228
    /**
229
     * Returns the stream filter mode
230
     *
231
     * @return int
232
     */
233 2
    public function getStreamFilterMode(): int
234
    {
235 2
        return $this->stream_filter_mode;
236
    }
237
238
    /**
239
     * Tells whether the stream filter capabilities can be used
240
     *
241
     * @return bool
242
     */
243 4
    public function supportsStreamFilter(): bool
244
    {
245 4
        return $this->document instanceof Document;
246
    }
247
248
    /**
249
     * Tell whether the specify stream filter is attach to the current stream
250
     *
251
     * @param string $filtername
252
     *
253
     * @return bool
254
     */
255 2
    public function hasStreamFilter(string $filtername): bool
256
    {
257 2
        return $this->stream_filters[$filtername] ?? false;
258
    }
259
260
    /**
261
     * Retrieves the CSV content
262
     *
263
     * @return string
264
     */
265 12
    public function __toString(): string
266
    {
267 12
        $raw = '';
268 12
        foreach ($this->chunk(8192) as $chunk) {
269 12
            $raw .= $chunk;
270
        }
271
272 12
        return $raw;
273
    }
274
275
    /**
276
     * Retuns the CSV document as a Generator of string chunk
277
     *
278
     * @param int $length number of bytes read
279
     *
280
     * @return Generator
281
     */
282 10
    public function chunk(int $length): Generator
283
    {
284 10
        if ($length < 1) {
285 2
            throw new OutOfRangeException(sprintf('%s() expects the length to be a positive integer %d given', __METHOD__, $length));
286
        }
287
288 8
        $input_bom = $this->getInputBOM();
289 8
        $this->document->rewind();
290 8
        $this->document->fseek(strlen($input_bom));
291 8
        foreach (str_split($this->output_bom.$this->document->fread($length), $length) as $chunk) {
292 8
            yield $chunk;
293
        }
294
295 8
        while ($this->document->valid()) {
296 6
            yield $this->document->fread($length);
297
        }
298 8
    }
299
300
    /**
301
     * Outputs all data on the CSV file
302
     *
303
     * @param string $filename CSV downloaded name if present adds extra headers
304
     *
305
     * @return int Returns the number of characters read from the handle
306
     *             and passed through to the output.
307
     */
308 4
    public function output(string $filename = null): int
309
    {
310 4
        if (null !== $filename) {
311 4
            header('Content-Type: text/csv');
312 4
            header('Content-Transfer-Encoding: binary');
313 4
            header('Content-Description: File Transfer');
314 4
            header('Content-Disposition: attachment; filename="'.rawurlencode($filename).'"');
315
        }
316
317 4
        $input_bom = $this->getInputBOM();
318 4
        $this->document->rewind();
319 4
        $this->document->fseek(strlen($input_bom));
320 4
        echo $this->output_bom;
321
322 4
        return strlen($this->output_bom) + $this->document->fpassthru();
323
    }
324
325
    /**
326
     * Sets the field delimiter
327
     *
328
     * @param string $delimiter
329
     *
330
     * @return static
331
     */
332 10
    public function setDelimiter(string $delimiter): self
333
    {
334 10
        if ($delimiter != $this->delimiter) {
335 8
            $this->delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
336 8
            $this->resetProperties();
337
        }
338
339 10
        return $this;
340
    }
341
342
    /**
343
     * Reset dynamic object properties to improve performance
344
     */
345 2
    protected function resetProperties()
346
    {
347 2
    }
348
349
    /**
350
     * Sets the field enclosure
351
     *
352
     * @param string $enclosure
353
     *
354
     * @return static
355
     */
356 2
    public function setEnclosure(string $enclosure): self
357
    {
358 2
        if ($enclosure != $this->enclosure) {
359 2
            $this->enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
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
        if ($escape != $this->escape) {
376 2
            $this->escape = $this->filterControl($escape, 'escape', __METHOD__);
377 2
            $this->resetProperties();
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