Completed
Push — master ( babf40...27ada1 )
by ignace nyamagana
02:52
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\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
     * BOM sequences list
60
     */
61
    const BOM_SEQUENCES = [
62
        self::BOM_UTF32_BE,
63
        self::BOM_UTF32_LE,
64
        self::BOM_UTF16_BE,
65
        self::BOM_UTF16_LE,
66
        self::BOM_UTF8,
67
    ];
68
69
    /**
70
     * The CSV document
71
     *
72
     * @var StreamIterator|SplFileObject
73
     */
74
    protected $document;
75
76
    /**
77
     * the field delimiter (one character only)
78
     *
79
     * @var string
80
     */
81
    protected $delimiter = ',';
82
83
    /**
84
     * the field enclosure character (one character only)
85
     *
86
     * @var string
87
     */
88
    protected $enclosure = '"';
89
90
    /**
91
     * the field escape character (one character only)
92
     *
93
     * @var string
94
     */
95
    protected $escape = '\\';
96
97
    /**
98
     * The CSV document BOM sequence
99
     *
100
     * @var string|null
101
     */
102
    protected $input_bom = null;
103
104
    /**
105
     * The Output file BOM character
106
     *
107
     * @var string
108
     */
109
    protected $output_bom = '';
110
111
    /**
112
     * collection of stream filters
113
     *
114
     * @var array
115
     */
116
    protected $stream_filters = [];
117
118
    /**
119
     * The stream filter mode (read or write)
120
     *
121
     * @var int
122
     */
123
    protected $stream_filter_mode;
124
125
    /**
126
     * New instance
127
     *
128
     * @param SplFileObject|StreamIterator $document The CSV Object instance
129
     */
130 208
    protected function __construct($document)
131
    {
132 208
        $this->document = $document;
133 208
    }
134
135
    /**
136
     * @inheritdoc
137
     */
138 2
    public function __clone()
139
    {
140 2
        throw new LogicException('An object of class '.get_class($this).' cannot be cloned');
141
    }
142
143
    /**
144
     * Return a new {@link AbstractCsv} from a SplFileObject
145
     *
146
     * @param SplFileObject $file
147
     *
148
     * @return static
149
     */
150 198
    public static function createFromFileObject(SplFileObject $file): self
151
    {
152 198
        $csv = new static($file);
153 198
        $controls = $file->getCsvControl();
154 198
        $csv->delimiter = $controls[0];
155 198
        $csv->enclosure = $controls[1];
156 198
        if (isset($controls[2])) {
157 198
            $csv->escape = $controls[2];
158
        }
159
160 198
        return $csv;
161
    }
162
163
    /**
164
     * Return a new {@link AbstractCsv} from a PHP resource stream
165
     *
166
     * @param resource $stream
167
     *
168
     * @return static
169
     */
170 12
    public static function createFromStream($stream): self
171
    {
172 12
        return new static(new StreamIterator($stream));
173
    }
174
175
    /**
176
     * Return a new {@link AbstractCsv} from a string
177
     *
178
     * @param string $str the string
179
     *
180
     * @return static
181
     */
182 16
    public static function createFromString(string $str): self
183
    {
184 16
        $stream = fopen('php://temp', 'r+');
185 16
        fwrite($stream, $str);
186
187 16
        return new static(new StreamIterator($stream));
188
    }
189
190
    /**
191
     * Return a new {@link AbstractCsv} from a file path
192
     *
193
     * @param string $path      file path
194
     * @param string $open_mode the file open mode flag
195
     *
196
     * @return static
197
     */
198 18
    public static function createFromPath(string $path, string $open_mode = 'r+'): self
199
    {
200 18
        if (!$stream = @fopen($path, $open_mode)) {
201 2
            throw new RuntimeException(error_get_last()['message']);
202
        }
203
204 16
        return new static(new StreamIterator($stream));
205
    }
206
207
    /**
208
     * Returns the current field delimiter
209
     *
210
     * @return string
211
     */
212 4
    public function getDelimiter(): string
213
    {
214 4
        return $this->delimiter;
215
    }
216
217
    /**
218
     * Returns the current field enclosure
219
     *
220
     * @return string
221
     */
222 4
    public function getEnclosure(): string
223
    {
224 4
        return $this->enclosure;
225
    }
226
227
    /**
228
     * Returns the current field escape character
229
     *
230
     * @return string
231
     */
232 4
    public function getEscape(): string
