Completed
Pull Request — master (#178)
by ignace nyamagana
02:29
created

AbstractCsv::validateString()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 7
ccs 3
cts 3
cp 1
rs 9.2
cc 4
eloc 4
nc 2
nop 1
crap 4
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 8.1.1
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
namespace League\Csv;
14
15
use InvalidArgumentException;
16
use IteratorAggregate;
17
use League\Csv\Config\Controls;
18
use LimitIterator;
19
use SplFileInfo;
20
use SplFileObject;
21
use SplTempFileObject;
22
23
/**
24
 *  An abstract class to enable basic CSV manipulation
25
 *
26
 * @package League.csv
27
 * @since  4.0.0
28
 *
29
 */
30
abstract class AbstractCsv implements IteratorAggregate
31
{
32
    use Controls;
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 = "\x00\x00\xFF\xFE";
58
59
    /**
60
     * The constructor path
61
     *
62
     * can be a SplFileInfo object or the string path to a file
63
     *
64
     * @var SplFileObject|string
65
     */
66
    protected $path;
67
68
    /**
69
     * The file open mode flag
70
     *
71
     * @var string
72
     */
73
    protected $open_mode;
74
75
    /**
76
     * Csv Header Info
77
     *
78
     * @var array|int
79
     */
80
    protected $header = [];
81
82
    /**
83
     * Creates a new instance
84
     *
85
     * The path must be an SplFileInfo object
86
     * an object that implements the `__toString` method
87
     * a path to a file
88
     *
89
     * @param SplFileObject|string $path      The file path
90
     * @param string               $open_mode The file open mode flag
91
     */
92
    protected function __construct($path, $open_mode = 'r+')
93
    {
94
        $this->open_mode = strtolower($open_mode);
95
        $this->path = $path;
96
        $this->initStreamFilter($this->path);
97
    }
98
99
    /**
100
     * The destructor
101
     */
102
    public function __destruct()
103
    {
104
        $this->path = null;
105 357
    }
106
107 357
    /**
108 357
     * Return a new {@link AbstractCsv} from a SplFileObject
109 357
     *
110 357
     * @param SplFileObject $file
111
     *
112
     * @return static
113
     */
114
    public static function createFromFileObject(SplFileObject $file)
115 240
    {
116
        $csv = new static($file);
117 240
        $controls = $file->getCsvControl();
118 240
        $csv->setDelimiter($controls[0]);
119
        $csv->setEnclosure($controls[1]);
120
        if (isset($controls[2])) {
121
            $csv->setEscape($controls[2]);
122
        }
123
124
        return $csv;
125
    }
126
127 312
    /**
128
     * Return a new {@link AbstractCsv} from a string
129 312
     *
130
     * The string must be an object that implements the `__toString` method,
131
     * or a string
132
     *
133
     * @param string|object $str the string
134
     *
135
     * @return static
136
     */
137
    public static function createFromString($str)
138
    {
139
        $file = new SplTempFileObject();
140
        $file->fwrite(static::validateString($str));
141
142 27
        return new static($file);
143
    }
144 27
145 27
    /**
146
     * Return a new {@link AbstractCsv} from a string
147 27
     *
148
     * @param mixed  $path      file path
149
     * @param string $open_mode the file open mode flag
150
     *
151
     * @throws InvalidArgumentException If $path is a SplTempFileObject object
152
     *
153
     * @return static
154
     */
155
    public static function createFromPath($path, $open_mode = 'r+')
156
    {
157
        if ($path instanceof SplTempFileObject) {
158
            throw new InvalidArgumentException('an `SplTempFileObject` object does not contain a valid path');
159 90
        }
160
161 90
        if ($path instanceof SplFileInfo) {
162 87
            $path = $path->getPath().'/'.$path->getBasename();
163
        }
164 3
165
        return new static(static::validateString($path), $open_mode);
166
    }
167
168
    /**
169
     * Return a new {@link AbstractCsv} instance from another {@link AbstractCsv} object
170
     *
171
     * @param string $class     the class to be instantiated
172
     * @param string $open_mode the file open mode flag
173
     *
174
     * @return static
175
     */
176
    protected function newInstance($class, $open_mode)
177 54
    {
178
        $csv = new $class($this->path, $open_mode);
179 54
        $csv->delimiter = $this->delimiter;
180 3
        $csv->enclosure = $this->enclosure;
181
        $csv->escape = $this->escape;
182
        $csv->input_encoding = $this->input_encoding;
183 51
        $csv->input_bom = $this->input_bom;
184 3
        $csv->output_bom = $this->output_bom;
185 2
        $csv->newline = $this->newline;
186
        $csv->header = $this->header;
187 51
188
        return $csv;
189
    }
190
191
    /**
192
     * Return a new {@link Writer} instance from a {@link AbstractCsv} object
193
     *
194
     * @param string $open_mode the file open mode flag
195
     *
196
     * @return Writer
197
     */
198 6
    public function newWriter($open_mode = 'r+')
199
    {
200 6
        return $this->newInstance(Writer::class, $open_mode);
201 6
    }
202 6
203 6
    /**
204 6
     * Return a new {@link Reader} instance from a {@link AbstractCsv} object
205 6
     *
206 6
     * @param string $open_mode the file open mode flag
207 6
     *
208
     * @return Reader
209 6
     */
210
    public function newReader($open_mode = 'r+')
211
    {
212
        return $this->newInstance(Reader::class, $open_mode);
213
    }
214
215
    /**
216
     * Returns the inner SplFileObject
217
     *
218
     * @return SplFileObject
219 3
     */
220
    public function getIterator()
221 3
    {
222
        $iterator = $this->path;
223
        if (!$iterator instanceof SplFileObject) {
224
            $iterator = new SplFileObject($this->getStreamFilterPath(), $this->open_mode);
225
        }
226
        $iterator->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
227
        $iterator->setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY);
228
229
        return $iterator;
230
    }
231 3
232
    /**
233 3
     * Tell whether the current header is internal
234
     * or user submitted
235
     *
236
     * @return int|null
237
     */
238
    public function getHeaderOffset()
239
    {
240
        if (is_array($this->header)) {
241 267
            return null;
242
        }
243 267
244 267
        return $this->header;
245 33
    }
246 22
247 267
    /**
248 267
     * Returns the CSV header
249
     *
250 267
     * @return array
251
     */
252
    public function getHeader()
253
    {
254
        if (is_array($this->header)) {
255
            return $this->header;
256
        }
257
258
        $iterator = new LimitIterator($this->getIterator(), $this->header);
259
        $iterator->rewind();
260
        $header = $iterator->current();
261
        if (!is_array($header) || $iterator->key() !== $this->header) {
262
            throw new InvalidArgumentException('the select offset does not exist');
263
        }
264
265
        if (empty($header) || 0 !== $this->header) {
266
            return $header;
267
        }
268
269
        return $this->formatDocumentHeader($header);
270
    }
271
272
    protected function formatDocumentHeader(array $header)
273
    {
274
        $bom_length = mb_strlen($this->getInputBOM());
275
        $header[0] = mb_substr($header[0], $bom_length);
276
        $enclosure = $this->getEnclosure();
277
        if ($header[0][0] === $enclosure && mb_substr($header[0], -1, 1) === $enclosure) {
278
            $header[0] = mb_substr($header[0], 1, -1);
279
        }
280
281
        if (!$this->useInternalConverter()) {
282
            return $header;
283
        }
284
285
        $input_encoding = $this->getInputEncoding();
286
287
        return array_map(function ($value) use ($input_encoding) {
288
            return mb_convert_encoding($value, 'UTF-8', $input_encoding);
289
        }, $header);
290
    }
291
292
    /**
293
     * Tell whether to use Stream Filter or not to convert the CSV
294
     *
295
     * @return bool
296
     */
297
    protected function useInternalConverter()
298
    {
299
        return !('UTF-8' === $this->getInputEncoding()
300
            || ($this->isActiveStreamFilter() && STREAM_FILTER_READ === $this->getStreamFilterMode()));
301
    }
302
303
    /**
304
     * Selects the array to be used as key for the fetchAssoc method
305
     *
306
     * @param int|null|array $offset_or_keys the assoc key OR the row Index to be used
307
     *                                       as the key index
308
     *
309
     * @return array
310
     */
311
    public function setHeader($offset_or_keys)
312
    {
313
        if (is_array($offset_or_keys)) {
314
            $this->header = $this->validateHeader($offset_or_keys);
315
            return $this;
316
        }
317
318
        if (null === $offset_or_keys) {
319
            $this->header = [];
320
            return $this;
321
        }
322
323
        $this->header = $this->validateInteger($offset_or_keys, 0, 'the header offset is invalid');
324
        return $this;
325
    }
326
327
    /**
328
     * Validates the array to be used by the fetchAssoc method
329
     *
330
     * @param array $keys
331
     *
332
     * @throws InvalidArgumentException If the submitted array fails the assertion
333
     *
334
     * @return array
335
     */
336
    protected function validateHeader(array $keys)
337
    {
338
        $validateKeyValue = function ($value) {
339
            return is_scalar($value) || (is_object($value) && method_exists($value, '__toString'));
340
        };
341
342
        if (empty($keys) || $keys === array_unique(array_filter($keys, $validateKeyValue))) {
343
            return $keys;
344
        }
345
346
        throw new InvalidArgumentException('Use a flat array with unique string values');
347
    }
348
349
    /**
350
     * Returns the Record object
351
     *
352
     * @param Statement|null $stmt
353
     *
354
     * @return RecordSet
355
     */
356
    public function select(Statement $stmt = null)
357
    {
358
        $stmt = $stmt ?: new Statement();
359
360
        return $stmt->process($this);
361
    }
362
}
363