Completed
Pull Request — master (#377)
by ignace nyamagana
01:58
created

ResultSet::fetchColumn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 16
ccs 10
cts 10
cp 1
crap 2
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * League.Csv (https://csv.thephpleague.com)
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Csv;
15
16
use CallbackFilterIterator;
17
use Countable;
18
use Generator;
19
use Iterator;
20
use IteratorAggregate;
21
use JsonSerializable;
22
use LimitIterator;
23
use function array_flip;
24
use function array_search;
25
use function is_string;
26
use function iterator_count;
27
use function iterator_to_array;
28
use function sprintf;
29
30
/**
31
 * Represents the result set of a {@link Reader} processed by a {@link Statement}.
32
 */
33
class ResultSet implements Countable, IteratorAggregate, JsonSerializable
34
{
35
    /**
36
     * The CSV records collection.
37
     *
38
     * @var Iterator
39
     */
40
    protected $records;
41
42
    /**
43
     * The CSV records collection header.
44
     *
45
     * @var array
46
     */
47
    protected $header = [];
48
49
    /**
50
     * New instance.
51
     */
52 18
    public function __construct(Iterator $records, array $header)
53
    {
54 18
        $this->records = $records;
55 18
        $this->header = $header;
56 18
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 21
    public function __destruct()
62
    {
63 21
        unset($this->records);
64 21
    }
65
66
    /**
67
     * Returns the header associated with the result set.
68
     *
69
     * @return string[]
70
     */
71 3
    public function getHeader(): array
72
    {
73 3
        return $this->header;
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79 24
    public function getIterator(): Generator
80
    {
81 24
        return $this->getRecords();
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 18
    public function getRecords(array $header = []): Generator
88
    {
89 18
        $records = $this->combineHeader($header);
90 18
        foreach ($records as $offset => $value) {
91 18
            yield $offset => $value;
92
        }
93 18
    }
94
95
    /**
96
     * Combine the header to each record if present.
97
     */
98 15
    protected function combineHeader(array $header): Iterator
99
    {
100 15
        if ($header === $this->header || [] === $header) {
101 15
            return $this->records;
102
        }
103
104 3
        $field_count = count($header);
105
        $mapper = static function (array $record) use ($header, $field_count): array {
106 3
            if (count($record) != $field_count) {
107 3
                $record = array_slice(array_pad($record, $field_count, null), 0, $field_count);
108
            }
109
110
            /** @var array<string|null> $assocRecord */
111 3
            $assocRecord = array_combine($header, $record);
112
113 3
            return $assocRecord;
114 3
        };
115
116 3
        return new MapIterator($this->records, $mapper);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 6
    public function count(): int
123
    {
124 6
        return iterator_count($this->records);
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130 3
    public function jsonSerialize(): array
131
    {
132 3
        return iterator_to_array($this->records, false);
133
    }
134
135
    /**
136
     * Returns the nth record from the result set.
137
     *
138
     * By default if no index is provided the first record of the resultet is returned
139
     *
140
     * @param int $nth_record the CSV record offset
141
     *
142
     * @throws Exception if argument is lesser than 0
143
     */
144 6
    public function fetchOne(int $nth_record = 0): array
145
    {
146 6
        if ($nth_record < 0) {
147 3
            throw new InvalidArgument(sprintf('%s() expects the submitted offset to be a positive integer or 0, %s given', __METHOD__, $nth_record));
148
        }
149
150 3
        $iterator = new LimitIterator($this->records, $nth_record, 1);
151 3
        $iterator->rewind();
152
153 3
        return (array) $iterator->current();
154
    }
155
156
    /**
157
     * Returns a single column from the next record of the result set.
158
     *
159
     * By default if no value is supplied the first column is fetch
160
     *
161
     * @param string|int $index CSV column index
162
     */
163 21
    public function fetchColumn($index = 0): Generator
164
    {
165 21
        $offset = $this->getColumnIndex($index, __METHOD__.'() expects the column index to be a valid string or integer, `%s` given');
166
        $filter = static function (array $record) use ($offset): bool {
167 12
            return isset($record[$offset]);
168 12
        };
169
170
        $select = static function (array $record) use ($offset): string {
171 9
            return $record[$offset];
172 12
        };
173
174 12
        $iterator = new MapIterator(new CallbackFilterIterator($this->records, $filter), $select);
175 12
        foreach ($iterator as $tKey => $tValue) {
176 9
            yield $tKey => $tValue;
177
        }
178 6
    }
179
180
    /**
181
     * Filter a column name against the header if any.
182
     *
183
     * @param string|int $field         the field name or the field index
184
     * @param string     $error_message the associated error message
185
     *
186
     * @return string|int
187
     */
188 30
    protected function getColumnIndex($field, string $error_message)
189
    {
190 30
        if (is_string($field)) {
191 6
            return $this->getColumnIndexByValue($field, $error_message);
192
        }
193
194 27
        return $this->getColumnIndexByKey($field, $error_message);
195
    }
196
197
    /**
198
     * Returns the selected column name.
199
     *
200
     * @throws Exception if the column is not found
201
     */
202 6
    protected function getColumnIndexByValue(string $value, string $error_message): string
203
    {
204 6
        if (false !== array_search($value, $this->header, true)) {
205 3
            return $value;
206
        }
207
208 3
        throw new InvalidArgument(sprintf($error_message, $value));
209
    }
210
211
    /**
212
     * Returns the selected column name according to its offset.
213
     *
214
     * @throws Exception if the field is invalid or not found
215
     *
216
     * @return int|string
217
     */
218 18
    protected function getColumnIndexByKey(int $index, string $error_message)
219
    {
220 18
        if ($index < 0) {
221 3
            throw new InvalidArgument($error_message);
222
        }
223
224 15
        if ([] === $this->header) {
225 9
            return $index;
226
        }
227
228 6
        $value = array_search($index, array_flip($this->header), true);
229 6
        if (false !== $value) {
230 3
            return $value;
231
        }
232
233 3
        throw new InvalidArgument(sprintf($error_message, $index));
234
    }
235
236
    /**
237
     * Returns the next key-value pairs from a result set (first
238
     * column is the key, second column is the value).
239
     *
240
     * By default if no column index is provided:
241
     * - the first column is used to provide the keys
242
     * - the second column is used to provide the value
243
     *
244
     * @param string|int $offset_index The column index to serve as offset
245
     * @param string|int $value_index  The column index to serve as value
246
     */
247 12
    public function fetchPairs($offset_index = 0, $value_index = 1): Generator
248
    {
249 12
        $offset = $this->getColumnIndex($offset_index, __METHOD__.'() expects the offset index value to be a valid string or integer, `%s` given');
250 12
        $value = $this->getColumnIndex($value_index, __METHOD__.'() expects the value index value to be a valid string or integer, `%s` given');
251
252
        $filter = static function (array $record) use ($offset): bool {
253 12
            return isset($record[$offset]);
254 12
        };
255
256
        $select = static function (array $record) use ($offset, $value): array {
257 9
            return [$record[$offset], $record[$value] ?? null];
258 12
        };
259
260 12
        $iterator = new MapIterator(new CallbackFilterIterator($this->records, $filter), $select);
261 12
        foreach ($iterator as $pair) {
262 9
            yield $pair[0] => $pair[1];
263
        }
264 12
    }
265
}
266