Completed
Pull Request — master (#210)
by ignace nyamagana
03:05
created

Statement   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 5
dl 0
loc 278
ccs 89
cts 89
cp 1
rs 10
c 0
b 0
f 0

11 Methods

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