Completed
Pull Request — master (#234)
by ignace nyamagana
02:39
created

AbstractCsv::getStreamFilterMode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
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->resetProperties();
98 10
        $this->document = $document;
99 10
    }
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 instance from a SplFileObject
111
     *
112
     * @param SplFileObject $file
113
     *
114
     * @return static
115
     */
116 12
    public static function createFromFileObject(SplFileObject $file): self
117
    {
118 12
        $csv = new static($file);
119 12
        $controls = $file->getCsvControl();
120 12
        $csv->delimiter = $controls[0];
121 12
        $csv->enclosure = $controls[1];
122 12
        if (isset($controls[2])) {
123 12
            $csv->escape = $controls[2];
124
        }
125
126 12
        return $csv;
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 StreamIterator($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(StreamIterator::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(StreamIterator::createFromPath($path, $open_mode, $context));
165
    }
166
167
    /**
168
     * Returns the class filter mode
169
     *
170
     * @return int
171
     */
172 2
    public function getStreamFilterMode(): int
173
    {
174 2
        return $this->stream_filter_mode;
175
    }
176
177
    /**
178
     * Returns the current field delimiter
179
     *
180
     * @return string
181
     */
182 2
    public function getDelimiter(): string
183
    {
184 2
        return $this->delimiter;
185
    }
186
187
    /**
188
     * Returns the current field enclosure
189
     *
190
     * @return string
191
     */
192 2
    public function getEnclosure(): string
193
    {
194 2
        return $this->enclosure;
195
    }
196
197
    /**
198
     * Returns the current field escape character
199
     *
200
     * @return string
201
     */
202 2
    public function getEscape(): string
203
    {
204 2
        return $this->escape;
205
    }
206
207
    /**
208
     * Returns the BOM sequence in use on Output methods
209
     *
210
     * @return string
211
     */
212 2
    public function getOutputBOM(): string
213
    {
214 2
        return $this->output_bom;
215
    }
216
217
    /**
218
     * Returns the BOM sequence of the given CSV
219
     *
220
     * @return string
221
     */
222 6
    public function getInputBOM(): string
223
    {
224 6
        if (null === $this->input_bom) {
225 6
            $this->document->setFlags(SplFileObject::READ_CSV);
226 6
            $this->document->rewind();
227 6
            $line = $this->document->fgets();
228 6
            $this->input_bom = false === $line ? '' : bom_match($line);
229
        }
230
231 6
        return $this->input_bom;
232
    }
233
234
    /**
235
     * Tells whether the stream filter capabilities can be used
236
     *
237
     * @return bool
238
     */
239 4
    public function supportsStreamFilter(): bool
240
    {
241 4
        return $this->document instanceof StreamIterator;
242
    }
243
244
    /**
245
     * Tell whether the specify stream filter is attach to the current stream
246
     *
247
     * @param  string $filtername
248
     * @return bool
249
     */
250 2
    public function hasStreamFilter(string $filtername): bool
251
    {
252 2
        return isset($this->stream_filters[$filtername]);
253
    }
254
255
    /**
256
     * Retrieves the CSV content
257
     *
258
     * @return string
259
     */
260 12
    public function __toString(): string
261
    {
262 12
        $raw = '';
263 12
        foreach ($this->chunk(8192) as $chunk) {
264 12
            $raw .= $chunk;
265
        }
266
267 12
        return $raw;
268
    }
269
270
    /**
271
     * Retuns the CSV document as a Generator of string chunk
272
     *
273
     * @param int $length number of bytes read
274
     *
275
     * @return Generator
276
     */
277 10
    public function chunk(int $length): Generator
278
    {
279 10
        $length = $this->filterMinRange($length, 1, __METHOD__.'() expects the length to be a positive integer %s given');
280 8
        $input_bom = $this->getInputBOM();
281 8
        $this->document->rewind();
282 8
        if ($input_bom != $this->output_bom) {
283 2
            $this->document->fseek(strlen($input_bom));
284 2
            $base_chunk = $this->output_bom.$this->document->fread($length);
285 2
            $this->document->fflush();
286 2
            foreach (str_split($base_chunk, $length) as $chunk) {
287 2
                yield $chunk;
288
            }
289
        }
290
291 8
        while ($this->document->valid()) {
292 8
            $chunk = $this->document->fread($length);
293 8
            $this->document->fflush();
294 8
            yield $chunk;
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
        $res = 0;
316 4
        $input_bom = $this->getInputBOM();
317 4
        $this->document->rewind();
318 4
        if ($input_bom != $this->output_bom) {
319 2
            $res = strlen($this->output_bom);
320 2
            $this->document->fseek(mb_strlen($input_bom));
321 2
            echo $this->output_bom;
322
        }
323
324 4
        return $res + $this->document->fpassthru();
325
    }
326
327
    /**
328
     * Sets the field delimiter
329
     *
330
     * @param string $delimiter
331
     *
332
     * @return static
333
     */
334 2
    public function setDelimiter(string $delimiter): self
335
    {
336 2
        $char = $this->filterControl($delimiter, 'delimiter', __METHOD__);
337 2
        if ($char != $this->delimiter) {
338 2
            $this->delimiter = $char;
339 2
            $this->resetProperties();
340
        }
341
342 2
        return $this;
343
    }
344
345
    /**
346
     * Reset dynamic object properties to improve performance
347
     */
348 10
    protected function resetProperties()
349
    {
350 10
    }
351
352
    /**
353
     * Sets the field enclosure
354
     *
355
     * @param string $enclosure
356
     *
357
     * @return static
358
     */
359 2
    public function setEnclosure(string $enclosure): self
360
    {
361 2
        $char = $this->filterControl($enclosure, 'enclosure', __METHOD__);
362 2
        if ($char != $this->enclosure) {
363 2
            $this->enclosure = $char;
364 2
            $this->resetProperties();
365
        }
366
367 2
        return $this;
368
    }
369
370
    /**
371
     * Sets the field escape character
372
     *
373
     * @param string $escape
374
     *
375
     * @return static
376
     */
377 2
    public function setEscape(string $escape): self
378
    {
379 2
        $char = $this->filterControl($escape, 'escape', __METHOD__);
380 2
        if ($char != $this->escape) {
381 2
            $this->escape = $char;
382 2
            $this->resetProperties();
383
        }
384
385
386 2
        return $this;
387
    }
388
389
    /**
390
     * Sets the BOM sequence to prepend the CSV on output
391
     *
392
     * @param string $str The BOM sequence
393
     *
394
     * @return static
395
     */
396 6
    public function setOutputBOM(string $str): self
397
    {
398 6
        $this->output_bom = $str;
399
400 6
        return $this;
401
    }
402
403
    /**
404
     * append a stream filter
405
     *
406
     * @param string $filtername a string or an object that implements the '__toString' method
407
     * @param mixed  $params     additional parameters for the filter
408
     *
409
     * @throws LogicException If the stream filter API can not be used
410
     *
411
     * @return static
412
     */
413 10
    public function addStreamFilter(string $filtername, $params = null): self
414
    {
415 10
        if (!$this->document instanceof StreamIterator) {
416 2
            throw new LogicException('The stream filter API can not be used');
417
        }
418
419 8
        $this->stream_filters[$filtername][] = $this->document->appendFilter($filtername, $this->stream_filter_mode, $params);
420 6
        $this->resetProperties();
421 6
        $this->input_bom = null;
422
423 6
        return $this;
424
    }
425
426
    /**
427
     * The destructor
428
     */
429 14
    public function __destruct()
430
    {
431 14
        if ($this->document instanceof StreamIterator) {
432 6
            $walker = function ($filter) {
433 4
                $this->document->removeFilter($filter);
434 6
            };
435
436 8
            array_walk_recursive($this->stream_filters, $walker);
437
        }
438
439 14
        $this->document = null;
440 14
    }
441
}
442