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

Statement::sort()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
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 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
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 Statement class to fetch rows against a Csv file object
24
 *
25
 * @package League.csv
26
 * @since  9.0.0
27
 *
28
 */
29
class Statement
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
     * @inheritdoc
63
     */
64
    public function __set($property, $value)
65
    {
66
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
67
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72
    public function __unset($property)
73
    {
74
        throw new InvalidArgumentException(sprintf('%s is an undefined property', $property));
75
    }
76
77
    /**
78
     * Returns a Record object
79
     *
80
     * @param Reader $csv
81
     *
82
     * @return RecordSet
83
     */
84
    public function process(Reader $csv)
85
    {
86
        $input_encoding = $csv->getInputEncoding();
87
        $use_converter = $this->useInternalConverter($csv);
88
        $header = $csv->getHeader();
89
        $header_offset = $csv->getHeaderOffset();
90
        $filters = [];
91
        if (null !== $header_offset) {
92
            $filters[] = function ($row, $index) use ($header_offset) {
93
                return $index !== $header_offset;
94
            };
95
        }
96
97
        $iterator = $this->format($csv, $header);
98
        $iterator = $this->convert($iterator, $input_encoding, $use_converter);
99
        $iterator = $this->filter($iterator, $filters);
100
        $iterator = $this->sort($iterator);
101
102
        return new RecordSet(new LimitIterator($iterator, $this->offset, $this->limit), $header);
103
    }
104
105
    /**
106
     * Prepare the csv for manipulation
107
     *
108
     * - remove the BOM sequence if present
109
     * - attach the header to the records if present
110
     *
111
     * @param Reader $csv
112
     *
113
     * @throws InvalidRowException if the column is inconsistent
114
     *
115
     * @return Iterator
116
     */
117
    protected function format(Reader $csv, array $header)
118
    {
119
        $iterator = $this->removeBOM($csv);
120
        if (empty($header)) {
121
            return $iterator;
122
        }
123
124
        $header_column_count = count($header);
125
        $combine_array = function (array $row) use ($header, $header_column_count) {
126
            if ($header_column_count != count($row)) {
127
                throw new InvalidRowException('csv_consistency', $row, 'The record and header column count differ');
128
            }
129
130
            return array_combine($header, $row);
131
        };
132
133
        return new MapIterator($iterator, $combine_array);
134
    }
135
136
    /**
137
     * Remove the BOM sequence from the CSV
138
     *
139
     * @param Reader $csv
140
     *
141
     * @return Iterator
142
     */
143
    protected function removeBOM(Reader $csv)
144
    {
145
        $bom = $csv->getInputBOM();
146
        if ('' === $bom) {
147
            return $csv->getIterator();
148
        }
149
150
        $enclosure = $csv->getEnclosure();
151
        $strip_bom = function ($row, $index) use ($bom, $enclosure) {
152
            if (0 != $index) {
153
                return $row;
154
            }
155
156
            return $this->stripBOM($row, $bom, $enclosure);
157
        };
158
159
        return new MapIterator($csv->getIterator(), $strip_bom);
160
    }
161
162
    /**
163
    * Filter the Iterator
164
    *
165
    * @param Iterator $iterator
166
    *
167
    * @return Iterator
168
    */
169
    protected function filter(Iterator $iterator, array $filters)
170
    {
171
        $reducer = function ($iterator, $callable) {
172
            return new CallbackFilterIterator($iterator, $callable);
173
        };
174
175
        $filters[] = function ($row) {
176
            return is_array($row) && $row != [null];
177
        };
178
179
        return array_reduce(array_merge($filters, $this->filters), $reducer, $iterator);
180
    }
181
182
    /**
183
    * Sort the Iterator
184
    *
185
    * @param Iterator $iterator
186
    *
187
    * @return Iterator
188
    */
189
    protected function sort(Iterator $iterator)
190
    {
191
        if (empty($this->sort_by)) {
192
            return $iterator;
193
        }
194
195
        $obj = new ArrayIterator(iterator_to_array($iterator));
196
        $obj->uasort(function ($row_a, $row_b) {
197
            $res = 0;
198
            foreach ($this->sort_by as $compare) {
199
                if (0 !== ($res = call_user_func($compare, $row_a, $row_b))) {
200
                    break;
201
                }
202
            }
203
204
            return $res;
205
        });
206
207
        return $obj;
208
    }
209
210
    /**
211
     * Convert Iterator to UTF-8
212
     *
213
     * @param Iterator $iterator
214
     * @param string   $input_encoding
215
     * @param bool     $use_converter
216
     *
217
     * @return Iterator
218
     */
219
    protected function convert(Iterator $iterator, $input_encoding, $use_converter)
220
    {
221
        if (!$use_converter) {
222
            return $iterator;
223
        }
224
225
        $convert_row = function ($row) use ($input_encoding) {
226
            return $this->convertRecordToUtf8($row, $input_encoding);
227
        };
228
229
        return new MapIterator($iterator, $convert_row);
230
    }
231
232
    /**
233
     * Set LimitIterator Offset
234
     *
235
     * @param $offset
236
     *
237
     * @return static
238
     */
239
    public function setOffset($offset)
240
    {
241
        $offset = $this->validateInteger($offset, 0, 'the offset must be a positive integer or 0');
242
        if ($offset === $this->offset) {
243
            return $this;
244
        }
245
246
        $clone = clone $this;
247
        $clone->offset = $offset;
248
249
        return $clone;
250
    }
251
252
    /**
253
     * Set LimitIterator Count
254
     *
255
     * @param int $limit
256
     *
257
     * @return static
258
     */
259
    public function setLimit($limit)
260
    {
261
        $limit = $this->validateInteger($limit, -1, 'the limit must an integer greater or equals to -1');
262
        if ($limit === $this->limit) {
263
            return $this;
264
        }
265
266
        $clone = clone $this;
267
        $clone->limit = $limit;
268
269
        return $clone;
270
    }
271
272
    /**
273
     * Set an Iterator sorting callable function
274
     *
275
     * @param callable $callable
276
     *
277
     * @return static
278
     */
279
    public function addSortBy(callable $callable)
280
    {
281
        $clone = clone $this;
282
        $clone->sort_by[] = $callable;
283
284
        return $clone;
285
    }
286
287
    /**
288
     * Set the Iterator filter method
289
     *
290
     * @param callable $callable
291
     *
292
     * @return static
293
     */
294
    public function addFilter(callable $callable)
295
    {
296
        $clone = clone $this;
297
        $clone->filters[] = $callable;
298
299
        return $clone;
300
    }
301
}
302