Skip to content

Commit

Permalink
Adding collections methods to the TabularReader interface (#497)
Browse files Browse the repository at this point in the history
* Adding collections methods to the TabularReader interface
  • Loading branch information
nyamsprod authored Sep 19, 2023
1 parent 1a20a5c commit b0af98a
Show file tree
Hide file tree
Showing 7 changed files with 536 additions and 0 deletions.
108 changes: 108 additions & 0 deletions docs/9.0/reader/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,114 @@ $records = $stmt->process($reader);
//$records is a League\Csv\ResultSet object
```

### Collection methods

<p class="message-notice">New methods added in version <code>9.11</code>.</p>

To ease working with the loaded CSV document the following methods derived from collection are added.
Some are just wrapper methods around the `Statement` class while others use the iterable nature
of the CSV document.

#### Reader::each

Iterates over the records in the CSV document and passes each item to a closure:

```php
use League\Csv\Reader;
use League\Csv\Writer;

$writer = Writer::createFromString('');
$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$reader->each(function (array $record, int $offset) use ($writer) {
if ($offset < 10) {
return $writer->insertOne($record);
}

return false;
});

//$writer will contain at most 10 lines coming from the $reader document.
// the iteration stopped when the closure return false.
```

#### Reader::exists

Tests for the existence of an element that satisfies the given predicate.

```php
use League\Csv\Reader;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$exists = $reader->exists(fn (array $records) => in_array('twenty-five', $records, true));

//$exists returns true if at cell one cell contains the word `twenty-five` otherwise returns false,
```

#### Reader::reduce

Applies iteratively the given function to each element in the collection, so as to reduce the collection to
a single value.

```php
use League\Csv\Reader;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$nbTotalCells = $reader->recude(fn (?int $carry, array $records) => ($carry ?? 0) + count($records));

//$records contains the total number of celle contains in the CSV documents.
```

#### Reader::filter

Returns all the elements of this collection for which your callback function returns `true`. The order and keys of the elements are preserved.

<p class="message-info"> Wraps the functionality of <code>Statement::where</code>.</p>

```php
use League\Csv\Reader;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$records = $reader->filter(fn (array $record): => 5 === count($record));

//$recors is a ResultSet object with only records with 5 elements
```

#### Reader::slice

Extracts a slice of $length elements starting at position $offset from the Collection.
If $length is `-1` it returns all elements from `$offset` to the end of the
Collection. Keys have to be preserved by this method. Calling this
method will only return the selected slice and NOT change the
elements contained in the collection slice is called on.

<p class="message-info"> Wraps the functionality of <code>Statement::offset</code>
and <code>Statement::limit</code>.</p>

```php
use League\Csv\Reader;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$records = $reader->slice(10, 25);

//$records contains up to 25 rows starting at the offest 10 (the eleventh rows)
```

#### Reader::sorted

Sorts the CSV document while keeping the original keys.

```php
use League\Csv\Reader;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$records = $reader->sorted(fn (array $recordA, array $recordB) => $recordA['firstname'] <=> $recordB['firstname']);

//$records is a ResultSet containing the sorted CSV document.
//The original $reader is not changed
```

<p class="message-info"> Wraps the functionality of <code>Statement::orderBy</code>.</p>

## Records conversion

### Json serialization
Expand Down
121 changes: 121 additions & 0 deletions docs/9.0/reader/resultset.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,127 @@ foreach ($records->fetchPairs() as $firstname => $lastname) {

<p class="message-warning">If the <code>ResultSet</code> contains column names and the submitted arguments are not found, an <code>Exception</code> exception is thrown.</p>

### Collection methods

<p class="message-notice">New methods added in version <code>9.11</code>.</p>

To ease working with the `ResultSet` the following methods derived from collection are added.
Some are just wrapper methods around the `Statement` class while others use the iterable nature
of the instance.

#### ResultSet::each

Iterates over the records in the CSV document and passes each item to a closure:

```php
use League\Csv\Reader;
use League\Csv\Statement;
use League\Csv\Writer;

$writer = Writer::createFromString('');
$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');

$resultSet = Statement::create()->process($reader);
$resultSet->each(function (array $record, int $offset) use ($writer) {
if ($offset < 10) {
return $writer->insertOne($record);
}

return false;
});

//$writer will contain at most 10 lines coming from the $resultSet.
// the iteration stopped when the closure return false.
```

#### ResultSet::exists

Tests for the existence of an element that satisfies the given predicate.

```php
use League\Csv\Reader;
use League\Csv\Statement;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$resultSet = Statement::create()->process($reader);

$exists = $resultSet->exists(fn (array $records) => in_array('twenty-five', $records, true));

//$exists returns true if at cell one cell contains the word `twenty-five` otherwise returns false,
```

#### Reader::reduce

Applies iteratively the given function to each element in the collection, so as to reduce the collection to
a single value.

```php
use League\Csv\Reader;
use League\Csv\Statement;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$resultSet = Statement::create()->process($reader);

$nbTotalCells = $resultSet->recude(fn (?int $carry, array $records) => ($carry ?? 0) + count($records));

//$records contains the total number of celle contains in the $resultSet
```

#### Reader::filter

Returns all the elements of this collection for which your callback function returns `true`. The order and keys of the elements are preserved.

<p class="message-info"> Wraps the functionality of <code>Statement::where</code>.</p>

```php
use League\Csv\Reader;
use League\Csv\Statement;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$resultSet = Statement::create()->process($reader);

$records = $resultSet->filter(fn (array $record): => 5 === count($record));

//$recors is a ResultSet object with only records with 5 elements
```

#### Reader::slice

Extracts a slice of $length elements starting at position $offset from the Collection. If $length is `-1` it returns all elements from `$offset` to the end of the Collection.
Keys have to be preserved by this method. Calling this method will only return the selected slice and NOT change the elements contained in the collection slice is called on.

<p class="message-info"> Wraps the functionality of <code>Statement::offset</code> and <code>Statement::limit</code>.</p>

```php
use League\Csv\Reader;
use League\Csv\Statement;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$resultSet = Statement::create()->process($reader);

$records = $resultSet->slice(10, 25);

//$records contains up to 25 rows starting at the offset 10 (the eleventh rows)
```

#### Reader::sorted

Sorts the CSV document while keeping the original keys.

<p class="message-info"> Wraps the functionality of <code>Statement::orderBy</code>.</p>

```php
use League\Csv\Reader;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$resultSet = Statement::create()->process($reader);

$records = $resultSet->sorted(fn (array $recordA, array $recordB) => $recordA['firstname'] <=> $recordB['firstname']);

//$records is a ResultSet containing the original resultSet.
//The original ResultSet is not changed
```

## Conversions

### Json serialization
Expand Down
80 changes: 80 additions & 0 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace League\Csv;

use CallbackFilterIterator;
use Closure;
use Iterator;
use JsonSerializable;
use SplFileObject;
Expand Down Expand Up @@ -232,6 +233,85 @@ public function jsonSerialize(): array
return array_values([...$this->getRecords()]);
}

/**
* @param Closure(array<string|null>, array-key=): (void|bool|null) $closure
*/
public function each(Closure $closure): bool
{
foreach ($this as $offset => $record) {
if (false === $closure($record, $offset)) {
return false;
}
}

return true;
}

/**
* @param Closure(array<string|null>, array-key=): bool $closure
*/
public function exists(Closure $closure): bool
{
foreach ($this as $offset => $record) {
if (true === $closure($record, $offset)) {
return true;
}
}

return false;
}

/**
* @param Closure(TInitial|null, array<string|null>, array-key=): TInitial $closure
* @param TInitial|null $initial
*
* @template TInitial
*
* @return TInitial|null
*/
public function reduce(Closure $closure, mixed $initial = null): mixed
{
foreach ($this as $offset => $record) {
$initial = $closure($initial, $record, $offset);
}

return $initial;
}

/**
* @param Closure(array<string|int>, array-key): bool $closure
*
* @throws Exception
* @throws SyntaxError
*/
public function filter(Closure $closure): TabularDataReader
{
return Statement::create()->where($closure)->process($this);
}

/**
* @param int<0, max> $offset
* @param int<-1, max> $length
*
* @throws Exception
* @throws SyntaxError
*/
public function slice(int $offset, int $length = -1): TabularDataReader
{
return Statement::create()->offset($offset)->limit($length)->process($this);
}

/**
* @param Closure(array<string|null>, array<string|null>): int $orderBy
*
* @throws Exception
* @throws SyntaxError
*/
public function sorted(Closure $orderBy): TabularDataReader
{
return Statement::create()->orderBy($orderBy)->process($this);
}

/**
* @param array<string> $header
*
Expand Down
Loading

0 comments on commit b0af98a

Please sign in to comment.