Completed
Push — master ( 5fa4d6...c4412d )
by ignace nyamagana
04:24 queued 02:33
created

src/AbstractCsv.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 210
    protected function __construct($document)
96
    {
97 210
        $this->document = $document;
98 210
    }
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 {@link AbstractCsv} from a SplFileObject
110
     *
111
     * @param SplFileObject $file
112
     *
113
     * @return static
114
     */
115 200
    public static function createFromFileObject(SplFileObject $file): self
116
    {
117 200
        $csv = new static($file);
118 200
        $controls = $file->getCsvControl();
119 200
        $csv->delimiter = $controls[0];
120 200
        $csv->enclosure = $controls[1];
121 200
        if (isset($controls[2])) {
122 200
            $csv->escape = $controls[2];
123
        }
124
125 200
        return $csv;
126
    }
127
128
    /**
129
     * Return a new {@link 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 {@link AbstractCsv} from a string
142
     *
143
     * @param string $str the string
144
     *
145
     * @return static
146
     */
147 16
    public static function createFromString(string $str): self
148
    {
149 16
        $stream = fopen('php://temp', 'r+');
150 16
        fwrite($stream, $str);
151
152 16
        return new static(new StreamIterator($stream));
153
    }
154
155
    /**
156
     * Return a new {@link 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 18
    public static function createFromPath(string $path, string $open_mode = 'r+'): self
164
    {
165 18
        if (!$stream = @fopen($path, $open_mode)) {
166 2
            throw new RuntimeException(error_get_last()['message']);
167
        }
168
169 16
        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
            $this->input_bom = bom_match($this->document->fgets());
223
        }
224
225 152
        return $this->input_bom;
226
    }
227
228
    /**
229
     * Tells whether the stream filter capabilities can be used
230
     *
231
     * @return bool
232
     */
233 4
    public function supportsStreamFilter(): bool
234
    {
235 4
        return $this->document instanceof StreamIterator;
236
    }
237
238
    /**
239
     * Tell whether the specify stream filter is attach to the current stream
240
     *
241
     * @return bool
242
     */
243 4
    public function hasStreamFilter(string $filtername): bool
244
    {
245 4
        return isset($this->stream_filters[$filtername]);
246
    }
247
248
    /**
249
     * Retrieves the CSV content
250
     *
251
     * @return string
252
     */
253 32
    public function __toString(): string
254
    {
255 32
        ob_start();
256 32
        $this->fpassthru();
257
258 32
        return ob_get_clean();
259
    }
260
261
    /**
262
     * Outputs all data on the CSV file
263
     *
264
     * @param string $filename CSV downloaded name if present adds extra headers
265
     *
266
     * @return int Returns the number of characters read from the handle
267
     *             and passed through to the output.
268
     */
269 4
    public function output(string $filename = null): int
270
    {
271 4
        if (null !== $filename) {
272 4
            $filename = filter_var($filename, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
273 4
            header('content-type: text/csv');
274 4
            header('content-transfer-encoding: binary');
275 4
            header('content-disposition: attachment; filename="'.rawurlencode($filename).'"');
276
        }
277
278 4
        return $this->fpassthru();
279
    }
280
281
    /**
282
     * Outputs all data from the CSV
283
     *
284
     * @return int Returns the number of characters read from the handle
285
     *             and passed through to the output.
286
     */
287 36
    protected function fpassthru(): int
288
    {
289 36
        $res = 0;
290 36
        $input_bom = $this->getInputBOM();
291 36
        $this->document->rewind();
292 36
        if ($input_bom != $this->output_bom) {
293 4
            $res = strlen($this->output_bom);
294 4
            $this->document->fseek(mb_strlen($input_bom));
295 4
            echo $this->output_bom;
296
        }
297
298 36
        return $res + $this->document->fpassthru();
299
    }
300
301
    /**
302
     * Sets the field delimiter
303
     *
304
     * @param string $delimiter
305
     *
306
     * @return static
307
     */
308 2
    public function setDelimiter(string $delimiter): self
309
    {
310 2
        $delimiter = $this->filterControl($delimiter, 'delimiter', __METHOD__);
311 2
        if ($delimiter != $this->delimiter) {
312 2
            $this->delimiter = $delimiter;
313 2
            $this->resetProperties();
314
        }
315
316 2
        return $this;
317
    }
318
319
    /**
320
     * Reset dynamic CSV document properties to improve performance
321
     */
322 4
    protected function resetProperties()
323
    {
324 4
    }
325
326
    /**
327
     * Sets the field enclosure
328
     *
329
     * @param string $enclosure
330
     *
331
     * @return static
332
     */
333 2
    public function setEnclosure(string $enclosure): self
334
    {
335 2
        $enclosure = $this->filterControl($enclosure, 'enclosure', __METHOD__);
336 2
        if ($enclosure != $this->enclosure) {
337 2
            $this->enclosure = $enclosure;
338 2
            $this->resetProperties();
339
        }
340
341 2
        return $this;
342
    }
343
344
    /**
345
     * Sets the field escape character
346
     *
347
     * @param string $escape
348
     *
349
     * @return static
350
     */
351 2
    public function setEscape(string $escape): self
352
    {
353 2
        $escape = $this->filterControl($escape, 'escape', __METHOD__);
354 2
        if ($escape != $this->escape) {
355 2
            $this->escape = $escape;
356 2
            $this->resetProperties();
357
        }
358
359
360 2
        return $this;
361
    }
362
363
    /**
364
     * Sets the BOM sequence to prepend the CSV on output
365
     *
366
     * @param string $str The BOM sequence
367
     *
368
     * @return static
369
     */
370 6
    public function setOutputBOM(string $str): self
371
    {
372 6
        $this->output_bom = $str;
373
374 6
        return $this;
375
    }
376
377
    /**
378
     * append a stream filter
379
     *
380
     * @param string $filtername a string or an object that implements the '__toString' method
381
     *
382
     * @throws LogicException If the stream filter API can not be used
383
     *
384
     * @return static
385
     */
386 12
    public function addStreamFilter(string $filtername): self
387
    {
388 12
        if (!$this->document instanceof StreamIterator) {
389 2
            throw new LogicException('The stream filter API can not be used');
390
        }
391
392 10
        $this->stream_filters[$filtername][] = $this->document->appendFilter($filtername, $this->stream_filter_mode);
393 10
        $this->resetProperties();
394 10
        $this->input_bom = null;
395
396 10
        return $this;
397
    }
398
399
    /**
400
     * The destructor
401
     */
402 210
    public function __destruct()
403
    {
404 210
        if ($this->document instanceof StreamIterator) {
405 26
            $mapper = function ($filter): bool {
406 10
                return $this->document->removeFilter($filter);
1 ignored issue
show
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...
407 21
            };
408
409 42
            array_walk_recursive($this->stream_filters, $mapper);
410
        }
411
412 210
        $this->document = null;
413 210
    }
414
}
415