diff --git a/CHANGELOG.md b/CHANGELOG.md index a4e47696..96c82373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All Notable changes to `Csv` will be documented in this file ### Fixed - `Reader::select` and `ResultSet::select` now internally use `Statement::select` +- `Statement` should not throw when `LimitIterator` is used in combinaison with `ArrayIterator`. ### Removed diff --git a/docs/9.0/reader/statement.md b/docs/9.0/reader/statement.md index 84b66a91..984bd5be 100644 --- a/docs/9.0/reader/statement.md +++ b/docs/9.0/reader/statement.md @@ -127,6 +127,39 @@ $records = Statement::create() // $records is a League\Csv\ResultSet instance with only 3 fields ``` +While we explain each method separately it is understood that you could use them all together to query your +CSV document as you want like in the following example. + +```php +use League\Csv\Reader; +use League\Csv\Statement; + +$constraints = Statement::create() + ->select('Integer', 'Text', 'Date and Time') + ->where(fn (array $record): bool => (float) $record['Float'] < 1.3) + ->orderBy(fn (array $r1, array $r2): int => (int) $r2['Integer'] <=> (int) $r1['Integer']) + ->limit(5) + ->offset(2); + +$document = <<setHeaderOffset(0); +$records = $constraints->process($csv); +//returns a ResultSet containing records which validates all the constraints. +``` + +Since the `Statement` instance is created independent of the CSV document you can re-use it on different CSV +document or `TabularDataReader` instances if needed. + ## FragmentFinder

This mechanism is introduced with version 9.12.0.

@@ -174,8 +207,8 @@ use League\Csv\FragmentFinder; $reader = Reader::createFromPath('/path/to/file.csv'); $finder = FragmentFinder::create(); -$finder->findAll('row=7-5;8-9', $reader); // return an Iterator -$finder->findFirst('row=7-5;8-9', $reader); // return an TabulatDataReader +$finder->findAll('row=7-5;8-9', $reader); // return an Iterator +$finder->findFirst('row=7-5;8-9', $reader); // return an TabularDataReader $finder->findFirstOrFail('row=7-5;8-9', $reader); // will throw ``` diff --git a/src/Statement.php b/src/Statement.php index e6b1aa1f..10ed55ac 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -18,6 +18,8 @@ use Iterator; use LimitIterator; +use OutOfBoundsException; + use function array_key_exists; use function array_reduce; use function array_search; @@ -153,9 +155,8 @@ public function process(TabularDataReader $tabular_data, array $header = []): Ta $header = $tabular_data->getHeader(); } - $iterator = $this->buildOrderBy( - array_reduce($this->where, $this->filter(...), $tabular_data->getRecords($header)) - ); + $iterator = array_reduce($this->where, $this->filter(...), $tabular_data->getRecords($header)); + $iterator = $this->buildOrderBy($iterator); /** @var Iterator> $iterator */ $iterator = new LimitIterator($iterator, $this->offset, $this->limit); @@ -189,8 +190,20 @@ protected function buildOrderBy(Iterator $iterator): Iterator return $cmp ?? 0; }; + + $class = new class () extends ArrayIterator { + public function seek(int $offset): void + { + try { + parent::seek($offset); + } catch (OutOfBoundsException) { + return; + } + } + }; + /** @var ArrayIterator> $it */ - $it = new ArrayIterator([...$iterator]); + $it = new $class([...$iterator]); $it->uasort($compare); return $it; diff --git a/src/StatementTest.php b/src/StatementTest.php index 11adc2ea..2c3a035c 100644 --- a/src/StatementTest.php +++ b/src/StatementTest.php @@ -156,4 +156,28 @@ public function testHeaderMapperOnStatement(): void 'does not exists' => null, ], $results->first()); } + + public function testOrderByDoesNotThrowOnInvalidOffsetOrLimit(): void + { + $document = <<setHeaderOffset(0); + $constraints = Statement::create() + ->select('Integer', 'Text', 'Date and Time') + ->where(fn (array $record): bool => (float) $record['Float'] < 1.3) + ->orderBy(fn (array $record1, array $record2): int => (int) $record2['Integer'] <=> (int) $record1['Integer']) + ->limit(5) + ->offset(2); + + self::assertSame([], $constraints->process($csv)->nth(42)); + } }