Completed
Pull Request — master (#210)
by ignace nyamagana
14:30
created

Statement::filterColumnAgainstCsvHeader()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 5
cts 5
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
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
 * @author  Ignace Nyamagana Butera <[email protected]>
28
 *
29
 */
30
class Statement
31
{
32
    use ValidatorTrait;
33
34
    /**
35
     * CSV columns name
36
     *
37
     * @var array
38
     */
39
    protected $columns = [];
40
41
    /**
42
     * Callables to filter the iterator
43
     *
44
     * @var callable[]
45
     */
46
    protected $where = [];
47
48
    /**
49
     * Callables to sort the iterator
50
     *
51
     * @var callable[]
52
     */
53
    protected $order_by = [];
54
55
    /**
56
     * iterator Offset
57
     *
58
     * @var int
59
     */
60
    protected $offset = 0;
61
62
    /**
63
     * iterator maximum length
64
     *
65
     * @var int
66
     */
67
    protected $limit = -1;
68
69
    /**
70
     * Set and select the column to be used by the RecordSet object
71
     *
72
     * @param array $columns
73
     *
74
     * @return self
75
     */
76 18
    public function columns(array $columns): self
77
    {
78 18
        $columns = $this->filterColumnNames($columns);
79 16
        if ($columns === $this->columns) {
80 2
            return $this;
81
        }
82
83 14
        $clone = clone $this;
84 14
        $clone->columns = $columns;
85
86 14
        return $clone;
87
    }
88
89
    /**
90
     * Set the Iterator filter method
91
     *
92
     * @param callable $callable
93
     *
94
     * @return self
95
     */
96 2
    public function where(callable $callable): self
97
    {
98 2
        $clone = clone $this;
99 2
        $clone->where[] = $callable;
100
101 2
        return $clone;
102
    }
103
104
    /**
105
     * Set an Iterator sorting callable function
106
     *
107
     * @param callable $callable
108
     *
109
     * @return self
110
     */
111 2
    public function orderBy(callable $callable): self
112
    {
113 2
        $clone = clone $this;
114 2
        $clone->order_by[] = $callable;
115
116 2
        return $clone;
117
    }
118
119
    /**
120
     * Set LimitIterator Offset
121
     *
122
     * @param $offset
123
     *
124
     * @return self
125
     */
126 12
    public function offset(int $offset = 0): self
127
    {
128 12
        $offset = $this->filterInteger($offset, 0, 'the offset must be a positive integer or 0');
129 12
        if ($offset === $this->offset) {
130 2
            return $this;
131
        }
132
133 10
        $clone = clone $this;
134 10
        $clone->offset = $offset;
135
136 10
        return $clone;
137
    }
138
139
    /**
140
     * Set LimitIterator Count
141
     *
142
     * @param int $limit
143
     *
144
     * @return self
145
     */
146 16
    public function limit(int $limit = -1): self
147
    {
148 16
        $limit = $this->filterInteger($limit, -1, 'the limit must an integer greater or equals to -1');
149 14
        if ($limit === $this->limit) {
150 2
            return $this;
151
        }
152
153 12
        $clone = clone $this;
154 12
        $clone->limit = $limit;
155
156 12
        return $clone;
157
    }
158
159
    /**
160
     * Returns the inner CSV Document Iterator object
161
     *
162
     * @param Reader $reader
163
     *
164
     * @return RecordSet
165
     */
166 108
    public function process(Reader $reader): RecordSet
167
    {
168 108
        list($columns, $iterator) = $this->buildColumns($reader);
169 106
        $iterator = $this->buildWhere($iterator);
170 106
        $iterator = $this->buildOrderBy($iterator);
171
172 106
        return new RecordSet(new LimitIterator($iterator, $this->offset, $this->limit), $columns);
173
    }
174
175
    /**
176
     * Add the CSV column if present
177
     *
178
     * @param Reader $reader
179
     *
180
     * @return array
181
     */
182 108
    protected function buildColumns(Reader $reader): array
183
    {
184 108
        $header = $reader->getHeader();
185 106
        $iterator = $reader->getIterator();
186 106
        if (empty($this->columns)) {
187 92
            return [$header, $iterator];
188
        }
189
190 14
        $columns = $this->filterColumnAgainstCsvHeader($header);
191 14
        $combine = function (array $row) use ($columns): array {
192 2
            $record = [];
193 2
            foreach ($columns as $key => $alias) {
194
                $record[$alias] = $row[$key] ?? null;
195
            }
196
197 10
            return $record;
198 10
        };
199 10
200
        return [array_values($columns), new MapIterator($iterator, $combine)];
0 ignored issues
show
Compatibility introduced by
$iterator of type object<Traversable> is not a sub-type of object<Iterator>. It seems like you assume a child interface of the interface Traversable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
201
    }
202 10
203 14
    /**
204
     * Format the column array
205 14
     *
206
     * @param array $columns
207
     *
208
     * @return array
209
     */
210
    private function formatColumns(array $columns): array
211
    {
212
        $res = [];
213
        foreach ($columns as $key => $alias) {
214
            $res[!is_string($key) ? $alias : $key] = $alias;
215 2
        }
216
217 2
        return $res;
218 2
    }
219 2
220
    /**
221
     * Validate the column against the processed CSV header
222 2
     *
223
     * @param array    $columns Statement CSV columns
0 ignored issues
show
Bug introduced by
There is no parameter named $columns. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
224
     * @param string[] $headers Reader CSV header
225
     *
226
     * @throws Exception If a column is not found
227
     */
228
    protected function filterColumnAgainstCsvHeader(array $headers)
229
    {
230
        if (empty($headers)) {
231
            return $this->columns;
232
        }
233 2
234
        $columns = $this->formatColumns($this->columns);
235 2
        foreach ($columns as $key => $alias) {
236 2
            if (false === array_search($key, $headers, true)) {
237 2
                throw new Exception(sprintf('The following column `%s` does not exist in the CSV document', $key));
238
            }
239
        }
240 2
241
        return $columns;
242
    }
243
244
    /**
245
    * Filter the Iterator
246
    *
247
    * @param Iterator $iterator
248
    *
249 106
    * @return Iterator
250
    */
251
    protected function buildWhere(Iterator $iterator): Iterator
252 2
    {
253 106
        $reducer = function (Iterator $iterator, callable $callable): Iterator {
254
            return new CallbackFilterIterator($iterator, $callable);
255 106
        };
256
257
        return array_reduce($this->where, $reducer, $iterator);
258
    }
259
260
    /**
261
    * Sort the Iterator
262
    *
263
    * @param Iterator $iterator
264
    *
265 106
    * @return Iterator
266
    */
267 106
    protected function buildOrderBy(Iterator $iterator): Iterator
268 104
    {
269
        if (empty($this->order_by)) {
270
            return $iterator;
271 2
        }
272 2
273 2
        $obj = new ArrayIterator(iterator_to_array($iterator, true));
274 2
        $obj->uasort(function (array $record_a, array $record_b): int {
275 2
            $res = 0;
276 2
            foreach ($this->order_by as $compare) {
277
                if (0 !== ($res = $compare($record_a, $record_b))) {
278
                    break;
279
                }
280 2
            }
281 2
282
            return $res;
283 2
        });
284
285
        return $obj;
286
    }
287
}
288