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

AbstractCsv::getHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 1
cts 1
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
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 InvalidArgumentException;
18
use League\Csv\Config\ControlsTrait;
19
use League\Csv\Config\StreamTrait;
20
use SplFileObject;
21
22
/**
23
 *  An abstract class to enable basic CSV manipulation
24
 *
25
 * @package League.csv
26
 * @since  4.0.0
27
 *
28
 */
29
abstract class AbstractCsv
30
{
31
    use ControlsTrait;
32
    use StreamTrait;
33
34
    /**
35
     *  UTF-8 BOM sequence
36
     */
37
    const BOM_UTF8 = "\xEF\xBB\xBF";
38
39
    /**
40
     * UTF-16 BE BOM sequence
41
     */
42
    const BOM_UTF16_BE = "\xFE\xFF";
43
44
    /**
45
     * UTF-16 LE BOM sequence
46
     */
47
    const BOM_UTF16_LE = "\xFF\xFE";
48
49
    /**
50
     * UTF-32 BE BOM sequence
51
     */
52
    const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
53
54
    /**
55
     * UTF-32 LE BOM sequence
56
     */
57
    const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
58
59
    /**
60
     * Creates a new instance
61
     *
62
     * The file path can be:
63
     *
64
     * - a SplFileObject
65
     * - a StreamIterator
66
     *
67
     * @param SplFileObject|StreamIterator $document The CSV Object instance
68
     */
69
    protected function __construct($document)
70
    {
71
        $this->document = $document;
72
    }
73
74
    /**
75
     * The destructor
76
     */
77
    public function __destruct()
78
    {
79
        if ($this->isActiveStreamFilter()) {
80
            $this->clearStreamFilter();
81
        }
82
83
        $this->document = null;
84
    }
85
86
    /**
87
     * Set the Inner Iterator
88
     *
89
     * @return StreamIterator|SplFileObject
90
     */
91
    public function getDocument()
92
    {
93
        return $this->document;
94
    }
95
96
    /**
97
     * Return a new {@link AbstractCsv} from a SplFileObject
98
     *
99
     * @param SplFileObject $file
100
     *
101
     * @return static
102
     */
103
    public static function createFromFileObject(SplFileObject $file): self
104
    {
105 357
        $csv = new static($file);
106
        $controls = $file->getCsvControl();
107 357
        $csv->setDelimiter($controls[0]);
108 357
        $csv->setEnclosure($controls[1]);
109 357
        if (isset($controls[2])) {
110 357
            $csv->setEscape($controls[2]);
111
        }
112
113
        return $csv;
114
    }
115 240
116
    /**
117 240
     * Return a new {@link AbstractCsv} from a PHP resource stream
118 240
     *
119
     * @param resource $stream
120
     *
121
     * @return static
122
     */
123
    public static function createFromStream($stream): self
124
    {
125
        return new static(new StreamIterator($stream));
126
    }
127 312
128
    /**
129 312
     * Return a new {@link AbstractCsv} from a string
130
     *
131
     * The string must be an object that implements the `__toString` method,
132
     * or a string
133
     *
134
     * @param string $str the string
135
     *
136
     * @return static
137
     */
138
    public static function createFromString(string $str): self
139
    {
140
        $stream = fopen('php://temp', 'r+');
141
        fwrite($stream, $str);
142 27
143
        return new static(new StreamIterator($stream));
144 27
    }
145 27
146
    /**
147 27
     * Return a new {@link AbstractCsv} from a file path
148
     *
149
     * @param string $path      file path
150
     * @param string $open_mode the file open mode flag
151
     *
152
     * @return static
153
     */
154
    public static function createFromPath(string $path, string $open_mode = 'r+'): self
155
    {
156
        $stream = fopen($path, $open_mode);
157
158
        return new static(new StreamIterator($stream));
159 90
    }
160
161 90
    /**
162 87
     * Return a new {@link AbstractCsv} instance from another {@link AbstractCsv} object
163
     *
164 3
     * @param string $class the class to be instantiated
165
     *
166
     * @return static
167
     */
168
    protected function newInstance(string $class): self
169
    {
170
        $csv = new $class($this->document);
171
        $csv->delimiter = $this->delimiter;
172
        $csv->enclosure = $this->enclosure;
173
        $csv->escape = $this->escape;
174
        $csv->input_bom = $this->input_bom;
175
        $csv->output_bom = $this->output_bom;
176
        $csv->newline = $this->newline;
177 54
        $csv->clearStreamFilter();
178
179 54
        return $csv;
180 3
    }
181
182
    /**
183 51
     * Return a new {@link Writer} instance from a {@link AbstractCsv} object
184 3
     *
185 2
     * @return Writer
186
     */
187 51
    public function newWriter(): self
188
    {
189
        return $this->newInstance(Writer::class);
190
    }
191
192
    /**
193
     * Return a new {@link Reader} instance from a {@link AbstractCsv} object
194
     *
195
     * @return Reader
196
     */
197
    public function newReader(): self
198 6
    {
199
        return $this->newInstance(Reader::class);
200 6
    }
201 6
202 6
    /**
203 6
     * Returns the header
204 6
     *
205 6
     * If no CSV record is used this method MUST return an empty array
206 6
     *
207 6
     * @return string[]
208
     */
209 6
    public function getHeader(): array
210
    {
211
        if (null === $this->header_offset) {
212
            return [];
213
        }
214
215
        return $this->filterHeader($this->getRow($this->header_offset));
216
    }
217
218
    /**
219 3
     * Returns a single row from the CSV without filtering
220
     *
221 3
     * @param int $offset
222
     *
223
     * @throws InvalidArgumentException If the $offset is not valid or the row does not exist
224
     *
225
     * @return array
226
     */
227
    protected function getRow(int $offset): array
228
    {
229
        $csv = $this->getDocument();
230
        $csv->setFlags(SplFileObject::READ_CSV);
231 3
        $csv->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
232
        $csv->seek($offset);
233 3
        $row = $csv->current();
234
        if (empty($row) || [null] === $row) {
235
            throw new InvalidArgumentException('the specified row does not exist or is empty');
236
        }
237
238
        if (0 != $offset) {
239
            return $row;
240
        }
241 267
242
        return $this->removeBOM($row, mb_strlen($this->getInputBOM()), $this->enclosure);
243 267
    }
244 267
245 33
    /**
246 22
     * Outputs all data on the CSV file
247 267
     *
248 267
     * @param string $filename CSV downloaded name if present adds extra headers
249
     *
250 267
     * @return int Returns the number of characters read from the handle
251
     *             and passed through to the output.
252
     */
253
    public function output(string $filename = null): int
254
    {
255
        if (null !== $filename) {
256
            $filename = filter_var($filename, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
257
            header('Content-Type: text/csv');
258
            header('Content-Transfer-Encoding: binary');
259
            header("Content-Disposition: attachment; filename=\"$filename\"");
260
        }
261
262
        return $this->fpassthru();
263
    }
264
265
    /**
266
     * Outputs all data from the CSV
267
     *
268
     * @return int Returns the number of characters read from the handle
269
     *             and passed through to the output.
270
     */
271
    protected function fpassthru(): int
272
    {
273
        $bom = '';
274
        $input_bom = $this->getInputBOM();
275
        if ($this->output_bom && $input_bom != $this->output_bom) {
276
            $bom = $this->output_bom;
277
        }
278
        $csv = $this->getDocument();
279
        $csv->rewind();
280
        if ('' !== $bom) {
281
            $csv->fseek(mb_strlen($input_bom));
282
        }
283
        echo $bom;
284
        $res = $csv->fpassthru();
285
286
        return $res + strlen($bom);
287
    }
288
289
    /**
290
     * Retrieves the CSV content
291
     *
292
     * @return string
293
     */
294
    public function __toString(): string
295
    {
296
        ob_start();
297
        $this->fpassthru();
298
299
        return ob_get_clean();
300
    }
301
}
302