AbstractCsv   A
last analyzed

↳ Parent: Project

Coupling/Cohesion

Components 1
Dependencies 4

Complexity

Total Complexity 34

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 386
ccs 100
cts 100
cp 1
rs 9.2
c 0
b 0
f 0
wmc 34
lcom 1
cbo 4

23 Methods

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