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

Query   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 39
c 6
b 0
f 0
lcom 1
cbo 4
dl 0
loc 349
rs 8.2857

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __set() 0 4 1
A __unset() 0 4 1
A addHeaders() 0 15 3
A __invoke() 0 12 2
B assertValidKeys() 0 10 5
A execute() 0 18 3
A useMBStringConverter() 0 5 3
A stripBOM() 0 12 2
A setOffset() 0 12 2
A setLimit() 0 12 2
A addSortBy() 0 7 1
A addFilter() 0 7 1
A applyBOMStripping() 0 8 3
A getStripBOMIterator() 0 19 4
A applyIteratorFilter() 0 8 1
A applyIteratorSortBy() 0 20 4
A convertToUtf8() 0 12 1
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 ArrayIterator;
16
use CallbackFilterIterator;
17
use InvalidArgumentException;
18
use Iterator;
19
use League\Csv\Config\Validator;
20
use LimitIterator;
21
22
/**
23
 * A simple Query class to fetch rows against a Csv file object
24
 *
25
 * @package League.csv
26
 * @since  4.2.1
27
 *
28
 */
29
class Query
30
{
31
    use Validator;
32
33
    /**
34
     * Callables to filter the iterator
35
     *
36
     * @var callable[]
37
     */
38
    protected $filters = [];
39
40
    /**
41
     * Callables to sort the iterator
42
     *
43
     * @var callable[]
44
     */
45
    protected $sort_by = [];
46
47
    /**
48
     * iterator Offset
49
     *
50
     * @var int
51
     */
52
    protected $offset = 0;
53
54
    /**
55
     * iterator maximum length
56
     *
57
     * @var int
58
     */
59
    protected $limit = -1;
60
61
    /**
62
     * Stripping BOM status
63
     *
64
     * @var bool
65
     */
66
    protected $strip_BOM = true;
67
68
    /**
69
     * Offset of Header keys
70
     *
71
     * @var array|int
72
     */
73
    protected $headers = 0;
74
75
    /**
76
     * @inheritdoc
77
     */
78
    public function __set($property, $value)
79
    {
80
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
81
    }
82
83
    /**
84
     * @inheritdoc
85
     */
86
    public function __unset($property)
87
    {
88
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
89
    }
90
91
    /**
92
     * Add Headers
93
     *
94
     * @param string[]|int $headers
95
     */
96
    public function addHeaders($headers)
97
    {
98
        if (!is_array($headers)) {
99
            $headers = $this->validateInteger($headers, 0, 'the row index must be a positive integer, 0 or a non empty array');
100
        }
101
102
        if ($headers === $this->headers) {
103
            return $this;
104
        }
105
106
        $clone = clone $this;
107
        $clone->headers = $headers;
108
109
        return $clone;
110
    }
111
112
    /**
113
     * Returns a Record object
114
     *
115
     * @return Records
116
     */
117
    public function __invoke(AbstractCsv $csv)
118
    {
119
        $headers = $this->headers;
120
        if (!is_array($headers)) {
121
            $it = (new static())->setOffset($headers)->setLimit(1)->execute($csv);
122
            $it->rewind();
123
            $headers = (array) $it->current();
124
        }
125
        $this->assertValidKeys($headers);
126
127
        return new Records($this->execute($csv), $headers);
128
    }
129
130
    /**
131
     * Validates the array to be used by the fetchAssoc method
132
     *
133
     * @param array $keys
0 ignored issues
show
Bug introduced by
There is no parameter named $keys. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
134
     *
135
     * @throws InvalidArgumentException If the submitted array fails the assertion
136
     *
137
     * @return array
138
     */
139
    protected function assertValidKeys(array $headers)
140
    {
141
        $is_valid_keys = function ($value) {
142
            return is_scalar($value) || (is_object($value) && method_exists($value, '__toString'));
143
        };
144
145
        if (empty($headers) || $headers !== array_unique(array_filter($headers, $is_valid_keys))) {
146
            throw new InvalidArgumentException('Use a flat array with unique string values');
147
        }
148
    }
149
150
    /**
151
     * Returns the CSV Iterator
152
     *
153
     * @return Iterator
154
     */
155
    protected function execute(AbstractCsv $csv)
156
    {
157
        $input_encoding = $csv->getInputEncoding();
158
        $use_converter = $this->useMBStringConverter($csv);
159
        $normalizedCsv = function ($row) {
160
            return is_array($row) && $row != [null];
161
        };
162
        array_unshift($this->filters, $normalizedCsv);
163
        $iterator = $this->applyBOMStripping($csv);
164
        $iterator = $this->applyIteratorFilter($iterator);
165
        $iterator = $this->applyIteratorSortBy($iterator);
166
        $iterator = new LimitIterator($iterator, $this->offset, $this->limit);
167
168
        if (!$use_converter) {
169
            return $iterator;
170
        }
171
        return $this->convertToUtf8($iterator, $input_encoding);
172
    }
173
174
    protected function useMBStringConverter(AbstractCsv $csv)
175
    {
176
        return !('UTF-8' === $csv->getInputEncoding()
177
            || ($csv->isActiveStreamFilter() && STREAM_FILTER_READ === $csv->getStreamFilterMode()));
178
    }
179
180
    /**
181
     * Stripping BOM setter
182
     *
183
     * @param bool $status
184
     *
185
     * @return static
186
     */
187
    public function stripBOM($status)
188
    {
189
        $status = (bool) $status;
190
        if ($status === $this->strip_BOM) {
191
            return $this;
192
        }
193
194
        $clone = clone $this;
195
        $clone->strip_BOM = $status;
196
197
        return $clone;
198
    }
199
200
    /**
201
     * Set LimitIterator Offset
202
     *
203
     * @param $offset
204
     *
205
     * @return static
206
     */
207
    public function setOffset($offset)
208
    {
209
        $offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
210
        if ($offset === $this->offset) {
211
            return $this;
212
        }
213
214
        $clone = clone $this;
215
        $clone->offset = $offset;
216
217
        return $clone;
218
    }
219
220
    /**
221
     * Set LimitIterator Count
222
     *
223
     * @param int $limit
224
     *
225
     * @return static
226
     */
227
    public function setLimit($limit)
228
    {
229
        $limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
230
        if ($limit === $this->limit) {
231
            return $this;
232
        }
233
234
        $clone = clone $this;
235
        $clone->limit = $limit;
236
237
        return $clone;
238
    }
239
240
    /**
241
     * Set an Iterator sorting callable function
242
     *
243
     * @param callable $callable
244
     *
245
     * @return static
246
     */
247
    public function addSortBy(callable $callable)
248
    {
249
        $clone = clone $this;
250
        $clone->sort_by[] = $callable;
251
252
        return $clone;
253
    }
254
255
    /**
256
     * Set the Iterator filter method
257
     *
258
     * @param callable $callable
259
     *
260
     * @return static
261
     */
262
    public function addFilter(callable $callable)
263
    {
264
        $clone = clone $this;
265
        $clone->filters[] = $callable;
266
267
        return $clone;
268
    }
269
270
    /**
271
     * Remove the BOM sequence from the CSV
272
     *
273
     * @param AbstractCsv $csv
274
     *
275
     * @return Iterator
276
     */
277
    protected function applyBOMStripping(AbstractCsv $csv)
278
    {
279
        if ($this->strip_BOM && '' != $csv->getInputBOM()) {
280
            return $this->getStripBOMIterator($csv);
281
        }
282
283
        return $csv->getIterator();
284
    }
285
286
    /**
287
     * Return the Iterator without the BOM sequence
288
     *
289
     * @param AbstractCsv $csv
290
     *
291
     * @return Iterator
292
     */
293
    protected function getStripBOMIterator(AbstractCsv $csv)
294
    {
295
        $bom_length = mb_strlen($csv->getInputBOM());
296
        $enclosure = $csv->getEnclosure();
297
        $strip_BOM = function ($row, $index) use ($bom_length, $enclosure) {
298
            if (0 != $index) {
299
                return $row;
300
            }
301
302
            $row[0] = mb_substr($row[0], $bom_length);
303
            if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) === $enclosure) {
304
                $row[0] = mb_substr($row[0], 1, -1);
305
            }
306
307
            return $row;
308
        };
