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

AbstractCsv::addStreamFilter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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