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