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

Query::__invoke()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 18
rs 9.4285
cc 3
eloc 13
nc 2
nop 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
     * @inheritdoc
70
     */
71
    public function __set($property, $value)
72
    {
73
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
74
    }
75
76
    /**
77
     * @inheritdoc
78
     */
79
    public function __unset($property)
80
    {
81
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
82
    }
83
84
    /**
85
     * Returns the CSV Iterator
86
     *
87
     * @return Iterator
88
     */
89
    public function __invoke(AbstractCsv $csv)
90
    {
91
        $input_encoding = $csv->getInputEncoding();
92
        $use_converter = $this->useMBStringConverter($csv);
93
        $normalizedCsv = function ($row) {
94
            return is_array($row) && $row != [null];
95
        };
96
        array_unshift($this->filters, $normalizedCsv);
97
        $iterator = $this->applyBOMStripping($csv);
98
        $iterator = $this->applyIteratorFilter($iterator);
99
        $iterator = $this->applyIteratorSortBy($iterator);
100
        $iterator = new LimitIterator($iterator, $this->offset, $this->limit);
101
102
        if (!$use_converter) {
103
            return $iterator;
104
        }
105
        return $this->convertToUtf8($iterator, $input_encoding);
106
    }
107
108
    protected function useMBStringConverter(AbstractCsv $csv)
109
    {
110
        return !(
111
            'UTF-8' === $csv->getInputEncoding()
112
            || ($csv->isActiveStreamFilter() && STREAM_FILTER_READ === $csv->getStreamFilterMode())
113
        );
114
    }
115
116
    /**
117
     * Stripping BOM setter
118
     *
119
     * @param bool $status
120
     *
121
     * @return static
122
     */
123
    public function stripBOM($status)
124
    {
125
        $status = (bool) $status;
126
        if ($status === $this->strip_BOM) {
127
            return $this;
128
        }
129
130
        $clone = clone $this;
131
        $clone->strip_BOM = $status;
132
133
        return $clone;
134
    }
135
136
    /**
137
     * Set LimitIterator Offset
138
     *
139
     * @param $offset
140
     *
141
     * @return static
142
     */
143
    public function setOffset($offset)
144
    {
145
        $offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
146
        if ($offset === $this->offset) {
147
            return $this;
148
        }
149
150
        $clone = clone $this;
151
        $clone->offset = $offset;
152
153
        return $clone;
154
    }
155
156
    /**
157
     * Set LimitIterator Count
158
     *
159
     * @param int $limit
160
     *
161
     * @return static
162
     */
163
    public function setLimit($limit)
164
    {
165
        $limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
166
        if ($limit === $this->limit) {
167
            return $this;
168
        }
169
170
        $clone = clone $this;
171
        $clone->limit = $limit;
172
173
        return $clone;
174
    }
175
176
    /**
177
     * Set an Iterator sorting callable function
178
     *
179
     * @param callable $callable
180
     *
181
     * @return static
182
     */
183
    public function addSortBy(callable $callable)
184
    {
185
        $clone = clone $this;
186
        $clone->sort_by[] = $callable;
187
188
        return $clone;
189
    }
190
191
    /**
192
     * Set the Iterator filter method
193
     *
194
     * @param callable $callable
195
     *
196
     * @return static
197
     */
198
    public function addFilter(callable $callable)
199
    {
200
        $clone = clone $this;
201
        $clone->filters[] = $callable;
202
203
        return $clone;
204
    }
205
206
    /**
207
     * Remove the BOM sequence from the CSV
208
     *
209
     * @param AbstractCsv $csv
210
     *
211
     * @return Iterator
212
     */
213
    protected function applyBOMStripping(AbstractCsv $csv)
214
    {
215
        if ($this->strip_BOM && '' != $csv->getInputBOM()) {
216
            return $this->getStripBOMIterator($csv);
217
        }
218
219
        return $csv->getIterator();
220
    }
221
222
    /**
223
     * Return the Iterator without the BOM sequence
224
     *
225
     * @param AbstractCsv $csv
226
     *
227
     * @return Iterator
228
     */
229
    protected function getStripBOMIterator(AbstractCsv $csv)
230
    {
231
        $bom_length = mb_strlen($csv->getInputBOM());
232
        $enclosure = $csv->getEnclosure();
233
        $strip_BOM = function ($row, $index) use ($bom_length, $enclosure) {
234
            if (0 != $index) {
235
                return $row;
236
            }
237
238
            $row[0] = mb_substr($row[0], $bom_length);
239
            if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) === $enclosure) {
240
                $row[0] = mb_substr($row[0], 1, -1);
241
            }
242
243
            return $row;
244
        };
245
246
        return new MapIterator($csv->getIterator(), $strip_BOM);
247
    }
248
249
    /**
250
    * Filter the Iterator
251
    *
252
    * @param Iterator $iterator
253
    *
254
    * @return Iterator
255
    */
256
    protected function applyIteratorFilter(Iterator $iterator)
257
    {
258
        $reducer = function ($iterator, $callable) {
259
            return new CallbackFilterIterator($iterator, $callable);
260
        };
261
262
        return array_reduce($this->filters, $reducer, $iterator);
263
    }
264
265
    /**
266
    * Sort the Iterator
267
    *
268
    * @param Iterator $iterator
269
    *
270
    * @return Iterator
271
    */
272
    protected function applyIteratorSortBy(Iterator $iterator)
273
    {
274
        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...
275
            return $iterator;
276
        }
277
278
        $obj = new ArrayIterator(iterator_to_array($iterator));
279
        $obj->uasort(function ($row_a, $row_b) {
280
            $res = 0;
281
            foreach ($this->sort_by as $compare) {
282
                if (0 !== ($res = call_user_func($compare, $row_a, $row_b))) {
283
                    break;
284
                }
285
            }
286
287
            return $res;
288
        });
289
290
        return $obj;
291
    }
292
293
    /**
294
     * Convert Iterator to UTF-8 if needed
295
     *
296
     * @param string   $input_encoding
297
     * @param Iterator $iterator
298
     *
299
     * @return Iterator
300
     */
301
    protected function convertToUtf8(Iterator $iterator, $input_encoding)
302
    {
303
        $convert_cell = function ($value) use ($input_encoding) {
304
            return mb_convert_encoding($value, 'UTF-8', $input_encoding);
305
        };
306
307
        $convert_row = function ($row) use ($convert_cell) {
308
            return array_map($convert_cell, (array) $row);
309
        };
310
311
        return new MapIterator($iterator, $convert_row);
312
    }
313
}
314