AbstractCsv.php
3 days ago
ByteSequence.php
3 days ago
CannotInsertRecord.php
3 days ago
CharsetConverter.php
3 days ago
ColumnConsistency.php
3 days ago
EncloseField.php
3 days ago
EscapeFormula.php
3 days ago
Exception.php
9 months ago
HTMLConverter.php
3 days ago
Info.php
3 days ago
InvalidArgument.php
9 months ago
MapIterator.php
3 days ago
RFC4180Field.php
3 days ago
Reader.php
3 days ago
ResultSet.php
3 days ago
Statement.php
3 days ago
Stream.php
3 days ago
SyntaxError.php
3 days ago
TabularDataReader.php
3 days ago
UnableToProcessCsv.php
9 months ago
UnavailableFeature.php
9 months ago
UnavailableStream.php
9 months ago
Writer.php
3 days ago
XMLConverter.php
3 days ago
functions.php
9 months ago
functions_include.php
9 months ago
ResultSet.php
212 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * League.Csv (https://csv.thephpleague.com) |
| 5 | * |
| 6 | * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> |
| 7 | * |
| 8 | * For the full copyright and license information, please view the LICENSE |
| 9 | * file that was distributed with this source code. |
| 10 | */ |
| 11 | declare (strict_types=1); |
| 12 | namespace IAWPSCOPED\League\Csv; |
| 13 | |
| 14 | use CallbackFilterIterator; |
| 15 | use Generator; |
| 16 | use Iterator; |
| 17 | use JsonSerializable; |
| 18 | use LimitIterator; |
| 19 | use function array_flip; |
| 20 | use function array_search; |
| 21 | use function is_string; |
| 22 | use function iterator_count; |
| 23 | use function iterator_to_array; |
| 24 | /** |
| 25 | * Represents the result set of a {@link Reader} processed by a {@link Statement}. |
| 26 | * @internal |
| 27 | */ |
| 28 | class ResultSet implements TabularDataReader, JsonSerializable |
| 29 | { |
| 30 | /** The CSV records collection. */ |
| 31 | protected Iterator $records; |
| 32 | /** @var array<string> The CSV records collection header. */ |
| 33 | protected array $header = []; |
| 34 | public function __construct(Iterator $records, array $header) |
| 35 | { |
| 36 | $this->validateHeader($header); |
| 37 | $this->records = $records; |
| 38 | $this->header = $header; |
| 39 | } |
| 40 | /** |
| 41 | * @throws SyntaxError if the header syntax is invalid |
| 42 | */ |
| 43 | protected function validateHeader(array $header) : void |
| 44 | { |
| 45 | if ($header !== ($filtered_header = \array_filter($header, 'is_string'))) { |
| 46 | throw SyntaxError::dueToInvalidHeaderColumnNames(); |
| 47 | } |
| 48 | if ($header !== \array_unique($filtered_header)) { |
| 49 | throw SyntaxError::dueToDuplicateHeaderColumnNames($header); |
| 50 | } |
| 51 | } |
| 52 | public function __destruct() |
| 53 | { |
| 54 | unset($this->records); |
| 55 | } |
| 56 | /** |
| 57 | * Returns a new instance from an object implementing the TabularDataReader interface. |
| 58 | */ |
| 59 | public static function createFromTabularDataReader(TabularDataReader $reader) : self |
| 60 | { |
| 61 | return new self($reader->getRecords(), $reader->getHeader()); |
| 62 | } |
| 63 | /** |
| 64 | * Returns the header associated with the result set. |
| 65 | * |
| 66 | * @return array<string> |
| 67 | */ |
| 68 | public function getHeader() : array |
| 69 | { |
| 70 | return $this->header; |
| 71 | } |
| 72 | public function getIterator() : Iterator |
| 73 | { |
| 74 | return $this->getRecords(); |
| 75 | } |
| 76 | public function getRecords(array $header = []) : Iterator |
| 77 | { |
| 78 | $this->validateHeader($header); |
| 79 | $records = $this->combineHeader($header); |
| 80 | foreach ($records as $offset => $value) { |
| 81 | (yield $offset => $value); |
| 82 | } |
| 83 | } |
| 84 | /** |
| 85 | * Combine the header to each record if present. |
| 86 | */ |
| 87 | protected function combineHeader(array $header) : Iterator |
| 88 | { |
| 89 | if ($header === $this->header || [] === $header) { |
| 90 | return $this->records; |
| 91 | } |
| 92 | $field_count = \count($header); |
| 93 | $mapper = static function (array $record) use($header, $field_count) : array { |
| 94 | if (\count($record) != $field_count) { |
| 95 | $record = \array_slice(\array_pad($record, $field_count, null), 0, $field_count); |
| 96 | } |
| 97 | /** @var array<string|null> $assocRecord */ |
| 98 | $assocRecord = \array_combine($header, $record); |
| 99 | return $assocRecord; |
| 100 | }; |
| 101 | return new MapIterator($this->records, $mapper); |
| 102 | } |
| 103 | public function count() : int |
| 104 | { |
| 105 | return iterator_count($this->records); |
| 106 | } |
| 107 | public function jsonSerialize() : array |
| 108 | { |
| 109 | return iterator_to_array($this->records, \false); |
| 110 | } |
| 111 | public function fetchOne(int $nth_record = 0) : array |
| 112 | { |
| 113 | if ($nth_record < 0) { |
| 114 | throw InvalidArgument::dueToInvalidRecordOffset($nth_record, __METHOD__); |
| 115 | } |
| 116 | $iterator = new LimitIterator($this->records, $nth_record, 1); |
| 117 | $iterator->rewind(); |
| 118 | $result = $iterator->current(); |
| 119 | if (!\is_array($result)) { |
| 120 | return []; |
| 121 | } |
| 122 | return $result; |
| 123 | } |
| 124 | /** |
| 125 | * @throws Exception |
| 126 | */ |
| 127 | public function fetchColumnByName(string $name) : Iterator |
| 128 | { |
| 129 | return $this->yieldColumn($this->getColumnIndexByValue($name, 'name', __METHOD__)); |
| 130 | } |
| 131 | /** |
| 132 | * @throws Exception |
| 133 | */ |
| 134 | public function fetchColumnByOffset(int $offset) : Iterator |
| 135 | { |
| 136 | return $this->yieldColumn($this->getColumnIndexByKey($offset, 'offset', __METHOD__)); |
| 137 | } |
| 138 | public function fetchColumn($index = 0) : Iterator |
| 139 | { |
| 140 | return $this->yieldColumn($this->getColumnIndex($index, 'offset', __METHOD__)); |
| 141 | } |
| 142 | /** |
| 143 | * @param string|int $offset |
| 144 | */ |
| 145 | protected function yieldColumn($offset) : Generator |
| 146 | { |
| 147 | $iterator = new MapIterator(new CallbackFilterIterator($this->records, fn(array $record): bool => isset($record[$offset])), fn(array $record): string => $record[$offset]); |
| 148 | foreach ($iterator as $key => $value) { |
| 149 | (yield $key => $value); |
| 150 | } |
| 151 | } |
| 152 | /** |
| 153 | * Filter a column name against the header if any. |
| 154 | * |
| 155 | * @param string|int $field the field name or the field index |
| 156 | * |
| 157 | * @throws InvalidArgument if the field is invalid or not found |
| 158 | * |
| 159 | * @return string|int |
| 160 | */ |
| 161 | protected function getColumnIndex($field, string $type, string $method) |
| 162 | { |
| 163 | if (is_string($field)) { |
| 164 | return $this->getColumnIndexByValue($field, $type, $method); |
| 165 | } |
| 166 | return $this->getColumnIndexByKey($field, $type, $method); |
| 167 | } |
| 168 | /** |
| 169 | * Returns the selected column name. |
| 170 | * |
| 171 | * @throws InvalidArgument if the column is not found |
| 172 | */ |
| 173 | protected function getColumnIndexByValue(string $value, string $type, string $method) : string |
| 174 | { |
| 175 | if (\false === array_search($value, $this->header, \true)) { |
| 176 | throw InvalidArgument::dueToInvalidColumnIndex($value, $type, $method); |
| 177 | } |
| 178 | return $value; |
| 179 | } |
| 180 | /** |
| 181 | * Returns the selected column name according to its offset. |
| 182 | * |
| 183 | * @throws InvalidArgument if the field is invalid or not found |
| 184 | * |
| 185 | * @return int|string |
| 186 | */ |
| 187 | protected function getColumnIndexByKey(int $index, string $type, string $method) |
| 188 | { |
| 189 | if ($index < 0) { |
| 190 | throw InvalidArgument::dueToInvalidColumnIndex($index, $type, $method); |
| 191 | } |
| 192 | if ([] === $this->header) { |
| 193 | return $index; |
| 194 | } |
| 195 | $value = array_search($index, array_flip($this->header), \true); |
| 196 | if (\false === $value) { |
| 197 | throw InvalidArgument::dueToInvalidColumnIndex($index, $type, $method); |
| 198 | } |
| 199 | return $value; |
| 200 | } |
| 201 | public function fetchPairs($offset_index = 0, $value_index = 1) : Iterator |
| 202 | { |
| 203 | $offset = $this->getColumnIndex($offset_index, 'offset', __METHOD__); |
| 204 | $value = $this->getColumnIndex($value_index, 'value', __METHOD__); |
| 205 | $iterator = new MapIterator(new CallbackFilterIterator($this->records, fn(array $record): bool => isset($record[$offset])), fn(array $record): array => [$record[$offset], $record[$value] ?? null]); |
| 206 | /** @var array{0:int|string, 1:string|null} $pair */ |
| 207 | foreach ($iterator as $pair) { |
| 208 | (yield $pair[0] => $pair[1]); |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 |