Completed
Push — master ( d8a5e5...b3757c )
by ignace nyamagana
03:17
created

QueryFilter::hasFilter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 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.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
namespace League\Csv\Modifier;
14
15
use ArrayObject;
16
use CallbackFilterIterator;
17
use Iterator;
18
use League\Csv\AbstractCsv;
19
use LimitIterator;
20
use UnexpectedValueException;
21
22
/**
23
 *  A Trait to Query rows against a SplFileObject
24
 *
25
 * @package League.csv
26
 * @since  4.2.1
27
 *
28
 */
29
trait QueryFilter
30
{
31
    /**
32
     * Callables to filter the iterator
33
     *
34
     * @var callable[]
35
     */
36
    protected $iterator_filters = [];
37
38
    /**
39
     * Callables to sort the iterator
40
     *
41
     * @var callable[]
42
     */
43
    protected $iterator_sort_by = [];
44
45
    /**
46
     * iterator Offset
47
     *
48
     * @var int
49
     */
50
    protected $iterator_offset = 0;
51
52
    /**
53
     * iterator maximum length
54
     *
55
     * @var int
56
     */
57
    protected $iterator_limit = -1;
58
59
    /**
60
     * Stripping BOM status
61
     *
62
     * @var boolean
63
     */
64
    protected $strip_bom = false;
65
66
    /**
67
     * Reader return type
68
     *
69
     * @var int
70
     */
71
    protected $returnType = AbstractCsv::TYPE_ARRAY;
72
73
    /**
74
     * Set the return type for the next fetch call
75
     *
76
     * @param int $type
77
     *
78
     * @throws UnexpectedValueException If the value is not one of the defined constant
79
     *
80
     * @return static
81
     */
82 24
    public function setReturnType($type)
83
    {
84 24
        $modes = [AbstractCsv::TYPE_ARRAY => 1, AbstractCsv::TYPE_ITERATOR => 1];
85 24
        if (!isset($modes[$type])) {
86 2
            throw new UnexpectedValueException('Unknown return type');
87
        }
88 22
        $this->returnType = $type;
89
90 22
        return $this;
91
    }
92
93
    /**
94
     * Stripping BOM setter
95
     *
96
     * @param bool $status
97
     *
98
     * @return $this
99
     */
100 16
    public function stripBom($status)
101
    {
102 16
        $this->strip_bom = (bool) $status;
103
104 16
        return $this;
105
    }
106
107
    /**
108
     * Tell whether we can strip or not the leading BOM sequence
109
     *
110
     * @return bool
111
     */
112 18
    protected function isBomStrippable()
113
    {
114 18
        $bom = $this->getInputBom();
115
116 18
        return !empty($bom) && $this->strip_bom;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    abstract public function getInputBom();
123
124
    /**
125
     * Set LimitIterator Offset
126
     *
127
     * @param $offset
128
     *
129
     * @return $this
130
     */
131 22
    public function setOffset($offset = 0)
132
    {
133 22
        $this->iterator_offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
134
135 20
        return $this;
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141
    abstract protected function validateInteger($int, $minValue, $errorMessage);
142
143
    /**
144
     * Set LimitIterator Count
145
     *
146
     * @param int $limit
147
     *
148
     * @return $this
149
     */
150 20
    public function setLimit($limit = -1)
151
    {
152 20
        $this->iterator_limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
153
154 18
        return $this;
155
    }
156
157
    /**
158
     * Set an Iterator sorting callable function
159
     *
160
     * @param callable $callable
161
     *
162
     * @return $this
163
     */
164 2
    public function addSortBy(callable $callable)
165
    {
166 2
        $this->iterator_sort_by[] = $callable;
167
168 2
        return $this;
169
    }
170
171
    /**
172
     * Remove a callable from the collection
173
     *
174
     * @param callable $callable
175
     *
176
     * @return $this
177
     */
178 2
    public function removeSortBy(callable $callable)
179
    {
180 2
        $res = array_search($callable, $this->iterator_sort_by, true);
181 2
        unset($this->iterator_sort_by[$res]);
182
183 2
        return $this;
184
    }
185
186
    /**
187
     * Detect if the callable is already registered
188
     *
189
     * @param callable $callable
190
     *
191
     * @return bool
192
     */
193 2
    public function hasSortBy(callable $callable)
194
    {
195 2
        return false !== array_search($callable, $this->iterator_sort_by, true);
196
    }
197
198
    /**
199
     * Remove all registered callable
200
     *
201
     * @return $this
202
     */
203 14
    public function clearSortBy()
204
    {
205 14
        $this->iterator_sort_by = [];
206
207 2
        return $this;
208
    }
209
210
    /**
211
     * Set the Iterator filter method
212
     *
213
     * @param callable $callable
214
     *
215
     * @return $this
216
     */
217 44
    public function addFilter(callable $callable)
218
    {
219 44
        $this->iterator_filters[] = $callable;
220
221 44
        return $this;
222
    }
223
224
    /**
225
     * Remove a filter from the callable collection
226
     *
227
     * @param callable $callable
228
     *
229
     * @return $this
230
     */
231 2
    public function removeFilter(callable $callable)
232
    {
233 2
        $res = array_search($callable, $this->iterator_filters, true);
234 2
        unset($this->iterator_filters[$res]);
235
236 2
        return $this;
237
    }
238
239
    /**
240
     * Detect if the callable filter is already registered
241
     *
242
     * @param callable $callable
243
     *
244
     * @return bool
245
     */
246 2
    public function hasFilter(callable $callable)
247
    {
248 2
        return false !== array_search($callable, $this->iterator_filters, true);
249
    }
250
251
    /**
252
     * Remove all registered callable filter
253
     *
254
     * @return $this
255
     */
256 116
    public function clearFilter()
257
    {
258 116
        $this->iterator_filters = [];
259
260 116
        return $this;
261
    }
262
263
    /**
264
     * Remove the BOM sequence from the CSV
265
     *
266
     * @param Iterator $iterator
267
     *
268
     * @return Iterator
269
     */
270 116
    protected function applyBomStripping(Iterator $iterator)
271
    {
272 116
        if (!$this->strip_bom) {
273 100
            return $iterator;
274
        }
275
276 16
        if (!$this->isBomStrippable()) {
277 2
            $this->strip_bom = false;
278
279 2
            return $iterator;
280
        }
281
282 14
        $this->strip_bom = false;
283
284 14
        return $this->getStripBomIterator($iterator);
285
    }
286
287
    /**
288
     * Return the Iterator without the BOM sequence
289
     *
290
     * @param Iterator $iterator
291
     *
292
     * @return Iterator
293
     */
294 14
    protected function getStripBomIterator(Iterator $iterator)
295
    {
296 14
        $bom = $this->getInputBom();
297
298
        return new MapIterator($iterator, function ($row, $index) use ($bom) {
299 14
            if (0 == $index) {
300 14
                $row[0] = mb_substr($row[0], mb_strlen($bom));
301 14
                $enclosure = $this->getEnclosure();
302
                //enclosure should be remove when a BOM sequence is stripped
303 14
                if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) == $enclosure) {
304 8
                    $row[0] = mb_substr($row[0], 1, -1);
305 8
                }
306 14
            }
307
308 14
            return $row;
309 14
        });
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315
    abstract public function getEnclosure();
316
317
    /**
318
     * Returns the CSV Iterator
319
     *
320
     * @return Iterator
321
     */
322 116
    protected function getQueryIterator()
323
    {
324
        array_unshift($this->iterator_filters, function ($row) {
325 114
            return is_array($row) && $row != [null];
326 116
        });
327 116
        $iterator = $this->getIterator();
328 116
        $iterator = $this->applyBomStripping($iterator);
329 116
        $iterator = $this->applyIteratorFilter($iterator);
330 116
        $iterator = $this->applyIteratorSortBy($iterator);
331 116
        $this->returnType = AbstractCsv::TYPE_ARRAY;
332
333 116
        return $this->applyIteratorInterval($iterator);
334
    }
335
336
    /**
337
     * {@inheritdoc}
338
     */
339
    abstract public function getIterator();
340
341
    /**
342
    * Filter the Iterator
343
    *
344
    * @param Iterator $iterator
345
    *
346
    * @return Iterator
347
    */
348 116
    protected function applyIteratorFilter(Iterator $iterator)
349
    {
350 116
        foreach ($this->iterator_filters as $callable) {
351 116
            $iterator = new CallbackFilterIterator($iterator, $callable);
352 116
        }
353 116
        $this->clearFilter();
354
355 116
        return $iterator;
356
    }
357
358
    /**
359
    * Sort the Iterator
360
    *
361
    * @param Iterator $iterator
362
    *
363
    * @return Iterator
364
    */
365 116
    protected function applyIteratorInterval(Iterator $iterator)
366
    {
367 116
        if (0 == $this->iterator_offset && -1 == $this->iterator_limit) {
368 94
            return $iterator;
369
        }
370 22
        $offset = $this->iterator_offset;
371 22
        $limit  = $this->iterator_limit;
372 22
        $this->iterator_limit  = -1;
373 22
        $this->iterator_offset = 0;
374
375 22
        return new LimitIterator($iterator, $offset, $limit);
376
    }
377
378
    /**
379
    * Sort the Iterator
380
    *
381
    * @param Iterator $iterator
382
    *
383
    * @return Iterator
384
    */
385 116
    protected function applyIteratorSortBy(Iterator $iterator)
386
    {
387 116
        if (!$this->iterator_sort_by) {
388 114
            return $iterator;
389
        }
390 2
        $obj = new ArrayObject(iterator_to_array($iterator, false));
391 2
        $obj->uasort(function ($rowA, $rowB) {
392 2
            $sortRes = 0;
393 2
            foreach ($this->iterator_sort_by as $callable) {
394 2
                if (0 !== ($sortRes = call_user_func($callable, $rowA, $rowB))) {
395 2
                    break;
396
                }
397 2
            }
398
399 2
            return $sortRes;
400 2
        });
401 2
        $this->clearSortBy();
402
403 2
        return $obj->getIterator();
404
    }
405
406
    /**
407
     * Convert the Iterator into an array depending on the selected return type
408
     *
409
     * @param int      $type
410
     * @param Iterator $iterator
411
     * @param bool     $use_keys Whether to use the iterator element keys as index
412
     *
413
     * @return Iterator|array
414
     */
415 90
    protected function applyReturnType($type, Iterator $iterator, $use_keys = true)
416
    {
417 90
        if (AbstractCsv::TYPE_ARRAY == $type) {
418 80
            return iterator_to_array($iterator, $use_keys);
419
        }
420
421 14
        return $iterator;
422
    }
423
}
424