309
310
        return new MapIterator($csv->getIterator(), $strip_BOM);
311
    }
312
313
    /**
314
    * Filter the Iterator
315
    *
316
    * @param Iterator $iterator
317
    *
318
    * @return Iterator
319
    */
320
    protected function applyIteratorFilter(Iterator $iterator)
321
    {
322
        $reducer = function ($iterator, $callable) {
323
            return new CallbackFilterIterator($iterator, $callable);
324
        };
325
326
        return array_reduce($this->filters, $reducer, $iterator);
327
    }
328
329
    /**
330
    * Sort the Iterator
331
    *
332
    * @param Iterator $iterator
333
    *
334
    * @return Iterator
335
    */
336
    protected function applyIteratorSortBy(Iterator $iterator)
337
    {
338
        if (!$this->sort_by) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sort_by of type callable[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
339
            return $iterator;
340
        }
341
342
        $obj = new ArrayIterator(iterator_to_array($iterator));
343
        $obj->uasort(function ($row_a, $row_b) {
344
            $res = 0;
345
            foreach ($this->sort_by as $compare) {
346
                if (0 !== ($res = call_user_func($compare, $row_a, $row_b))) {
347
                    break;
348
                }
349
            }
350
351
            return $res;
352
        });
353
354
        return $obj;
355
    }
356
357
    /**
358
     * Convert Iterator to UTF-8 if needed
359
     *
360
     * @param string   $input_encoding
361
     * @param Iterator $iterator
362
     *
363
     * @return Iterator
364
     */
365
    protected function convertToUtf8(Iterator $iterator, $input_encoding)
366
    {
367
        $convert_cell = function ($value) use ($input_encoding) {
368
            return mb_convert_encoding($value, 'UTF-8', $input_encoding);
369
        };
370
371
        $convert_row = function ($row) use ($convert_cell) {
372
            return array_map($convert_cell, (array) $row);
373
        };
374
375
        return new MapIterator($iterator, $convert_row);
376
    }
377
}
378