Completed
Pull Request — master (#210)
by ignace nyamagana
02:58
created

AbstractCsv::setOutputBOM()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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