Completed
Push — master ( 89f242...927176 )
by ignace nyamagana
02:39
created

src/Statement.php (1 issue)

Upgrade to new PHP Analysis Engine

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
$reader->getIterator() 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...
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