Completed
Pull Request — master (#210)
by ignace nyamagana
06:26
created

AbstractCsv::getInputBOM()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

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

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
457
        }
458
459 10
        unset($this->stream_filters[$filtername]);
460 10
    }
461
}
462