Completed
Push — master ( 2436da...4019f1 )
by ignace nyamagana
06:21
created

AbstractCsv::output()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 14
nc 4
nop 1
dl 0
loc 20
ccs 14
cts 14
cp 1
crap 3
rs 9.4285
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 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 array
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 10
    protected function __construct($document)
96
    {
97 10
        $this->document = $document;
98 10
    }
99
100
    /**
101
     * @inheritdoc
102
     */
103 2
    public function __clone()
104
    {
105 2
        throw new LogicException('An object of class '.get_class($this).' cannot be cloned');
106
    }
107
108
    /**
109
     * Return a new instance from a SplFileObject
110
     *
111
     * @param SplFileObject $file
112
     *
113
     * @return static
114
     */
115 12
    public static function createFromFileObject(SplFileObject $file): self
116
    {
117 12
        $csv = new static($file);
118 12
        $controls = $file->getCsvControl();
119 12
        $csv->delimiter = $controls[0];
120 12
        $csv->enclosure = $controls[1];
121 12
        if (isset($controls[2])) {
122 12
            $csv->escape = $controls[2];
123
        }
124
125 12
        return $csv;
126
    }
127
128
    /**
129
     * Return a new instance from a PHP resource stream
130
     *
131
     * @param resource $stream
132
     *
133
     * @return static
134
     */
135 4
    public static function createFromStream($stream): self
136
    {
137 4
        return new static(new StreamIterator($stream));
138
    }
139
140
    /**
141
     * Return a new instance from a string
142
     *
143
     * @param string $content the CSV document as a string
144
     *
145
     * @return static
146
     */
147 4
    public static function createFromString(string $content): self
148
    {
149 4
        return new static(StreamIterator::createFromString($content));
150
    }
151
152
    /**
153
     * Return a new instance from a file path
154
     *
155
     * @param string        $path      file path
156
     * @param string        $open_mode the file open mode flag
157
     * @param resource|null $context   the resource context
158
     *
159
     * @return static
160
     */
161 4
    public static function createFromPath(string $path, string $open_mode = 'r+', $context = null): self
162
    {
163 4
        return new static(StreamIterator::createFromPath($path, $open_mode, $context));
164
    }
165
166
    /**
167
     * Returns the current field delimiter
168
     *
169
     * @return string
170
     */
171 2
    public function getDelimiter(): string
172
    {
173 2
        return $this->delimiter;
174
    }
175
176
    /**
177
     * Returns the current field enclosure
178
     *
179
     * @return string
180
     */
181 2
    public function getEnclosure(): string
182
    {
183 2
        return $this->enclosure;
184
    }
185
186
    /**
187
     * Returns the current field escape character
188
     *
189
     * @return string
190
     */
191 2
    public function getEscape(): string
192
    {
193 2
        return $this->escape;
194
    }
195
196
    /**
197
     * Returns the BOM sequence in use on Output methods
198
     *
199
     * @return string
200
     */
201 2
    public function getOutputBOM(): string
202
    {
203 2
        return $this->output_bom;
204
    }
205
206
    /**
207
     * Returns the BOM sequence of the given CSV
208
     *
209
     * @return string
210
     */
211 6
    public function getInputBOM(): string
212
    {
213 6
        if (null === $this->input_bom) {
214 6
            $this->document->setFlags(SplFileObject::READ_CSV);
215 6
            $this->document->rewind();
216 6
            $line = $this->document->fgets();
217 6
            $this->input_bom = false === $line ? '' : bom_match($line);
218
        }
219
220 6
        return $this->input_bom;
221
    }
222
223
    /**
224
     * Returns the stream filter mode
225
     *
226
     * @return int
227
     */
228 2
    public function getStreamFilterMode(): int
229
    {
230 2
        return $this->stream_filter_mode;
231
    }
232
233
    /**
234
     * Tells whether the stream filter capabilities can be used
235
     *
236
     * @return bool
237
     */
238 4
    public function supportsStreamFilter(): bool
239
    {
240 4
        return $this->document instanceof StreamIterator;
241
    }
242
243
    /**
244
     * Tell whether the specify stream filter is attach to the current stream
245
     *
246
     * @param  string $filtername
247
     * @return bool
248
     */
249 2
    public function hasStreamFilter(string $filtername): bool
250
    {
251 2
        return isset($this->stream_filters[$filtername]);
252
    }
253
254
    /**
255
     * Retrieves the CSV content
256
     *
257
     * @return string
258
     */
259 12
    public function __toString(): string
260
    {
261 12
        $raw = '';
262 12
        foreach ($this->chunk(8192) as $chunk) {
263 12
            $raw .= $chunk;
264
        }
265
266 12
        return $raw;
267
    }
