diff --git a/src/Reader.php b/src/Reader.php index a2c00a31..58344e2a 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -19,12 +19,8 @@ use JsonSerializable; use SplFileObject; -use function array_combine; use function array_filter; -use function array_pad; -use function array_slice; use function array_unique; -use function count; use function is_array; use function iterator_count; use function mb_strlen; @@ -325,18 +321,16 @@ public function sorted(Closure $orderBy): TabularDataReader } /** - * @param array $header + * @param array $headerMapper * * @throws Exception * * @return Iterator> */ - public function getRecords(array $header = []): Iterator + public function getRecords(array $headerMapper = []): Iterator { - $header = $this->computeHeader($header); - + $headerMapper = $this->computeHeader($headerMapper); $normalized = fn ($record): bool => is_array($record) && ($this->is_empty_records_included || $record !== [null]); - $bom = ''; if (!$this->is_input_bom_included) { $bom = $this->getInputBOM(); @@ -348,13 +342,10 @@ public function getRecords(array $header = []): Iterator } if ($this->is_empty_records_included) { - $records = new MapIterator( - $records, - fn (array $record): array => ([null] === $record) ? [] : $record - ); + $records = new MapIterator($records, fn (array $record): array => ([null] === $record) ? [] : $record); } - return $this->combineHeader($records, $header); + return $this->combineHeader($records, $headerMapper); } /** @@ -382,9 +373,9 @@ protected function computeHeader(array $header): array /** * Combines the CSV header to each record if present. * - * @param array $header + * @param array $headerMapper */ - protected function combineHeader(Iterator $iterator, array $header): Iterator + protected function combineHeader(Iterator $iterator, array $headerMapper): Iterator { $formatter = fn (array $record): array => array_reduce( $this->formatters, @@ -392,23 +383,17 @@ protected function combineHeader(Iterator $iterator, array $header): Iterator $record ); - if ([] === $header) { - return new MapIterator($iterator, $formatter(...)); - } - - $field_count = count($header); - $mapper = function (array $record) use ($header, $field_count, $formatter): array { - if (count($record) !== $field_count) { - $record = array_slice(array_pad($record, $field_count, null), 0, $field_count); - } + return match ([]) { + $headerMapper => new MapIterator($iterator, $formatter(...)), + default => new MapIterator($iterator, function (array $record) use ($headerMapper, $formatter): array { + $assocRecord = []; + foreach ($headerMapper as $offset => $headerName) { + $assocRecord[$headerName] = $record[$offset] ?? null; + } - /** @var array $assocRecord */ - $assocRecord = array_combine($header, $record); - - return $formatter($assocRecord); + return $formatter($assocRecord); + }), }; - - return new MapIterator($iterator, $mapper); } /** diff --git a/src/ReaderTest.php b/src/ReaderTest.php index a4a76394..22a3b4e1 100644 --- a/src/ReaderTest.php +++ b/src/ReaderTest.php @@ -646,4 +646,17 @@ public function testReaderFormatterUsesOffset(): void ], ], [...$reader]); } + + public function testHeaderMapper(): void + { + $csv = <<getRecords([3 => 'Year', 0 => 'Firstname', 1 => 'Count'])][0]; + self::assertSame(['Year' => '2004', 'Firstname' => 'Abel', 'Count' => '14'], $firstRow); + } } diff --git a/src/ResultSet.php b/src/ResultSet.php index 0c4a9e40..713efc99 100644 --- a/src/ResultSet.php +++ b/src/ResultSet.php @@ -31,15 +31,19 @@ */ class ResultSet implements TabularDataReader, JsonSerializable { + /** @var array */ + protected array $header; + /** * @param Iterator> $records * @param array $header * * @throws SyntaxError */ - public function __construct(protected Iterator $records, protected array $header = []) + public function __construct(protected Iterator $records, array $header = []) { - $this->validateHeader($this->header); + $this->validateHeader($header); + $this->header = array_values($header); } /** @@ -150,45 +154,40 @@ public function sorted(Closure $orderBy): TabularDataReader } /** - * @param array $header + * @param array $headerMapper * * @throws SyntaxError * * @return Iterator> */ - public function getRecords(array $header = []): Iterator + public function getRecords(array $headerMapper = []): Iterator { - $this->validateHeader($header); + $this->validateHeader($headerMapper); - yield from $this->combineHeader($header); + yield from $this->combineHeader($headerMapper); } /** * Combines the header to each record if present. * - * @param array $header + * @param array $headerMapper * * @return Iterator> */ - protected function combineHeader(array $header): Iterator + protected function combineHeader(array $headerMapper): Iterator { - if ($header === $this->header || [] === $header) { - return $this->records; - } - - $field_count = count($header); - $mapper = static function (array $record) use ($header, $field_count): array { - if (count($record) !== $field_count) { - $record = array_slice(array_pad($record, $field_count, null), 0, $field_count); - } - - /** @var array $assocRecord */ - $assocRecord = array_combine($header, $record); - - return $assocRecord; + return match (true) { + $headerMapper === $this->header, + [] === $headerMapper => $this->records, + default => new MapIterator($this->records, function (array $record) use ($headerMapper): array { + $assocRecord = []; + foreach ($headerMapper as $offset => $headerName) { + $assocRecord[$headerName] = $record[$offset] ?? null; + } + + return $assocRecord; + }), }; - - return new MapIterator($this->records, $mapper); } public function count(): int diff --git a/src/ResultSetTest.php b/src/ResultSetTest.php index 3ef4d272..15fc5ee4 100644 --- a/src/ResultSetTest.php +++ b/src/ResultSetTest.php @@ -410,4 +410,17 @@ public function testExistsRecord(): void self::assertFalse(Statement::create()->process($this->csv)->exists(fn (array $record) => array_key_exists('foobar', $record))); self::assertTrue(Statement::create()->process($this->csv)->exists(fn (array $record) => count($record) < 5)); } + + public function testHeaderMapperOnResultSet(): void + { + $results = Statement::create() + ->process($this->csv) + ->getRecords([2 => 'e-mail', 1 => 'lastname', 33 => 'does not exists']); + + self::assertSame([ + 'e-mail' => 'john.doe@example.com', + 'lastname' => 'doe', + 'does not exists' => null, + ], [...$results][0]); + } } diff --git a/src/StatementTest.php b/src/StatementTest.php index fbc23225..11adc2ea 100644 --- a/src/StatementTest.php +++ b/src/StatementTest.php @@ -144,4 +144,16 @@ public function testOrderByWithEquity(): void self::assertSame($this->expected, array_values([...$calculated])); } + + public function testHeaderMapperOnStatement(): void + { + $results = Statement::create() + ->process($this->csv, [2 => 'e-mail', 1 => 'lastname', 33 => 'does not exists']); + self::assertSame(['e-mail', 'lastname', 'does not exists'], $results->getHeader()); + self::assertSame([ + 'e-mail' => 'john.doe@example.com', + 'lastname' => 'doe', + 'does not exists' => null, + ], $results->first()); + } } diff --git a/src/TabularDataReader.php b/src/TabularDataReader.php index c96940c9..fb2ffff5 100644 --- a/src/TabularDataReader.php +++ b/src/TabularDataReader.php @@ -78,11 +78,11 @@ public function getHeader(): array; * filled with null values while extra record fields are strip from * the returned object. * - * @param array $header an optional header to use instead of the CSV document header + * @param array $headerMapper an optional header mapper to use instead of the CSV document header * * @return Iterator> */ - public function getRecords(array $header = []): Iterator; + public function getRecords(array $headerMapper = []): Iterator; /** * Returns the next key-value pairs from the tabular data (first