Completed
Push — master ( 5bc6de...8096c4 )
by ignace nyamagana
11:00
created

Reader::fetchPairsWithoutDuplicates()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 3
crap 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 8.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 Generator;
16
use InvalidArgumentException;
17
use Iterator;
18
use League\Csv\Modifier\MapIterator;
19
use LimitIterator;
20
use SplFileObject;
21
22
/**
23
 *  A class to manage extracting and filtering a CSV
24
 *
25
 * @package League.csv
26
 * @since  3.0.0
27
 *
28
 */
29
class Reader extends AbstractCsv
30
{
31
    /**
32
     * @inheritdoc
33
     */
34
    protected $stream_filter_mode = STREAM_FILTER_READ;
35
36
    /**
37
     * Return a Filtered Iterator
38
     *
39
     * @param callable|null $callable a callable function to be applied to each Iterator item
40
     *
41
     * @return Iterator
42
     */
43 159
    public function fetch(callable $callable = null)
44
    {
45 159
        return $this->applyCallable($this->getQueryIterator(), $callable);
46
    }
47
48
    /**
49
     * Apply The callable function
50
     *
51
     * @param Iterator      $iterator
52
     * @param callable|null $callable
53
     *
54
     * @return Iterator
55
     */
56 159
    protected function applyCallable(Iterator $iterator, callable $callable = null)
57
    {
58 159
        if (null !== $callable) {
59 75
            return new MapIterator($iterator, $callable);
60
        }
61
62 144
        return $iterator;
63
    }
64
65
    /**
66
     * Returns a sequential array of all CSV lines
67
     *
68
     * The callable function will be applied to each Iterator item
69
     *
70
     * @param callable|null $callable a callable function
71
     *
72
     * @return array
73
     */
74 63
    public function fetchAll(callable $callable = null)
75
    {
76 63
        return iterator_to_array($this->fetch($callable), false);
77
    }
78
79
    /**
80
     * Applies a callback function on the CSV
81
     *
82
     * The callback function must return TRUE in order to continue
83
     * iterating over the iterator.
84
     *
85
     * @param callable $callable a callable function to apply to each selected CSV rows
86
     *
87
     * @return int the iteration count
88
     */
89 9
    public function each(callable $callable)
90
    {
91 9
        $index = 0;
92 9
        $iterator = $this->fetch();
93 9
        $iterator->rewind();
94 9
        while ($iterator->valid() && true === call_user_func(
95 6
            $callable,
96 9
            $iterator->current(),
97 9
            $iterator->key(),
98
            $iterator
99 6
        )) {
100 9
            ++$index;
101 9
            $iterator->next();
102 6
        }
103
104 9
        return $index;
105
    }
106
107
    /**
108
     * Returns a single row from the CSV
109
     *
110
     * By default if no offset is provided the first row of the CSV is selected
111
     *
112
     * @param int $offset the CSV row offset
113
     *
114
     * @return array
115
     */
116 12
    public function fetchOne($offset = 0)
117
    {
118 12
        $this->setOffset($offset);
119 12
        $this->setLimit(1);
120 12
        $iterator = $this->fetch();
121 12
        $iterator->rewind();
122
123 12
        return (array) $iterator->current();
124
    }
125
126
    /**
127
     * Returns a single column from the CSV data
128
     *
129
     * The callable function will be applied to each value to be return
130
     *
131
     * By default if no column index is provided the first column of the CSV is selected
132
     *
133
     * @param int           $columnIndex CSV column index
134
     * @param callable|null $callable    A callable to be applied to each of the value to be returned.
135
     *
136
     * @return Iterator
137
     */
138 21
    public function fetchColumn($columnIndex = 0, callable $callable = null)
139
    {
140 21
        $columnIndex = $this->validateInteger($columnIndex, 0, 'the column index must be a positive integer or 0');
141
142
        $filterColumn = function ($row) use ($columnIndex) {
143 18
            return isset($row[$columnIndex]);
144 18
        };
145
146
        $selectColumn = function ($row) use ($columnIndex) {
147 15
            return $row[$columnIndex];
148 18
        };
149
150 18
        $this->addFilter($filterColumn);
151 18
        $iterator = $this->fetch($selectColumn);
152 18
        $iterator = $this->applyCallable($iterator, $callable);
153 18
154
        return $iterator;
155 18
    }
156
157
    /**
158
     * Retrive CSV data as pairs
159
     *
160
     * Fetches an associative array of all rows as key-value pairs (first
161
     * column is the key, second column is the value).
162
     *
163
     * By default if no column index is provided:
164
     * - the first CSV column is used to provide the keys
165
     * - the second CSV column is used to provide the value
166
     *
167
     * @param int           $offsetIndex The column index to serve as offset
168
     * @param int           $valueIndex  The column index to serve as value
169
     * @param callable|null $callable    A callable to be applied to each of the rows to be returned.
170
     *
171
     * @return Generator
172
     */
173
    public function fetchPairs($offsetIndex = 0, $valueIndex = 1, callable $callable = null)
174 27
    {
175
        $offsetIndex = $this->validateInteger($offsetIndex, 0, 'the offset column index must be a positive integer or 0');
176 27
        $valueIndex = $this->validateInteger($valueIndex, 0, 'the value column index must be a positive integer or 0');
177 27
        $filterPairs = function ($row) use ($offsetIndex, $valueIndex) {
178
            return isset($row[$offsetIndex]);
179 27
        };
180 27
        $selectPairs = function ($row) use ($offsetIndex, $valueIndex) {
181
            return [
182
                $row[$offsetIndex],
183 24
                isset($row[$valueIndex]) ? $row[$valueIndex] : null,
184 24
            ];
185 16
        };
186 27
187
        $this->addFilter($filterPairs);
188 27
        $iterator = $this->fetch($selectPairs);
189 27
        $iterator = $this->applyCallable($iterator, $callable);
190 27
191 27
        return $this->generatePairs($iterator);
192
    }
193 27
194
    /**
195
     * Return the key/pairs as a PHP generator
196
     *
197
     * @param Iterator $iterator
198
     *
199
     * @return Generator
200
     */
201
    protected function generatePairs(Iterator $iterator)
202
    {
203 27
        foreach ($iterator as $row) {
204
            yield $row[0] => $row[1];
205 27
        }
206 24
    }
207 18
208 27
    /**
209
     * Retrive CSV data as pairs
210
     *
211
     * Fetches an associative array of all rows as key-value pairs (first
212
     * column is the key, second column is the value).
213
     *
214
     * By default if no column index is provided:
215
     * - the first CSV column is used to provide the keys
216
     * - the second CSV column is used to provide the value
217
     *
218
     * @param int           $offsetIndex The column index to serve as offset
219
     * @param int           $valueIndex  The column index to serve as value
220
     * @param callable|null $callable    A callable to be applied to each of the rows to be returned.
221
     *
222
     * @return array
223
     */
224
    public function fetchPairsWithoutDuplicates($offsetIndex = 0, $valueIndex = 1, callable $callable = null)
225 39
    {
226
        return iterator_to_array($this->fetchPairs($offsetIndex, $valueIndex, $callable), true);
227 39
    }
228 27
229
    /**
230 27
     * Returns a sequential array of all CSV lines;
231 6
     *
232 4
     * The rows are presented as associated arrays
233
     * The callable function will be applied to each Iterator item
234 27
     *
235 27
     * @param int|array $offset_or_keys the name for each key member OR the row Index to be
236
     *                                  used as the associated named keys
237 27
     *
238 27
     * @param callable $callable A callable to be applied to each of the rows to be returned.
239 27
     *
240
     * @throws InvalidArgumentException If the submitted keys are invalid
241 27
     *
242
     * @return Iterator|array
243
     */
244
    public function fetchAssoc($offset_or_keys = 0, callable $callable = null)
245
    {
246
        $keys = $this->getAssocKeys($offset_or_keys);
247
        $keys_count = count($keys);
248
        $combineArray = function (array $row) use ($keys, $keys_count) {
249
            if ($keys_count != count($row)) {
250
                $row = array_slice(array_pad($row, $keys_count, null), 0, $keys_count);
251
            }
252
253
            return array_combine($keys, $row);
254 39
        };
255
256 39
        $iterator = $this->fetch($combineArray);
257 21
        $iterator = $this->applyCallable($iterator, $callable);
258
259
        return $iterator;
260 18
    }
261 12
262 18
    /**
263 6
     * Selects the array to be used as key for the fetchAssoc method
264 12
     *
265 15
     * @param int|array $offset_or_keys the assoc key OR the row Index to be used
266 12
     *                                  as the key index
267 12
     *
268 12
     * @throws InvalidArgumentException If the row index and/or the resulting array is invalid
269 12
     *
270
     * @return array
271 12
     */
272
    protected function getAssocKeys($offset_or_keys)
273
    {
274
        if (is_array($offset_or_keys)) {
275
            return $this->validateKeys($offset_or_keys);
276
        }
277
278
        $offset_or_keys = $this->validateInteger(
279
            $offset_or_keys,
280
            0,
281
            'the row index must be a positive integer, 0 or a non empty array'
282
        );
283 33
        $keys = $this->validateKeys($this->getRow($offset_or_keys));
284
        $filterOutRow = function ($row, $rowIndex) use ($offset_or_keys) {
285 33
            return $rowIndex != $offset_or_keys;
286 6
        };
287
        $this->addFilter($filterOutRow);
288
289 27
        return $keys;
290
    }
291
292
    /**
293
     * Validates the array to be used by the fetchAssoc method
294
     *
295
     * @param array $keys
296
     *
297
     * @throws InvalidArgumentException If the submitted array fails the assertion
298
     *
299 30
     * @return array
300
     */
301 30
    protected function validateKeys(array $keys)
302
    {
303
        if (empty($keys) || $keys !== array_unique(array_filter($keys, [$this, 'isValidKey']))) {
304
            throw new InvalidArgumentException('Use a flat array with unique string values');
305
        }
306
307
        return $keys;
308
    }
309
310
    /**
311
     * Returns whether the submitted value can be used as string
312
     *
313 15
     * @param mixed $value
314
     *
315 15
     * @return bool
316 15
     */
317 15
    protected function isValidKey($value)
318 15
    {
319 15
        return is_scalar($value) || (is_object($value) && method_exists($value, '__toString'));
320
    }
321 15
322 3
    /**
323
     * Returns a single row from the CSV without filtering
324
     *
325 12
     * @param int $offset
326 6
     *
327 4
     * @throws InvalidArgumentException If the $offset is not valid or the row does not exist
328
     *
329 12
     * @return array
330
     */
331
    protected function getRow($offset)
332
    {
333
        $fileObj = $this->getIterator();
334
        $fileObj->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY);
335
        $iterator = new LimitIterator($fileObj, $offset, 1);
336
        $iterator->rewind();
337
        $line = $iterator->current();
338
339
        if (empty($line)) {
340
            throw new InvalidArgumentException('the specified row does not exist or is empty');
341
        }
342
343
        if (0 === $offset && $this->isBomStrippable()) {
344
            $line = mb_substr($line, mb_strlen($this->getInputBom()));
345
        }
346
347
        return str_getcsv($line, $this->delimiter, $this->enclosure, $this->escape);
348
    }
349
}
350