Completed
Push — master ( 4bb3d9...4ca182 )
by ignace nyamagana
05:29
created

AbstractCsv::removeStreamFilter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 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 League\Csv\Exception\LogicException;
18
use League\Csv\Exception\RuntimeException;
19
use SplFileObject;
20
21
/**
22
 *  An abstract class to enable basic CSV manipulation
23
 *
24
 * @package League.csv
25
 * @since  4.0.0
26
 * @author  Ignace Nyamagana Butera <[email protected]>
27
 *
28
 */
29
abstract class AbstractCsv
30
{
31
    use ValidatorTrait;
32
33
    /**
34
     *  UTF-8 BOM sequence
35
     */
36
    const BOM_UTF8 = "\xEF\xBB\xBF";
37
38
    /**
39
     * UTF-16 BE BOM sequence
40
     */
41
    const BOM_UTF16_BE = "\xFE\xFF";
42
43
    /**
44
     * UTF-16 LE BOM sequence
45
     */
46
    const BOM_UTF16_LE = "\xFF\xFE";
47
48
    /**
49
     * UTF-32 BE BOM sequence
50
     */
51
    const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
52
53
    /**
54
     * UTF-32 LE BOM sequence
55
     */
56
    const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
57
58
    /**
59
     * The CSV document
60
     *
61
     * @var StreamIterator|SplFileObject
62
     */
63
    protected $document;
64
65
    /**
66
     * the field delimiter (one character only)
67
     *
68
     * @var string
69
     */
70
    protected $delimiter = ',';
71
72
    /**
73
     * the field enclosure character (one character only)
74
     *
75
     * @var string
76
     */
77
    protected $enclosure = '"';
78
79
    /**
80
     * the field escape character (one character only)
81
     *
82
     * @var string
83
     */
84
    protected $escape = '\\';
85
86
    /**
87
     * The Output file BOM character
88
     * @var string
89
     */
90
    protected $output_bom = '';
91
92
    /**
93
     * collection of stream filters
94
     *
95
     * @var array
96
     */
97
    protected $stream_filters = [];
98
99
    /**
100
     * The stream filter mode (read or write)
101
     *
102
     * @var int
103
     */
104
    protected $stream_filter_mode;
105
106
    /**
107
     * The CSV document BOM sequence
108
     *
109
     * @var string|null
110
     */
111
    protected $input_bom = null;
112
113
    /**
114
     * New instance
115
     *
116
     * @param SplFileObject|StreamIterator $document The CSV Object instance
117
     */
118 210
    protected function __construct($document)
119
    {
120 210
        $this->document = $document;
121 210
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126 2
    public function __clone()
127
    {
128 2
        throw new LogicException('An object of class '.get_class($this).' cannot be cloned');
129
    }
130
131
    /**
132
     * Return a new {@link AbstractCsv} from a SplFileObject
133
     *
134
     * @param SplFileObject $file
135
     *
136
     * @return static
137
     */
138 200
    public static function createFromFileObject(SplFileObject $file): self
139
    {
140 200
        $csv = new static($file);
141 200
        $controls = $file->getCsvControl();
142 200
        $csv->delimiter = $controls[0];
143 200
        $csv->enclosure = $controls[1];
144 200
        if (isset($controls[2])) {
145 200
            $csv->escape = $controls[2];
146
        }
147
148 200
        return $csv;
149
    }
150
151
    /**
152
     * Return a new {@link AbstractCsv} from a PHP resource stream
153
     *
154
     * @param resource $stream
155
     *
156
     * @return static
157
     */
158 12
    public static function createFromStream($stream): self
159
    {
160 12
        return new static(new StreamIterator($stream));
161
    }
162
163
    /**
164
     * Return a new {@link AbstractCsv} from a string
165
     *
166
     * @param string $str the string
167
     *
168
     * @return static
169
     */
170 16
    public static function createFromString(string $str): self
171
    {
172 16
        $stream = fopen('php://temp', 'r+');
173 16
        fwrite($stream, $str);
174
175 16
        return new static(new StreamIterator($stream));
176
    }
177
178
    /**
179
     * Return a new {@link AbstractCsv} from a file path
180
     *
181
     * @param string $path      file path
182
     * @param string $open_mode the file open mode flag
183
     *
184
     * @return static
185
     */
186 18
    public static function createFromPath(string $path, string $open_mode = 'r+'): self
187
    {
188 18
        if (!$stream = @fopen($path, $open_mode)) {
189 2
            throw new RuntimeException(error_get_last()['message']);
190
        }
191
192 16
        return new static(new StreamIterator($stream));
193
    }
194
195
    /**
196
     * Returns the current field delimiter
197
     *
198
     * @return string
199
     */
200 4
    public function getDelimiter(): string
201
    {
202 4
        return $this->delimiter;
203
    }
204
205
    /**
206
     * Returns the current field enclosure
207
     *
208
     * @return string
209
     */
210 4
    public function getEnclosure(): string
211
    {
212 4
        return $this->enclosure;
213
    }
214
215
    /**
216
     * Returns the current field escape character
217
     *
218
     * @return string
219
     */
220 4
    public function getEscape(): string
221
    {
222 4
        return $this->escape;
223
    }
224
225
    /**
226
     * Returns the BOM sequence in use on Output methods
227
     *
228
     * @return string
229
     */
230 2
    public function getOutputBOM(): string
231
    {
232 2
        return $this->output_bom;
233
    }
234
235
    /**
236
     * Returns the BOM sequence of the given CSV
237
     *
238
     * @return string
239
     */
240 150
    public function getInputBOM(): string
241
    {
242 150
        if (null !== $this->input_bom) {
243 20
            return $this->input_bom;
244
        }
245
246
        $bom = [
247 150
            self::BOM_UTF32_BE, self::BOM_UTF32_LE,
248 150
            self::BOM_UTF16_BE, self::BOM_UTF16_LE, self::BOM_UTF8,
249
        ];
250
251 150
        $this->document->setFlags(SplFileObject::READ_CSV);
252 150
        $this->document->rewind();
253 150
        $line = $this->document->fgets();
254 150
        $res = array_filter($bom, function ($sequence) use ($line) {
255 150
            return strpos($line, $sequence) === 0;
256 150
        });
257
258 150
        $this->input_bom = (string) array_shift($res);
259
260 150
        return $this->input_bom;
261
    }
262
263
    /**
264
     * Tells whether the stream filter capabilities can be used
265
     *
266
     * @return bool
267
     */
268 4
    public function supportsStreamFilter(): bool
269
    {
270 4
        return $this->document instanceof StreamIterator;
271
    }
272
273
    /**
274
     * Tell whether the specify stream filter is attach to the current stream
275
     *
276
     * @return bool
277
     */
278 4
    public function hasStreamFilter(string $filtername): bool
279
    {
280 4
        return isset($this->stream_filters[$filtername]);
281
    }
282
283
    /**
284
     * Retrieves the CSV content
285
     *
286
     * @return string
287
     */
288 32
    public function __toString(): string
289
    {
290 32
        ob_start();
291 32
        $this->fpassthru();
292
293 32
        return ob_get_clean();
294
    }
295
296
    /**
297
     * Outputs all data on the CSV file
298
     *
299
     * @param string $filename CSV downloaded name if present adds extra headers
300
     *
301
     * @return int Returns the number of characters read from the handle
302
     *             and passed through to the output.
303
     */
304 4
    public function output(string $filename = null): int
305
    {
306 4
        if (null !== $filename) {
307 4
            $filename = filter_var($filename, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
308 4
            header('content-type: text/csv');
309 4
            header('content-transfer-encoding: binary');
310 4
            header('content-disposition: attachment; filename="'.rawurlencode($filename).'"');
311
        }
312
313 4
        return $this->fpassthru();
314
    }
315
316
    /**
317
     * Outputs all data from the CSV
318
     *
319
     * @return int Returns the number of characters read from the handle
320
     *             and passed through to the output.
321
     */
322 36
    protected function fpassthru(): int
323
    {
324 36
        $bom = '';
325 36
        $input_bom = $this->getInputBOM();
326 36
        if ($this->output_bom && $input_bom != $this->output_bom) {
327 4
            $bom = $this->output_bom;
328
        }
329
330 36
        $this->document->rewind();
331 36
        if ('' !== $bom) {
332 4
            $this->document->fseek(mb_strlen($input_bom));
333
        }
334 36
        echo $bom;
335 36
        $res = $this->document->fpassthru();
336
337 36
        return $res + strlen($bom);
338
    }
339
340
    /**
341
     * Sets the field delimiter
342
     *
343
     * @param string $delimiter
344
     *
345
     * @return static
346
     */
347 2
    public function setDelimiter(string $delimiter): self
348
    {
349 2
        $this->delimiter = $this->filterControl($delimiter, 'delimiter');
350 2
        $this->resetDynamicProperties();
351
352 2
        return $this;
353
    }
354
355
    /**
356
     * Reset dynamic CSV document properties to improve performance
357
     */
358 4
    protected function resetDynamicProperties()
359
    {
360 4
    }
361
362
    /**
363
     * Sets the field enclosure
364
     *
365
     * @param string $enclosure
366
     *
367
     * @return static
368
     */
369 2
    public function setEnclosure(string $enclosure): self
370
    {
371 2
        $this->enclosure = $this->filterControl($enclosure, 'enclosure');
372 2
        $this->resetDynamicProperties();
373
374 2
        return $this;
375
    }
376
377
    /**
378
     * Sets the field escape character
379
     *
380
     * @param string $escape
381
     *
382
     * @return static
383
     */
384 2
    public function setEscape(string $escape): self
385
    {
386 2
        $this->escape = $this->filterControl($escape, 'escape');
387 2
        $this->resetDynamicProperties();
388
389 2
        return $this;
390
    }
391
392
    /**
393
     * Sets the BOM sequence to prepend the CSV on output
394
     *
395
     * @param string $str The BOM sequence
396
     *
397
     * @return static
398
     */
399 6
    public function setOutputBOM(string $str): self
400
    {
401 6
        $this->output_bom = $str;
402
403 6
        return $this;
404
    }
405
406
    /**
407
     * append a stream filter
408
     *
409
     * @param string $filtername a string or an object that implements the '__toString' method
410
     *
411
     * @throws LogicException If the stream filter API can not be used
412
     *
413
     * @return static
414
     */
415 12
    public function addStreamFilter(string $filtername): self
416
    {
417 12
        if (!$this->document instanceof StreamIterator) {
418 2
            throw new LogicException('The stream filter API can not be used');
419
        }
420
421 10
        $this->stream_filters[$filtername][] = $this->document->appendFilter($filtername, $this->stream_filter_mode);
422 10
        $this->resetDynamicProperties();
423 10
        $this->input_bom = null;
424
425 10
        return $this;
426
    }
427
428
    /**
429
     * The destructor
430
     */
431 210
    public function __destruct()
432
    {
433 210
        if ($this->document instanceof StreamIterator) {
434 42
            foreach ($this->stream_filters as $filters) {
435 10
                foreach ($filters as $filter) {
436 10
                    $this->document->removeFilter($filter);
437
                }
438
            }
439
        }
440
441 210
        $this->document = null;
442 210
    }
443
}
444