Completed
Pull Request — master (#210)
by ignace nyamagana
12:18
created

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