Completed
Pull Request — master (#178)
by ignace nyamagana
40:39
created

Query::applyIteratorSortBy()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
rs 9.2
cc 4
eloc 11
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 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 = false;
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
        $normalizedCsv = function ($row) {
76
            return is_array($row) && $row != [null];
77
        };
78
        array_unshift($this->filters, $normalizedCsv);
79
        $iterator = $this->applyBomStripping($csv);
80
        $iterator = $this->applyIteratorFilter($iterator);
81
        $iterator = $this->applyIteratorSortBy($iterator);
82
        $iterator = new LimitIterator($iterator, $this->offset, $this->limit);
83
84
        return $this->convertToUtf8($input_encoding, $iterator);
85
    }
86
87
    /**
88
     * Stripping BOM setter
89
     *
90
     * @param bool $status
91
     *
92
     * @return $this
93
     */
94
    public function stripBom($status)
95
    {
96
        $this->strip_bom = (bool) $status;
97
98
        return $this;
99
    }
100
101
    /**
102
     * Set LimitIterator Offset
103
     *
104
     * @param $offset
105
     *
106
     * @return $this
107
     */
108
    public function setOffset($offset = 0)
109
    {
110
        $this->offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
111
112
        return $this;
113
    }
114
115
    /**
116
     * Set LimitIterator Count
117
     *
118
     * @param int $limit
119
     *
120
     * @return $this
121
     */
122
    public function setLimit($limit = -1)
123
    {
124
        $this->limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
125
126
        return $this;
127
    }
128
129
    /**
130
     * Set an Iterator sorting callable function
131
     *
132
     * @param callable $callable
133
     *
134
     * @return $this
135
     */
136
    public function addSortBy(callable $callable)
137
    {
138
        $this->sort_by[] = $callable;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Set the Iterator filter method
145
     *
146
     * @param callable $callable
147
     *
148
     * @return $this
149
     */
150
    public function addFilter(callable $callable)
151
    {
152
        $this->filters[] = $callable;
153
154
        return $this;
155
    }
156
157
    /**
158
     * Remove the BOM sequence from the CSV
159
     *
160
     * @param AbstractCsv $csv
161
     *
162
     * @return Iterator
163
     */
164
    protected function applyBomStripping(AbstractCsv $csv)
165
    {
166
        if ($this->strip_bom && '' != $csv->getInputBOM()) {
167
            return $this->getStripBomIterator($csv);
168
        }
169
170
        return $csv->getIterator();
171
    }
172
173
    /**
174
     * Return the Iterator without the BOM sequence
175
     *
176
     * @param AbstractCsv $csv
177
     *
178
     * @return Iterator
179
     */
180
    protected function getStripBomIterator(AbstractCsv $csv)
181
    {
182
        $bom_length = mb_strlen($csv->getInputBOM());
183
        $enclosure = $csv->getEnclosure();
184
        $strip_bom = function ($row, $index) use ($bom_length, $enclosure) {
185
            if (0 != $index) {
186
                return $row;
187
            }
188
189
            $row[0] = mb_substr($row[0], $bom_length);
190
            if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) === $enclosure) {
191
                $row[0] = mb_substr($row[0], 1, -1);
192
            }
193
194
            return $row;
195
        };
196
197
        return new MapIterator($csv->getIterator(), $strip_bom);
198
    }
199
200
    /**
201
    * Filter the Iterator
202
    *
203
    * @param Iterator $iterator
204
    *
205
    * @return Iterator
206
    */
207
    protected function applyIteratorFilter(Iterator $iterator)
208
    {
209
        $reducer = function ($iterator, $callable) {
210
            return new CallbackFilterIterator($iterator, $callable);
211
        };
212
213
        return array_reduce($this->filters, $reducer, $iterator);
214
    }
215
216
    /**
217
    * Sort the Iterator
218
    *
219
    * @param Iterator $iterator
220
    *
221
    * @return Iterator
222
    */
223
    protected function applyIteratorSortBy(Iterator $iterator)
224
    {
225
        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...
226
            return $iterator;
227
        }
228
229
        $obj = new ArrayIterator(iterator_to_array($iterator));
230
        $obj->uasort(function ($row_a, $row_b) {
231
            $res = 0;
232
            foreach ($this->sort_by as $compare) {
233
                if (0 !== ($res = call_user_func($compare, $row_a, $row_b))) {
234
                    break;
235
                }
236
            }
237
238
            return $res;
239
        });
240
241
        return $obj;
242
    }
243
244
    /**
245
     * Convert Iterator to UTF-8 if needed
246
     *
247
     * @param string   $input_encoding
248
     * @param Iterator $iterator
249
     *
250
     * @return Iterator
251
     */
252
    protected function convertToUtf8($input_encoding, Iterator $iterator)
253
    {
254
        if (stripos($input_encoding, 'UTF-8') !== false) {
255
            return $iterator;
256
        }
257
258
        $convert_cell = function ($value) use ($input_encoding) {
259
            return mb_convert_encoding($value, 'UTF-8', $input_encoding);
260
        };
261
262
        $convert_row = function (array $row) use ($convert_cell) {
263
            return array_map($convert_cell, $row);
264
        };
265
266
        return new MapIterator($iterator, $convert_row);
267
    }
268
}
269