Completed
Push — master ( 9fb684...03d152 )
by ignace nyamagana
04:58 queued 03:47
created

ResultSet::preserveRecordOffset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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