268
269
    /**
270
     * Retuns the CSV document as a Generator of string chunk
271
     *
272
     * @param int $length number of bytes read
273
     *
274
     * @return Generator
275
     */
276 10
    public function chunk(int $length): Generator
277
    {
278 10
        $length = $this->filterMinRange($length, 1, __METHOD__.'() expects the length to be a positive integer %s given');
279 8
        $input_bom = $this->getInputBOM();
280 8
        $this->document->rewind();
281 8
        if ($input_bom != $this->output_bom) {
282 2
            $this->document->fseek(strlen($input_bom));
283 2
            $base_chunk = $this->output_bom.$this->document->fread($length);
284 2
            $this->document->fflush();
285 2
            foreach (str_split($base_chunk, $length) as $chunk) {
286 2
                yield $chunk;
287
            }
288
        }
289
290 8
        while ($this->document->valid()) {
291 8
            $chunk = $this->document->fread($length);
292 8
            $this->document->fflush();
293 8
            yield $chunk;
294
        }
295 8
    }
296
297
    /**
298
     * Outputs all data on the CSV file
299
     *
300
     * @param string $filename CSV downloaded name if present adds extra headers
301
     *
302
     * @return int Returns the number of characters read from the handle
303
     *             and passed through to the output.
304
     */
305 4
    public function output(string $filename = null): int
306
    {
307 4
        if (null !== $filename) {
308 4
            header('Content-Type: text/csv');
309 4
            header('Content-Transfer-Encoding: binary');
310 4
            header('Content-Description: File Transfer');
311 4
            header('Content-Disposition: attachment; filename="'.rawurlencode($filename).'"');
312
        }
313
314 4
        $res = 0;
315 4
        $input_bom = $this->getInputBOM();
316 4
        $this->document->rewind();
317 4
        if ($input_bom != $this->output_bom) {
318 2
            $res = strlen($this->output_bom);
319 2
            $this->document->fseek(mb_strlen($input_bom));
320 2
            echo $this->output_bom;
321
        }
322
323 4
        return $res + $this->document->fpassthru();
324
    }
325
326
    /**
327
     * Sets the field delimiter
328
     *
329
     * @param string $delimiter
330
     *
331
     * @return static
332
     */
333 2
    public function setDelimiter(string $delimiter): self
334
    {
335 2
        $char = $this->filterControl($delimiter, 'delimiter', __METHOD__);
336 2
        if ($char != $this->delimiter) {
337 2
            $this->delimiter = $char;
338 2
            $this->resetProperties();
339
        }
340
341 2
        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
        $char = $this->filterControl($enclosure, 'enclosure', __METHOD__);
361 2
        if ($char != $this->enclosure) {
362 2
            $this->enclosure = $char;
363 2
            $this->resetProperties();
364
        }
365
366 2
        return $this;
367
    }
368
369
    /**
370
     * Sets the field escape character
371
     *
372
     * @param string $escape
373
     *
374
     * @return static
375
     */
376 2
    public function setEscape(string $escape): self
377
    {
378 2
        $char = $this->filterControl($escape, 'escape', __METHOD__);
379 2
        if ($char != $this->escape) {
380 2
            $this->escape = $char;
381 2
            $this->resetProperties();
382
        }
383
384
385 2
        return $this;
386
    }
387
388
    /**
389
     * Sets the BOM sequence to prepend the CSV on output
390
     *
391
     * @param string $str The BOM sequence
392
     *
393
     * @return static
394
     */
395 6
    public function setOutputBOM(string $str): self
396
    {
397 6
        $this->output_bom = $str;
398
399 6
        return $this;
400
    }
401
402
    /**
403
     * append a stream filter
404
     *
405
     * @param string $filtername a string or an object that implements the '__toString' method
406
     * @param mixed  $params     additional parameters for the filter
407
     *
408
     * @throws LogicException If the stream filter API can not be used
409
     *
410
     * @return static
411
     */
412 10
    public function addStreamFilter(string $filtername, $params = null): self
413
    {
414 10
        if (!$this->document instanceof StreamIterator) {
415 2
            throw new LogicException('The stream filter API can not be used');
416
        }
417
418 8
        $this->stream_filters[$filtername][] = $this->document->appendFilter($filtername, $this->stream_filter_mode, $params);
419 6
        $this->resetProperties();
420 6
        $this->input_bom = null;
421
422 6
        return $this;
423
    }
424
425
    /**
426
     * The destructor
427
     */
428 12
    public function __destruct()
429
    {
430 12
        if ($this->document instanceof StreamIterator) {
431 6
            $walker = function ($filter): bool {
432 4
                return $this->document->removeFilter($filter);
433 4
            };
434
435 8
            array_walk_recursive($this->stream_filters, $walker);
436
        }
437
438 12
        $this->document = null;
439 12
    }
440
}
441