233
    {
234 4
        return $this->escape;
235
    }
236
237
    /**
238
     * Returns the BOM sequence in use on Output methods
239
     *
240
     * @return string
241
     */
242 2
    public function getOutputBOM(): string
243
    {
244 2
        return $this->output_bom;
245
    }
246
247
    /**
248
     * Returns the BOM sequence of the given CSV
249
     *
250
     * @return string
251
     */
252 150
    public function getInputBOM(): string
253
    {
254 150
        if (null !== $this->input_bom) {
255 20
            return $this->input_bom;
256
        }
257
258 150
        $this->document->setFlags(SplFileObject::READ_CSV);
259 150
        $this->document->rewind();
260 150
        $line = $this->document->fgets();
261
        $filter = function ($sequence) use ($line): bool {
262 150
            return strpos($line, $sequence) === 0;
263 150
        };
264
265 150
        $res = array_filter(self::BOM_SEQUENCES, $filter);
266 150
        $this->input_bom = (string) array_shift($res);
267
268 150
        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 supportsStreamFilter(): 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
        $res = 0;
333 36
        $input_bom = $this->getInputBOM();
334 36
        $this->document->rewind();
335 36
        if ($input_bom != $this->output_bom) {
336 4
            $res = strlen($this->output_bom);
337 4
            $this->document->fseek(mb_strlen($input_bom));
338 4
            echo $this->output_bom;
339
        }
340
341 36
        return $res + $this->document->fpassthru();
342
    }
343
344
    /**
345
     * Sets the field delimiter
346
     *
347
     * @param string $delimiter
348
     *
349
     * @return static
350
     */
351 2
    public function setDelimiter(string $delimiter): self
352
    {
353 2
        $delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
354 2
        if ($delimiter != $this->delimiter) {
355 2
            $this->delimiter = $delimiter;
356 2
            $this->resetProperties();
357
        }
358
359 2
        return $this;
360
    }
361
362
    /**
363
     * Reset dynamic CSV document properties to improve performance
364
     */
365 4
    protected function resetProperties()
366
    {
367 4
    }
368
369
    /**
370
     * Sets the field enclosure
371
     *
372
     * @param string $enclosure
373
     *
374
     * @return static
375
     */
376 2
    public function setEnclosure(string $enclosure): self
377
    {
378 2
        $enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
379 2
        if ($enclosure != $this->enclosure) {
380 2
            $this->enclosure = $enclosure;
381 2
            $this->resetProperties();
382
        }
383
384 2
        return $this;
385
    }
386
387
    /**
388
     * Sets the field escape character
389
     *
390
     * @param string $escape
391
     *
392
     * @return static
393
     */
394 2
    public function setEscape(string $escape): self
395
    {
396 2
        $escape = $this->filterControl($escape, 'escape', __METHOD__);
397 2
        if ($escape != $this->escape) {
398 2
            $this->escape = $escape;
399 2
            $this->resetProperties();
400
        }
401
402
403 2
        return $this;
404
    }
405
406
    /**
407
     * Sets the BOM sequence to prepend the CSV on output
408
     *
409
     * @param string $str The BOM sequence
410
     *
411
     * @return static
412
     */
413 6
    public function setOutputBOM(string $str): self
414
    {
415 6
        $this->output_bom = $str;
416
417 6
        return $this;
418
    }
419
420
    /**
421
     * append a stream filter
422
     *
423
     * @param string $filtername a string or an object that implements the '__toString' method
424
     *
425
     * @throws LogicException If the stream filter API can not be used
426
     *
427
     * @return static
428
     */
429 12
    public function addStreamFilter(string $filtername): self
430
    {
431 12
        if (!$this->document instanceof StreamIterator) {
432 2
            throw new LogicException('The stream filter API can not be used');
433
        }
434
435 10
        $this->stream_filters[$filtername][] = $this->document->appendFilter($filtername, $this->stream_filter_mode);
436 10
        $this->resetProperties();
437 10
        $this->input_bom = null;
438
439 10
        return $this;
440
    }
441
442
    /**
443
     * The destructor
444
     */
445 208
    public function __destruct()
446
    {
447 208
        if ($this->document instanceof StreamIterator) {
448 26
            $mapper = function ($filter): bool {
449 10
                return $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...
450 21
            };
451
452 42
            array_walk_recursive($this->stream_filters, $mapper);
453
        }
454
455 208
        $this->document = null;
456 208
    }
457
}
458