Completed
Push — master ( 8913e7...8ca68d )
by ignace nyamagana
08:57
created

ResultSet::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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