Completed
Pull Request — master (#207)
by ignace nyamagana
09:14
created

StatementTrait::applyIteratorInterval()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 6
nc 1
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 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\Config;
16
17
use ArrayIterator;
18
use CallbackFilterIterator;
19
use Iterator;
20
use League\Csv\MapIterator;
21
use League\Csv\StreamIterator;
22
use LimitIterator;
23
use SplFileObject;
24
25
/**
26
 *  A trait to manage filtering a CSV
27
 *
28
 * @package League.csv
29
 * @since  9.0.0
30
 *
31
 */
32
trait StatementTrait
33
{
34
    use ValidatorTrait;
35
36
    /**
37
     * Callables to filter the iterator
38
     *
39
     * @var callable[]
40
     */
41
    protected $iterator_filters = [];
42
43
    /**
44
     * Callables to sort the iterator
45
     *
46
     * @var callable[]
47
     */
48
    protected $iterator_sort_by = [];
49
50
    /**
51
     * iterator Offset
52
     *
53
     * @var int
54
     */
55
    protected $iterator_offset = 0;
56
57
    /**
58
     * iterator maximum length
59
     *
60
     * @var int
61
     */
62
    protected $iterator_limit = -1;
63
64
    /**
65
     * Set LimitIterator Offset
66
     *
67
     * @param $offset
68
     *
69
     * @return $this
70
     */
71
    public function setOffset(int $offset = 0): self
72
    {
73
        $this->iterator_offset = $this->filterInteger($offset, 0, 'the offset must be a positive integer or 0');
74
75
        return $this;
76
    }
77
78
    /**
79
     * Set LimitIterator Count
80
     *
81
     * @param int $limit
82
     *
83
     * @return $this
84
     */
85
    public function setLimit(int $limit = -1): self
86
    {
87
        $this->iterator_limit = $this->filterInteger($limit, -1, 'the limit must an integer greater or equals to -1');
88
89
        return $this;
90
    }
91
92
    /**
93
     * Set an Iterator sorting callable function
94
     *
95
     * @param callable $callable
96
     *
97
     * @return $this
98
     */
99
    public function addSortBy(callable $callable): self
100
    {
101
        $this->iterator_sort_by[] = $callable;
102
103
        return $this;
104
    }
105
106
    /**
107
     * Set the Iterator filter method
108
     *
109
     * @param callable $callable
110
     *
111
     * @return $this
112
     */
113
    public function addFilter(callable $callable): self
114
    {
115
        $this->iterator_filters[] = $callable;
116
117
        return $this;
118
    }
119
120
    /**
121
     * Returns the inner CSV Document Iterator object
122
     *
123
     * @return Iterator
124
     */
125
    public function getIterator()
126
    {
127
        $iterator = $this->getCsvDocument();
128
        $iterator->setCsvControl($this->getDelimiter(), $this->getEnclosure(), $this->getEscape());
129
        $iterator->setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY);
130
        $iterator = $this->applyBomStripping($iterator);
131
        $iterator = $this->applyHeader($iterator);
132
        $iterator = $this->applyFilter($iterator);
133
        $iterator = $this->applySortBy($iterator);
134
135
        return $this->applyIteratorInterval($iterator);
136
    }
137
138
    /**
139
     * Returns the current field delimiter
140
     *
141
     * @return string
142
     */
143
    abstract public function getDelimiter(): string;
144
145
    /**
146
     * Returns the current field enclosure
147
     *
148
     * @return string
149
     */
150
    abstract public function getEnclosure(): string;
151
152
    /**
153
     * Returns the current field escape character
154
     *
155
     * @return string
156
     */
157
    abstract public function getEscape(): string;
158
159
    /**
160
     * Returns the inner CSV Document Iterator object
161
     *
162
     * @return StreamIterator|SplFileObject
163
     */
164
    abstract public function getCsvDocument();
165
166
    /**
167
     * Returns the BOM sequence of the given CSV
168
     *
169
     * @return string
170
     */
171
    abstract public function getInputBOM(): string;
172
173
    /**
174
     * Remove the BOM sequence from the CSV
175
     *
176
     * @param Iterator $iterator
177
     *
178
     * @return Iterator
179
     */
180
    protected function applyBomStripping(Iterator $iterator): Iterator
181
    {
182
        $bom = $this->getInputBOM();
183
        if ('' == $bom) {
184
            return $iterator;
185
        }
186
187
        $bom_length = mb_strlen($bom);
188
        $enclosure = $this->getEnclosure();
189
        $strip_bom = function ($row, $index) use ($bom_length, $enclosure) {
190
            if (0 != $index || !is_array($row)) {
191
                return $row;
192
            }
193
194
            return $this->removeBOM($row, $bom_length, $enclosure);
195
        };
196
197
        return new MapIterator($iterator, $strip_bom);
198
    }
199
200
    /**
201
     * Returns the record offset used as header
202
     *
203
     * If no CSV record is used this method MUST return null
204
     *
205
     * @return int|null
206
     */
207
    abstract public function getHeaderOffset();
208
209
    /**
210
     * Returns the header
211
     *
212
     * If no CSV record is used this method MUST return an empty array
213
     *
214
     * @return string[]
215
     */
216
    abstract public function getHeader(): array;
217
218
    /**
219
     * Add the CSV header if present
220
     *
221
     * @param Iterator $iterator
222
     *
223
     * @return Iterator
224
     */
225
    public function applyHeader(Iterator $iterator): Iterator
226
    {
227
        $header = $this->getHeader();
228
        if (empty($header)) {
229
            return $iterator;
230
        }
231
232
        $header_count = count($header);
233
        $combine = function (array $row) use ($header, $header_count) {
234
            if ($header_count != count($row)) {
235
                $row = array_slice(array_pad($row, $header_count, null), 0, $header_count);
236
            }
237
238
            return array_combine($header, $row);
239
        };
240
241
        return new MapIterator($iterator, $combine);
242
    }
243
244
    /**
245
    * Filter the Iterator
246
    *
247
    * @param Iterator $iterator
248
    *
249
    * @return Iterator
250
    */
251
    protected function applyFilter(Iterator $iterator): Iterator
252
    {
253
        $header_offset = $this->getHeaderOffset();
254
        if (null !== $header_offset) {
255
            $strip_header = function ($row, $index) use ($header_offset) {
256
                return $index !== $header_offset;
257
            };
258
            array_unshift($this->iterator_filters, $strip_header);
259
        }
260
261
        $normalized_csv = function ($row) {
262
            return is_array($row) && $row != [null];
263
        };
264
        array_unshift($this->iterator_filters, $normalized_csv);
265
266
        $reducer = function ($iterator, $callable) {
267
            return new CallbackFilterIterator($iterator, $callable);
268
        };
269
        $iterator = array_reduce($this->iterator_filters, $reducer, $iterator);
270
        $this->iterator_filters = [];
271
272
        return $iterator;
273
    }
274
275
    /**
276
    * Sort the Iterator
277
    *
278
    * @param Iterator $iterator
279
    *
280
    * @return Iterator
281
    */
282
    protected function applySortBy(Iterator $iterator): Iterator
283
    {
284
        if (empty($this->iterator_sort_by)) {
285
            return $iterator;
286
        }
287
288
        $obj = new ArrayIterator(iterator_to_array($iterator));
289
        $obj->uasort(function ($row_a, $row_b) {
290
            $res = 0;
291
            foreach ($this->iterator_sort_by as $compare) {
292
                if (0 !== ($res = ($compare)($row_a, $row_b))) {
293
                    break;
294
                }
295
            }
296
297
            return $res;
298
        });
299
        $this->iterator_sort_by = [];
300
301
        return $obj;
302
    }
303
304
    /**
305
    * Sort the Iterator
306
    *
307
    * @param Iterator $iterator
308
    *
309
    * @return Iterator
310
    */
311
    protected function applyIteratorInterval(Iterator $iterator): Iterator
312
    {
313
        $offset = $this->iterator_offset;
314
        $limit = $this->iterator_limit;
315
        $this->iterator_limit = -1;
316
        $this->iterator_offset = 0;
317
318
        return new LimitIterator($iterator, $offset, $limit);
319
    }
320
}
321