Completed
Pull Request — master (#210)
by ignace nyamagana
06:59
created

Statement::orderRecords()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 2
nop 1
crap 4
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 9.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
declare(strict_types=1);
14
15
namespace League\Csv;
16
17
use ArrayIterator;
18
use CallbackFilterIterator;
19
use Iterator;
20
use LimitIterator;
21
22
/**
23
 *  A trait to manage filtering a CSV
24
 *
25
 * @package League.csv
26
 * @since  9.0.0
27
 *
28
 */
29
class Statement
30
{
31
    use ValidatorTrait;
32
33
    /**
34
     * Callables to filter the iterator
35
     *
36
     * @var callable[]
37
     */
38
    protected $where = [];
39
40
    /**
41
     * Callables to sort the iterator
42
     *
43
     * @var callable[]
44
     */
45
    protected $order_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
     * CSV headers
63
     *
64
     * @var string[]
65
     */
66
    protected $header = [];
67
68
    /**
69
     * Set LimitIterator Offset
70
     *
71
     * @param $offset
72
     *
73
     * @return self
74
     */
75 12
    public function offset(int $offset = 0): self
76
    {
77 12
        $offset = $this->filterInteger($offset, 0, 'the offset must be a positive integer or 0');
78 12
        if ($offset === $this->offset) {
79 2
            return $this;
80
        }
81
82 10
        $clone = clone $this;
83 10
        $clone->offset = $offset;
84
85 10
        return $clone;
86
    }
87
88
    /**
89
     * Set LimitIterator Count
90
     *
91
     * @param int $limit
92
     *
93
     * @return self
94
     */
95 16
    public function limit(int $limit = -1): self
96
    {
97 16
        $limit = $this->filterInteger($limit, -1, 'the limit must an integer greater or equals to -1');
98 14
        if ($limit === $this->limit) {
99 2
            return $this;
100
        }
101
102 12
        $clone = clone $this;
103 12
        $clone->limit = $limit;
104
105 12
        return $clone;
106
    }
107
108
    /**
109
     * Set an Iterator sorting callable function
110
     *
111
     * @param callable $callable
112
     *
113
     * @return self
114
     */
115 2
    public function orderBy(callable $callable): self
116
    {
117 2
        $clone = clone $this;
118 2
        $clone->order_by[] = $callable;
119
120 2
        return $clone;
121
    }
122
123
    /**
124
     * Set the Iterator filter method
125
     *
126
     * @param callable $callable
127
     *
128
     * @return self
129
     */
130 2
    public function where(callable $callable): self
131
    {
132 2
        $clone = clone $this;
133 2
        $clone->where[] = $callable;
134
135 2
        return $clone;
136
    }
137
138
    /**
139
     * Set the headers to be used by the RecordSet object
140
     *
141
     * @param string[] $header
142
     *
143
     * @return self
144
     */
145 18
    public function header(array $header): self
146
    {
147 18
        $header = $this->filterHeader($header);
148 16
        if ($header === $this->header) {
149 2
            return $this;
150
        }
151
152 14
        $clone = clone $this;
153 14
        $clone->header = $header;
154
155 14
        return $clone;
156
    }
157
158
    /**
159
     * Returns the inner CSV Document Iterator object
160
     *
161
     * @return RecordSet
162
     */
163 108
    public function process(Reader $reader): RecordSet
164
    {
165 108
        $header = $this->header;
166 108
        if (empty($header)) {
167 94
            $header = $this->computeHeader($reader);
168
        }
169
170
        $normalized = function ($row) {
171 94
            return is_array($row) && $row != [null];
172 106
        };
173
174 106
        $iterator = $this->stripBOM(
175 106
            new CallbackFilterIterator($reader->getIterator(), $normalized),
176 106
            $reader->getInputBOM(),
177 106
            $reader->getEnclosure()
178
        );
179 106
        $iterator = $this->combineHeader($iterator, $header);
180 106
        $iterator = $this->filterRecords($iterator);
181 106
        $iterator = $this->orderRecords($iterator);
182
183 106
        return new RecordSet(new LimitIterator($iterator, $this->offset, $this->limit), $header);
184
    }
185
186
    /**
187
     * Set the computed RecordSet headers
188
     *
189
     * @param Reader $reader The CSV document Reader object
190
     *
191
     * @throws Exception If the header is not found
192
     *
193
     * @return string[]
194
     */
195 94
    protected function computeHeader(Reader $reader)
196
    {
197 94
        $header = $reader->getHeader();
198 92
        if (empty($header)) {
199 78
            return $header;
200
        }
201
202 14
        $header = $this->filterHeader($header);
203 14
        $offset = $reader->getHeaderOffset();
204
        array_unshift($this->where, function ($row, $index) use ($offset) {
205 12
            return $index !== $offset;
206 14
        });
207
208 14
        return $header;
209
    }
210
211
    /**
212
     * Remove the BOM sequence from the CSV
213
     *
214
     * @param Iterator $iterator
215
     *
216
     * @return Iterator
217
     */
218 106
    protected function stripBOM(Iterator $iterator, string $bom, string $enclosure): Iterator
219
    {
220 106
        if ('' == $bom) {
221 88
            return $iterator;
222
        }
223
224 18
        $bom_length = mb_strlen($bom);
225
        $strip_bom = function ($row, $index) use ($bom_length, $enclosure) {
226 18
            if (0 != $index || !is_array($row)) {
227 14
                return $row;
228
            }
229
230 18
            return $this->removeBOM($row, $bom_length, $enclosure);
231 18
        };
232
233 18
        return new MapIterator($iterator, $strip_bom);
234
    }
235
236
    /**
237
     * Add the CSV header if present
238
     *
239
     * @param Iterator $iterator
240
     * @param string[] $header
241
     *
242
     * @return Iterator
243
     */
244 106
    protected function combineHeader(Iterator $iterator, array $header): Iterator
245
    {
246 106
        if (empty($header)) {
247 78
            return $iterator;
248
        }
249
250 28
        $header_count = count($header);
251
        $combine = function (array $row) use ($header_count, $header) {
252 22
            if ($header_count != count($row)) {
253 4
                $row = array_slice(array_pad($row, $header_count, null), 0, $header_count);
254
            }
255
256 22
            return array_combine($header, $row);
257 28
        };
258
259 28
        return new MapIterator($iterator, $combine);
260
    }
261
262
    /**
263
    * Filter the Iterator
264
    *
265
    * @param Iterator $iterator
266
    *
267
    * @return Iterator
268
    */
269 106
    protected function filterRecords(Iterator $iterator): Iterator
270
    {
271
        $reducer = function ($iterator, $callable) {
272 16
            return new CallbackFilterIterator($iterator, $callable);
273 106
        };
274
275 106
        return array_reduce($this->where, $reducer, $iterator);
276
    }
277
278
    /**
279
    * Sort the Iterator
280
    *
281
    * @param Iterator $iterator
282
    *
283
    * @return Iterator
284
    */
285 106
    protected function orderRecords(Iterator $iterator): Iterator
286
    {
287 106
        if (empty($this->order_by)) {
288 104
            return $iterator;
289
        }
290
291 2
        $obj = new ArrayIterator(iterator_to_array($iterator));
292 2
        $obj->uasort(function ($row_a, $row_b) {
293 2
            $res = 0;
294 2
            foreach ($this->order_by as $compare) {
295 2
                if (0 !== ($res = $compare($row_a, $row_b))) {
296 2
                    break;
297
                }
298
            }
299
300 2
            return $res;
301 2
        });
302
303 2
        return $obj;
304
    }
305
}
306