Skip to content

Commit

Permalink
Adding Reader::addFormatter
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Sep 23, 2023
1 parent fb9d8b9 commit f77d1af
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 27 deletions.
15 changes: 8 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ All Notable changes to `Csv` will be documented in this file
### Added

- `EscapeFormula::unescapeRecord` does the opposite of `EscapeFormula::escapeRecord`
- `TabularReader::each`
- `TabularReader::exists`
- `TabularReader::reduce`
- `TabularReader::filter`
- `TabularReader::slice`
- `TabularReader::sorted`
- `TabularReader::each` (implemented on the `Reader` and the `ResultSet` object)
- `TabularReader::exists` (implemented on the `Reader` and the `ResultSet` object)
- `TabularReader::reduce` (implemented on the `Reader` and the `ResultSet` object)****
- `TabularReader::filter` (implemented on the `Reader` and the `ResultSet` object)
- `TabularReader::slice` (implemented on the `Reader` and the `ResultSet` object)
- `TabularReader::sorted` (implemented on the `Reader` and the `ResultSet` object)
- `Reader::addFormatter` (implemented on the `Reader` and the `ResultSet` object)

### Deprecated

- None
- `EscapeFormula::__invoke` use `EscapeFormula::__escapeRecord` instead

### Fixed

Expand Down
47 changes: 47 additions & 0 deletions docs/9.0/reader/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,53 @@ foreach ($records as $offset => $record) {
}
```

### Reader::addFormatter

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

#### Record Formatter

A formatter is a `callable` which accepts a single CSV record as an `array` on input and returns an array
representing the formatted CSV record according to its inner rules.

```php
function(array $record): array
```

#### Adding a Formatter to a Reader object

You can attach as many formatters as you want to the `Reader` class using the `Reader::addFormatter` method.
Formatters are applied following the *First In First Out* rule.

Fornatting happens **AFTER** combining headers and CSV value **BUT BEFORE** you can access the actual value.

```php
use League\Csv\Reader;

$csv = <<<CSV
firstname,lastname,e-mail
john,doe,john.doe@example.com
CSV;

$formatter = fn (array $row): array => array_map(strtoupper(...), $row);
$reader = Reader::createFromString($csv)
->setHeaderOffset(0)
->addFormatter($formatter);
[...$reader];
// [
// [
// 'firstname' => 'JOHN',
// 'lastname' => DOE',
// 'e-mail' => 'JOHN.DOE@EXAMPLE.COM',
// ],
//];

echo $reader->toString(); //returns the original $csv value without the formatting.
```

<p class="message-info">If a header is selected it won't be affected by the formatting</p>
<p class="message-info">The CSV document is not affected by the formatting and keeps its original value</p>

### Controlling the presence of empty records

<p class="message-info">New since version <code>9.4.0</code></p>
Expand Down
14 changes: 7 additions & 7 deletions docs/9.0/reader/resultset.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,15 +352,15 @@ 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
## 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
### ResultSet::each

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

Expand All @@ -385,7 +385,7 @@ $resultSet->each(function (array $record, int $offset) use ($writer) {
// the iteration stopped when the closure return false.
```

#### ResultSet::exists
### ResultSet::exists

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

Expand All @@ -401,7 +401,7 @@ $exists = $resultSet->exists(fn (array $records) => in_array('twenty-five', $rec
//$exists returns true if at cell one cell contains the word `twenty-five` otherwise returns false,
```

#### Reader::reduce
### Reader::reduce

Applies iteratively the given function to each element in the collection, so as to reduce the collection to
a single value.
Expand All @@ -418,7 +418,7 @@ $nbTotalCells = $resultSet->recude(fn (?int $carry, array $records) => ($carry ?
//$records contains the total number of celle contains in the $resultSet
```

#### Reader::filter
### 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.

Expand All @@ -436,7 +436,7 @@ $records = $resultSet->filter(fn (array $record): => 5 === count($record));
//$recors is a ResultSet object with only records with 5 elements
```

#### Reader::slice
### 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.
Expand All @@ -455,7 +455,7 @@ $records = $resultSet->slice(10, 25);
//$records contains up to 25 rows starting at the offset 10 (the eleventh rows)
```

#### Reader::sorted
### Reader::sorted

Sorts the CSV document while keeping the original keys.

Expand Down
23 changes: 13 additions & 10 deletions src/EscapeFormula.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,6 @@ public function getEscape(): string
return $this->escape;
}

/**
* League CSV formatter hook.
*
* @see escapeRecord
*/
public function __invoke(array $record): array
{
return $this->escapeRecord($record);
}

/**
* Escapes a CSV record.
*/
Expand Down Expand Up @@ -154,4 +144,17 @@ protected function isStringable(mixed $value): bool
{
return is_string($value) || $value instanceof Stringable;
}

/**
* @deprecated since 9.11.0 will be removed in the next major release
* @codeCoverageIgnore
*
* League CSV formatter hook.
*
* @see escapeRecord
*/
public function __invoke(array $record): array
{
return $this->escapeRecord($record);
}
}
15 changes: 15 additions & 0 deletions src/EscapeFormulaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ public function testUnescapeRecord(): void
self::assertEquals($expected, $formatter->unescapeRecord($record));
}

