Completed
Push — master ( 4c829a...436317 )
by ignace nyamagana
07:33
created

ResultSet::getColumnIndexByKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 14
ccs 8
cts 8
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
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 CallbackFilterIterator;
18
use Countable;
19
use Generator;
20
use Iterator;
21
use IteratorAggregate;
22
use JsonSerializable;
23
use League\Csv\Exception\RuntimeException;
24
use LimitIterator;
25
26
/**
27
 * Represents the result set of a {@link Reader} processed by a {@link Statement}
28
 *
29
 * @package League.csv
30
 * @since   9.0.0
31
 * @author  Ignace Nyamagana Butera <[email protected]>
32
 *
33
 */
34
class ResultSet implements Countable, IteratorAggregate, JsonSerializable
35
{
36
    use ValidatorTrait;
37
38
    /**
39
     * The CSV records collection
40
     *
41
     * @var Iterator
42
     */
43
    protected $iterator;
44
45
    /**
46
     * The CSV records collection column names
47
     *
48
     * @var array
49
     */
50
    protected $column_names = [];
51
52
    /**
53
     * Tell whether the CSV records offset must be kept on output
54
     *
55
     * @var bool
56
     */
57
    protected $preserve_offset = false;
58
59
    /**
60
     * New instance
61
     *
62
     * @param Iterator $iterator     a CSV records collection iterator
63
     * @param array    $column_names the associated collection column names
64
     */
65 8
    public function __construct(Iterator $iterator, array $column_names)
66
    {
67 8
        $this->iterator = $iterator;
68 8
        $this->column_names = $column_names;
69 8
    }
70
71
    /**
72
     * @inheritdoc
73
     */
74 10
    public function __destruct()
75
    {
76 10
        $this->iterator = null;
77 10
    }
78
79
    /**
80
     * Returns the column names associated with the ResultSet
81
     *
82
     * @return string[]
83
     */
84 2
    public function getColumnNames(): array
85
    {
86 2
        return $this->column_names;
87
    }
88
89
    /**
90
     * Tell whether the CSV document record offset must be kept on output
91
     *
92
     * @return bool
93
     */
94 2
    public function isRecordOffsetPreserved(): bool
95
    {
96 2
        return $this->preserve_offset;
97
    }
98
99
    /**
100
     * @inheritdoc
101
     */
102 2
    public function getIterator(): Generator
103
    {
104 2
        return $this->iteratorToGenerator($this->iterator);
105
    }
106
107
    /**
108
     * Return the generator depending on the preserveRecordOffset setting
109
     *
110
     * @param Iterator $iterator
111
     *
112
     * @return Generator
113
     */
114 6
    protected function iteratorToGenerator(Iterator $iterator): Generator
115
    {
116 6
        if ($this->preserve_offset) {
117 4
            foreach ($iterator as $offset => $value) {
118 4
                yield $offset => $value;
119
            }
120 4
            return;
121
        }
122
123 2
        foreach ($iterator as $value) {
124 2
            yield $value;
125
        }
126 2
    }
127
128
    /**
129
     * @inheritdoc
130
     */
131 2
    public function count(): int
132
    {
133 2
        return iterator_count($this->iterator);
134
    }
135
136
    /**
137
     * Returns a sequential array of all CSV records found
138
     *
139
     * @return array
140
     */
141 8
    public function fetchAll(): array
142
    {
143 8
        return iterator_to_array($this->iterator, $this->preserve_offset);
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149 2
    public function jsonSerialize(): array
150
    {
151 2
        return $this->fetchAll();
152
    }
153
154
    /**
155
     * Returns a single record from the CSV
156
     *
157
     * By default if no offset is provided the first row of the CSV is selected
158
     *
159
     * @param int $offset the CSV record offset
160
     *
161
     * @return array
162
     */
163 4
    public function fetchOne(int $offset = 0): array
164
    {
165 4
        $iterator = new LimitIterator(
166 4
            $this->iterator,
167 4
            $this->filterMinRange($offset, 0, __METHOD__.'() expects the submitted offset to be a positive integer or 0, %s given'),
168 2
            1
169
        );
170
171 2
        $iterator->rewind();
172
173 2
        return (array) $iterator->current();
174
    }
175
176
    /**
177
     * Returns the next value from a single CSV record field
178
     *
179
     * By default if no column index is provided the first column of the CSV is selected
180
     *
181
     * @param string|int $index CSV column index
182
     *
183
     * @return Generator
184
     */
185 14
    public function fetchColumn($index = 0): Generator
186
    {
187 14
        $offset = $this->getColumnIndex($index, __METHOD__.'() expects the column index to be a valid string or integer, `%s` given');
188
        $filter = function (array $record) use ($offset): bool {
189 8
            return isset($record[$offset]);
190 8
        };
191
192
        $select = function (array $record) use ($offset): string {
193 6
            return $record[$offset];
194 8
        };
195
196 8
        $iterator = new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
197
198 8
        return $this->iteratorToGenerator($iterator);
199
    }
200
201
    /**
202
     * Filter a column name against the CSV header if any
203
     *
204
     * @param string|int $field         the field name or the field index
205
     * @param string     $error_message the associated error message
206
     *
207
     * @return string|int
208
     */
209 20
    protected function getColumnIndex($field, string $error_message)
210
    {
211 20
        $method = 'getColumnIndexByKey';
212 20
        if (is_string($field)) {
213 4
            $method = 'getColumnIndexByValue';
214
        }
215
216 20
        return $this->$method($field, $error_message);
217
    }
218
219
    /**
220
     * Returns the selected column name
221
     *
222
     * @param string $value
223
     * @param string $error_message
224
     *
225
     * @throws RuntimeException if the column is not found
226
     *
227
     * @return string
228
     */
229 4
    protected function getColumnIndexByValue(string $value, string $error_message): string
230
    {
231 4
        if (false !== array_search($value, $this->column_names, true)) {
232 2
            return $value;
233
        }
234
235 2
        throw new RuntimeException(sprintf($error_message, $value));
236
    }
237
238
    /**
239
     * Returns the selected column name according to its offset
240
     *
241
     * @param int    $index
242
     * @param string $error_message
243
     *
244
     * @throws RuntimeException if the field is invalid or not found
245
     *
246
     * @return int|string
247
     */
248 10
    protected function getColumnIndexByKey(int $index, string $error_message)
249
    {
250 10
        $offset = $this->filterMinRange($index, 0, $error_message);
251 10
        if (empty($this->column_names)) {
252 6
            return $offset;
253
        }
254
255 4
        $value = array_search($offset, array_flip($this->column_names), true);
256 4
        if (false !== $value) {
257 2
            return $value;
258
        }
259
260 2
        throw new RuntimeException(sprintf($error_message, $offset));
261
    }
262
263
    /**
264
     * Fetches the next key-value pairs from a result set (first
265
     * column is the key, second column is the value).
266
     *
267
     * By default if no column index is provided:
268
     * - the first CSV column is used to provide the keys
269
     * - the second CSV column is used to provide the value
270
     *
271
     * @param string|int $offset_index The column index to serve as offset
272
     * @param string|int $value_index  The column index to serve as value
273
     *
274
     * @return Generator
275
     */
276 8
    public function fetchPairs($offset_index = 0, $value_index = 1): Generator
277
    {
278 8
        $offset = $this->getColumnIndex($offset_index, __METHOD__.'() expects the offset index value to be a valid string or integer, `%s` given');
279 8
        $value = $this->getColumnIndex($value_index, __METHOD__.'() expects the value index value to be a valid string or integer, `%s` given');
280
281
        $filter = function (array $record) use ($offset): bool {
282 8
            return isset($record[$offset]);
283 8
        };
284
285 8
        $select = function (array $record) use ($offset, $value): array {
286 6
            return [$record[$offset], $record[$value] ?? null];
287 8
        };
288
289 8
        $iterator = new MapIterator(new CallbackFilterIterator($this->iterator, $filter), $select);
290 8
        foreach ($iterator as $pair) {
291 6
            yield $pair[0] => $pair[1];
292
        }
293 8
    }
294
295
    /**
296
     * Whether we should preserve the CSV document record offset.
297
     *
298
     * If set to true CSV document record offset will be added to
299
     * method output where it makes sense.
300
     *
301
     * @param bool $status
302
     *
303
     * @return self
304
     */
305 2
    public function preserveRecordOffset(bool $status): self
306
    {
307 2
        $this->preserve_offset = $status;
308
309 2
        return $this;
310
    }
311
}
312