These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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\Exception\RuntimeException; |
||
21 | use LimitIterator; |
||
22 | |||
23 | /** |
||
24 | * A trait to manage filtering a CSV |
||
25 | * |
||
26 | * @package League.csv |
||
27 | * @since 9.0.0 |
||
28 | * @author Ignace Nyamagana Butera <[email protected]> |
||
29 | * |
||
30 | */ |
||
31 | class Statement |
||
32 | { |
||
33 | use ValidatorTrait; |
||
34 | |||
35 | /** |
||
36 | * CSV columns name |
||
37 | * |
||
38 | * @var array |
||
39 | */ |
||
40 | protected $columns = []; |
||
41 | |||
42 | /** |
||
43 | * Callables to filter the iterator |
||
44 | * |
||
45 | * @var callable[] |
||
46 | */ |
||
47 | protected $where = []; |
||
48 | |||
49 | /** |
||
50 | * Callables to sort the iterator |
||
51 | * |
||
52 | * @var callable[] |
||
53 | */ |
||
54 | protected $order_by = []; |
||
55 | |||
56 | /** |
||
57 | * iterator Offset |
||
58 | * |
||
59 | * @var int |
||
60 | */ |
||
61 | protected $offset = 0; |
||
62 | |||
63 | /** |
||
64 | * iterator maximum length |
||
65 | * |
||
66 | * @var int |
||
67 | */ |
||
68 | protected $limit = -1; |
||
69 | |||
70 | /** |
||
71 | * Set and selected columns to be used by the RecordSet object |
||
72 | * |
||
73 | * The array offset represents the CSV document header value |
||
74 | * The array value represents the Alias named to be used by the RecordSet object |
||
75 | * |
||
76 | * @param array $columns |
||
77 | * |
||
78 | * @return self |
||
79 | */ |
||
80 | 24 | public function columns(array $columns): self |
|
81 | { |
||
82 | 24 | $columns = $this->filterColumnNames($columns); |
|
83 | 22 | if ($columns === $this->columns) { |
|
84 | 2 | return $this; |
|
85 | } |
||
86 | |||
87 | 20 | $clone = clone $this; |
|
88 | 20 | $clone->columns = $columns; |
|
89 | |||
90 | 20 | return $clone; |
|
91 | } |
||
92 | |||
93 | /** |
||
94 | * Set the Iterator filter method |
||
95 | * |
||
96 | * @param callable $callable |
||
97 | * |
||
98 | * @return self |
||
99 | */ |
||
100 | 2 | public function where(callable $callable): self |
|
101 | { |
||
102 | 2 | $clone = clone $this; |
|
103 | 2 | $clone->where[] = $callable; |
|
104 | |||
105 | 2 | 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 LimitIterator Offset |
||
125 | * |
||
126 | * @param $offset |
||
127 | * |
||
128 | * @return self |
||
129 | */ |
||
130 | 22 | public function offset(int $offset): self |
|
131 | { |
||
132 | 22 | $offset = $this->filterInteger($offset, 0, __METHOD__.': the offset must be a positive integer or 0'); |
|
133 | 22 | if ($offset === $this->offset) { |
|
134 | 2 | return $this; |
|
135 | } |
||
136 | |||
137 | 20 | $clone = clone $this; |
|
138 | 20 | $clone->offset = $offset; |
|
139 | |||
140 | 20 | return $clone; |
|
141 | } |
||
142 | |||
143 | /** |
||
144 | * Set LimitIterator Count |
||
145 | * |
||
146 | * @param int $limit |
||
147 | * |
||
148 | * @return self |
||
149 | */ |
||
150 | 26 | public function limit(int $limit): self |
|
151 | { |
||
152 | 26 | $limit = $this->filterInteger($limit, -1, __METHOD__.': the limit must an integer greater or equals to -1'); |
|
153 | 24 | if ($limit === $this->limit) { |
|
154 | 2 | return $this; |
|
155 | } |
||
156 | |||
157 | 22 | $clone = clone $this; |
|
158 | 22 | $clone->limit = $limit; |
|
159 | |||
160 | 22 | return $clone; |
|
161 | } |
||
162 | |||
163 | /** |
||
164 | * Returns the inner CSV Document Iterator object |
||
165 | * |
||
166 | * @param Reader $reader |
||
167 | * |
||
168 | * @return RecordSet |
||
169 | */ |
||
170 | 114 | public function process(Reader $reader): RecordSet |
|
171 | { |
||
172 | 114 | list($columns, $combine) = $this->buildColumns($reader->getHeader()); |
|
173 | 110 | $iterator = $this->buildWhere($reader->getIterator()); |
|
0 ignored issues
–
show
|
|||
174 | 110 | $iterator = $this->buildOrderBy($iterator); |
|
175 | 110 | $iterator = new LimitIterator($iterator, $this->offset, $this->limit); |
|
176 | 110 | if (null !== $combine) { |
|
177 | 18 | $iterator = new MapIterator($iterator, $combine); |
|
178 | } |
||
179 | |||
180 | 110 | return new RecordSet($iterator, $columns); |
|
181 | } |
||
182 | |||
183 | /** |
||
184 | * Add the CSV column if present |
||
185 | * |
||
186 | * @param string[] $columns |
||
187 | * |
||
188 | * @return array |
||
189 | */ |
||
190 | 112 | protected function buildColumns(array $columns): array |
|
191 | { |
||
192 | 112 | if (empty($this->columns)) { |
|
193 | 92 | return [$columns, null]; |
|
194 | } |
||
195 | |||
196 | 20 | $columns_alias = $this->filterColumnAgainstCsvHeader($columns); |
|
197 | 18 | $columns = array_values($columns_alias); |
|
198 | $combine = function (array $record) use ($columns_alias): array { |
||
199 | 10 | $res = []; |
|
200 | 10 | foreach ($columns_alias as $key => $alias) { |
|
201 | 10 | $res[$alias] = $record[$key] ?? null; |
|
202 | } |
||
203 | |||
204 | 10 | return $res; |
|
205 | 18 | }; |
|
206 | |||
207 | 18 | return [$columns, $combine]; |
|
208 | } |
||
209 | |||
210 | /** |
||
211 | * Validate the column against the processed CSV header |
||
212 | * |
||
213 | * @param string[] $headers Reader CSV header |
||
214 | * |
||
215 | * @throws RuntimeException If a column is not found |
||
216 | */ |
||
217 | 20 | protected function filterColumnAgainstCsvHeader(array $headers) |
|
218 | { |
||
219 | 20 | if (empty($headers)) { |
|
220 | $filter = function ($key): bool { |
||
221 | 18 | return !is_int($key) || $key < 0; |
|
222 | 9 | }; |
|
223 | |||
224 | 18 | if (empty(array_filter($this->columns, $filter, ARRAY_FILTER_USE_KEY))) { |
|
225 | 16 | return $this->columns; |
|
226 | } |
||
227 | |||
228 | 2 | throw new RuntimeException('If no header is specified the columns keys must contain only positive integer or 0'); |
|
229 | } |
||
230 | |||
231 | 2 | $columns = $this->formatColumns($this->columns); |
|
232 | 2 | foreach ($columns as $key => $alias) { |
|
233 | 2 | if (false === array_search($key, $headers, true)) { |
|
234 | 1 | throw new RuntimeException(sprintf('The `%s` column does not exist in the Csv document', $key)); |
|
235 | } |
||
236 | } |
||
237 | |||
238 | 2 | return $columns; |
|
239 | } |
||
240 | |||
241 | /** |
||
242 | * Format the column array |
||
243 | * |
||
244 | * @param array $columns |
||
245 | * |
||
246 | * @return array |
||
247 | */ |
||
248 | 2 | private function formatColumns(array $columns): array |
|
249 | { |
||
250 | 2 | $res = []; |
|
251 | 2 | foreach ($columns as $key => $alias) { |
|
252 | 2 | $res[!is_string($key) ? $alias : $key] = $alias; |
|
253 | } |
||
254 | |||
255 | 2 | return $res; |
|
256 | } |
||
257 | |||
258 | /** |
||
259 | * Filter the Iterator |
||
260 | * |
||
261 | * @param Iterator $iterator |
||
262 | * |
||
263 | * @return Iterator |
||
264 | */ |
||
265 | 110 | protected function buildWhere(Iterator $iterator): Iterator |
|
266 | { |
||
267 | $reducer = function (Iterator $iterator, callable $callable): Iterator { |
||
268 | 2 | return new CallbackFilterIterator($iterator, $callable); |
|
269 | 55 | }; |
|
270 | |||
271 | 110 | return array_reduce($this->where, $reducer, $iterator); |
|
272 | } |
||
273 | |||
274 | /** |
||
275 | * Sort the Iterator |
||
276 | * |
||
277 | * @param Iterator $iterator |
||
278 | * |
||
279 | * @return Iterator |
||
280 | */ |
||
281 | 110 | protected function buildOrderBy(Iterator $iterator): Iterator |
|
282 | { |
||
283 | 110 | if (empty($this->order_by)) { |
|
284 | 108 | return $iterator; |
|
285 | } |
||
286 | |||
287 | 2 | $compare = function (array $record_a, array $record_b): int { |
|
288 | 2 | foreach ($this->order_by as $callable) { |
|
289 | 2 | if (0 !== ($cmp = $callable($record_a, $record_b))) { |
|
290 | 2 | return $cmp; |
|
291 | } |
||
292 | } |
||
293 | |||
294 | return $cmp ?? 0; |
||
295 | 1 | }; |
|
296 | |||
297 | 2 | $iterator = new ArrayIterator(iterator_to_array($iterator, true)); |
|
298 | 2 | $iterator->uasort($compare); |
|
299 | |||
300 | 2 | return $iterator; |
|
301 | } |
||
302 | } |
||
303 |
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.