public function testFormatterOnReader(): void
{
$escaoeFormula = new EscapeFormula();
$record = ['2', '2017-07-25', 'Important Client', '=2+5', '240', "\ttab", "\rcr", ''];
$csv = Writer::createFromString();
$csv->addFormatter($escaoeFormula->escapeRecord(...));
$csv->insertOne($record);

$reader = Reader::createFromString($csv->toString());
self::assertNotEquals($record, $reader->first());

$reader->addFormatter($escaoeFormula->unescapeRecord(...));
self::assertSame($record, $reader->first());
}

public function testUnformatReader(): void
{
$formatter = new EscapeFormula();
Expand Down
24 changes: 21 additions & 3 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,24 @@ class Reader extends AbstractCsv implements TabularDataReader, JsonSerializable
protected bool $is_empty_records_included = false;
/** @var array<string> header record. */
protected array $header = [];
/** @var array<callable> callable collection to format the record before reading. */
protected array $formatters = [];

public static function createFromPath(string $path, string $open_mode = 'r', $context = null): static
{
return parent::createFromPath($path, $open_mode, $context);
}

/**
* Adds a record formatter.
*/
public function addFormatter(callable $formatter): self
{
$this->formatters[] = $formatter;

return $this;
}

protected function resetProperties(): void
{
parent::resetProperties();
Expand Down Expand Up @@ -374,20 +386,26 @@ protected function computeHeader(array $header): array
*/
protected function combineHeader(Iterator $iterator, array $header): Iterator
{
$formatter = fn (array $record): array => array_reduce(
$this->formatters,
fn (array $record, callable $formatter): array => $formatter($record),
$record
);

if ([] === $header) {
return $iterator;
return new MapIterator($iterator, $formatter(...));
}

$field_count = count($header);
$mapper = function (array $record) use ($header, $field_count): array {
$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);
}

/** @var array<string|null> $assocRecord */
$assocRecord = array_combine($header, $record);

return $assocRecord;
return $formatter($assocRecord);
};

return new MapIterator($iterator, $mapper);
Expand Down
43 changes: 43 additions & 0 deletions src/ReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -603,4 +603,47 @@ public function testExistsRecord(): void
self::assertFalse($this->csv->exists(fn (array $record) => array_key_exists('foobar', $record)));
self::assertTrue($this->csv->exists(fn (array $record) => count($record) < 5));
}

public function testReaderFormatterUsesOffset(): void
{
$csv = <<<CSV
FirstName,LastName,Year
John,Doe,2001
Jane,Doe,2005
CSV;

$reader = Reader::createFromString($csv);
$reader->setHeaderOffset(0);
self::assertSame([
[
'FirstName' => 'John',
'LastName' => 'Doe',
'Year' => '2001',
],
[
'FirstName' => 'Jane',
'LastName' => 'Doe',
'Year' => '2005',
],
], [...$reader]);

$reader->addFormatter(function (array $record): array {
$record['Year'] = (int) $record['Year'];

return $record;
});

self::assertSame([
[
'FirstName' => 'John',
'LastName' => 'Doe',
'Year' => 2001,
],
[
'FirstName' => 'Jane',
'LastName' => 'Doe',
'Year' => 2005,
],
], [...$reader]);
}
}

0 comments on commit f77d1af

Please sign in to comment.