Completed
Pull Request — master (#178)
by ignace nyamagana
03:07
created

Query   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 269
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 3

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 27
c 5
b 0
f 0
lcom 4
cbo 3
dl 0
loc 269
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __invoke() 0 18 3
A useMBStringConverter() 0 7 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 Iterator;
18
use League\Csv\Config\Validator;
19
use LimitIterator;
20
21
/**
22
 * A simple Query class to fetch rows against a Csv file object
23
 *
24
 * @package League.csv
25
 * @since  4.2.1
26
 *
27
 */
28
class Query
29
{
30
    use Validator;
31
32
    /**
33
     * Callables to filter the iterator
34
     *
35
     * @var callable[]
36
     */
37
    protected $filters = [];
38
39
    /**
40
     * Callables to sort the iterator
41
     *
42
     * @var callable[]
43
     */
44
    protected $sort_by = [];
45
46
    /**
47
     * iterator Offset
48
     *
49
     * @var int
50
     */
51
    protected $offset = 0;
52
53
    /**
54
     * iterator maximum length
55
     *
56
     * @var int
57
     */
58
    protected $limit = -1;
59
60
    /**
61
     * Stripping BOM status
62
     *
63
     * @var bool
64
     */
65
    protected $strip_BOM = true;
66
67
    /**
68
     * Returns the CSV Iterator
69
     *
70
     * @return Iterator
71
     */
72
    public function __invoke(AbstractCsv $csv)
73
    {
74
        $input_encoding = $csv->getInputEncoding();
75
        $use_converter = $this->useMBStringConverter($csv);
76
        $normalizedCsv = function ($row) {
77
            return is_array($row) && $row != [null];
78
        };
79
        array_unshift($this->filters, $normalizedCsv);
80
        $iterator = $this->applyBOMStripping($csv);
81
        $iterator = $this->applyIteratorFilter($iterator);
82
        $iterator = $this->applyIteratorSortBy($iterator);
83
        $iterator = new LimitIterator($iterator, $this->offset, $this->limit);
84
85
        if (!$use_converter) {
86
            return $iterator;
87
        }
88
        return $this->convertToUtf8($iterator, $input_encoding);
89
    }
90
91
    protected function useMBStringConverter(AbstractCsv $csv)
92
    {
93
        return !(
94
            'UTF-8' === $csv->getInputEncoding()
95
            || ($csv->isActiveStreamFilter() && STREAM_FILTER_READ === $csv->getStreamFilterMode())
96
        );
97
    }
98
99
    /**
100
     * Stripping BOM setter
101
     *
102
     * @param bool $status
103
     *
104
     * @return static
105
     */
106
    public function stripBOM($status)
107
    {
108
        $status = (bool) $status;
109
        if ($status === $this->strip_BOM) {
110
            return $this;
111
        }
112
113
        $clone = clone $this;
114
        $clone->strip_BOM = $status;
115
116
        return $clone;
117
    }
118
119
    /**
120
     * Set LimitIterator Offset
121
     *
122
     * @param $offset
123
     *
124
     * @return static
125
     */
126
    public function setOffset($offset)
127
    {
128
        $offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
129
        if ($offset === $this->offset) {
130
            return $this;
131
        }
132
133
        $clone = clone $this;
134
        $clone->offset = $offset;
135
136
        return $clone;
137
    }
138
139
    /**
140
     * Set LimitIterator Count
141
     *
142
     * @param int $limit
143
     *
144
     * @return static
145
     */
146
    public function setLimit($limit)
147
    {
148
        $limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
149
        if ($limit === $this->limit) {
150
            return $this;
151
        }
152
153
        $clone = clone $this;
154
        $clone->limit = $limit;
155
156
        return $clone;
157
    }
158
159
    /**
160
     * Set an Iterator sorting callable function
161
     *
162
     * @param callable $callable
163
     *
164
     * @return static
165
     */
166
    public function addSortBy(callable $callable)
167
    {
168
        $clone = clone $this;
169
        $clone->sort_by[] = $callable;
170
171
        return $clone;
172
    }
173
174
    /**
175
     * Set the Iterator filter method
176
     *
177
     * @param callable $callable
178
     *
179
     * @return static
180
     */
181
    public function addFilter(callable $callable)
182
    {
183
        $clone = clone $this;
184
        $clone->filters[] = $callable;
185
186
        return $clone;
187
    }
188
189
    /**
190
     * Remove the BOM sequence from the CSV
191
     *
192
     * @param AbstractCsv $csv
193
     *
194
     * @return Iterator
195
     */
196
    protected function applyBOMStripping(AbstractCsv $csv)
197
    {
198
        if ($this->strip_BOM && '' != $csv->getInputBOM()) {
199
            return $this->getStripBOMIterator($csv);
200
        }
201
202
        return $csv->getIterator();
203
    }
204
205
    /**
206
     * Return the Iterator without the BOM sequence
207
     *
208
     * @param AbstractCsv $csv
209
     *
210
     * @return Iterator
211
     */
212
    protected function getStripBOMIterator(AbstractCsv $csv)
213
    {
214
        $bom_length = mb_strlen($csv->getInputBOM());
215
        $enclosure = $csv->getEnclosure();
216
        $strip_BOM = function ($row, $index) use ($bom_length, $enclosure) {
217
            if (0 != $index) {
218
                return $row;
219
            }
220
221
            $row[0] = mb_substr($row[0], $bom_length);
222
            if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) === $enclosure) {
223
                $row[0] = mb_substr($row[0], 1, -1);
224
            }
225
226
            return $row;
227
        };
228
229
        return new MapIterator($csv->getIterator(), $strip_BOM);
230
    }
231
232
    /**
233
    * Filter the Iterator
234
    *
235
    * @param Iterator $iterator
236
    *
237
    * @return Iterator
238
    */
239
    protected function applyIteratorFilter(Iterator $iterator)
240
    {
241
        $reducer = function ($iterator, $callable) {
242
            return new CallbackFilterIterator($iterator, $callable);
243
        };
244
245
        return array_reduce($this->filters, $reducer, $iterator);
246
    }
247
248
    /**
249
    * Sort the Iterator
250
    *
251
    * @param Iterator $iterator
252
    *
253
    * @return Iterator
254
    */
255
    protected function applyIteratorSortBy(Iterator $iterator)
256
    {
257
        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...
258
            return $iterator;
259
        }
260
261
        $obj = new ArrayIterator(iterator_to_array($iterator));
262
        $obj->uasort(function ($row_a, $row_b) {
263
            $res = 0;
264
            foreach ($this->sort_by as $compare) {
265
                if (0 !== ($res = call_user_func($compare, $row_a, $row_b))) {
266
                    break;
267
                }
268
            }
269
270
            return $res;
271
        });
272
273
        return $obj;
274
    }
275
276
    /**
277
     * Convert Iterator to UTF-8 if needed
278
     *
279
     * @param string   $input_encoding
280
     * @param Iterator $iterator
281
     *
282
     * @return Iterator
283
     */
284
    protected function convertToUtf8(Iterator $iterator, $input_encoding)
285
    {
286
        $convert_cell = function ($value) use ($input_encoding) {
287
            return mb_convert_encoding($value, 'UTF-8', $input_encoding);
288
        };
289
290
        $convert_row = function ($row) use ($convert_cell) {
291
            return array_map($convert_cell, (array) $row);
292
        };
293
294
        return new MapIterator($iterator, $convert_row);
295
    }
296
}
297