ResultSet::fetchOne()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 2
rs 9.9
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 Iterator;
18
use JsonSerializable;
19
use LimitIterator;
20
use function array_flip;
21
use function array_search;
22
use function is_string;
23
use function iterator_count;
24
use function iterator_to_array;
25
use function sprintf;
26
27
/**
28
 * Represents the result set of a {@link Reader} processed by a {@link Statement}.
29
 */
30
class ResultSet implements TabularDataReader, JsonSerializable
31
{
32
    /**
33
     * The CSV records collection.
34
     *
35
     * @var Iterator
36
     */
37
    protected $records;
38
39
    /**
40
     * The CSV records collection header.
41
     *
42
     * @var array
43
     */
44
    protected $header = [];
45
46
    /**
47
     * New instance.
48
     */
49 21
    public function __construct(Iterator $records, array $header)
50
    {
51 21
        $this->validateHeader($header);
52
53 21
        $this->records = $records;
54 21
        $this->header = $header;
55 21
    }
56
57
    /**
58
     * @throws SyntaxError if the header syntax is invalid
59
     */
60 21
    protected function validateHeader(array $header): void
61
    {
62 21
        if ($header !== array_unique(array_filter($header, 'is_string'))) {
63 3
            throw new SyntaxError('The header record must be an empty or a flat array with unique string values.');
64
        }
65 21
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 24
    public function __destruct()
71
    {
72 24
        unset($this->records);
73 24
    }
74
75
    /**
76
     * Returns a new instance from a League\Csv\Reader object.
77
     */
78 6
    public static function createFromTabularDataReader(TabularDataReader $reader): self
79
    {
80 6
        return new self($reader->getRecords(), $reader->getHeader());
81
    }
82
83
    /**
84
     * Returns the header associated with the result set.
85
     *
86
     * @return string[]
87
     */
88 3
    public function getHeader(): array
89
    {
90 3
        return $this->header;
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96 9
    public function getIterator(): Iterator
97
    {
98 9
        return $this->getRecords();
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 18
    public function getRecords(array $header = []): Iterator
105
    {
106 18
        $this->validateHeader($header);
107 18
        $records = $this->combineHeader($header);
108 18
        foreach ($records as $offset => $value) {
109 18
            yield $offset => $value;
110
        }
111 18
    }
112
113
    /**
114
     * Combine the header to each record if present.
115
     */
116 15
    protected function combineHeader(array $header): Iterator
117
    {
118 15
        if ($header === $this->header || [] === $header) {
119 15
            return $this->records;
120
        }
121
122 3
        $field_count = count($header);
123
        $mapper = static function (array $record) use ($header, $field_count): array {
124 3
            if (count($record) != $field_count) {
125 3
                $record = array_slice(array_pad($record, $field_count, null), 0, $field_count);
126
            }
127
128
            /** @var array<string|null> $assocRecord */
129 3
            $assocRecord = array_combine($header, $record);
130
131 3
            return $assocRecord;
132 3
        };
133
134 3
        return new MapIterator($this->records, $mapper);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140 3
    public function count(): int
141
    {
142 3
        return iterator_count($this->records);
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 3
    public function jsonSerialize(): array
149
    {
150 3
        return iterator_to_array($this->records, false);
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 12
    public function fetchOne(int $nth_record = 0): array
157
    {
158 12
        if ($nth_record < 0) {
159 3
            throw new InvalidArgument(sprintf('%s() expects the submitted offset to be a positive integer or 0, %s given', __METHOD__, $nth_record));
160
        }
161
162 9
        $iterator = new LimitIterator($this->records, $nth_record, 1);
163 9
        $iterator->rewind();
164
165 9
        return (array) $iterator->current();
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 21
    public function fetchColumn($index = 0): Iterator
172
    {
173 21
        $offset = $this->getColumnIndex($index, __METHOD__.'() expects the column index to be a valid string or integer, `%s` given');
174
        $filter = static function (array $record) use ($offset): bool {
175 12
            return isset($record[$offset]);
176 12
        };
177
178
        $select = static function (array $record) use ($offset): string {
179 9
            return $record[$offset];
180 12
        };
181
182 12
        $iterator = new MapIterator(new CallbackFilterIterator($this->records, $filter), $select);
183 12
        foreach ($iterator as $tKey => $tValue) {
184 9
            yield $tKey => $tValue;
185
        }
186 6
    }
187
188
    /**
189
     * Filter a column name against the 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 30
    protected function getColumnIndex($field, string $error_message)
197
    {
198 30
        if (is_string($field)) {
199 6
            return $this->getColumnIndexByValue($field, $error_message);
200
        }
201
202 27
        return $this->getColumnIndexByKey($field, $error_message);
203
    }
204
205
    /**
206
     * Returns the selected column name.
207
     *
208
     * @throws Exception if the column is not found
209
     */
210 6
    protected function getColumnIndexByValue(string $value, string $error_message): string
211
    {
212 6
        if (false !== array_search($value, $this->header, true)) {
213 3
            return $value;
214
        }
215
216 3
        throw new InvalidArgument(sprintf($error_message, $value));
217
    }
218
219
    /**
220
     * Returns the selected column name according to its offset.
221
     *
222
     * @throws Exception if the field is invalid or not found
223
     *
224
     * @return int|string
225
     */
226 18
    protected function getColumnIndexByKey(int $index, string $error_message)
227
    {
228 18
        if ($index < 0) {
229 3
            throw new InvalidArgument($error_message);
230
        }
231
232 15
        if ([] === $this->header) {
233 9
            return $index;
234
        }
235
236 6
        $value = array_search($index, array_flip($this->header), true);
237 6
        if (false !== $value) {
238 3
            return $value;
239
        }
240
241 3
        throw new InvalidArgument(sprintf($error_message, $index));
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247 12
    public function fetchPairs($offset_index = 0, $value_index = 1): Iterator
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