Skip to content

Commit

Permalink
Adding Statement::select method
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Jan 9, 2024
1 parent 73038ac commit 3fe8c69
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 32 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All Notable changes to `Csv` will be documented in this file

## [Next](https://github.com/thephpleague/csv/compare/9.14.0...master) - TBD

### Added

- `Statement::select`

### Deprecated

- None

### Fixed

- `Reader::select` and `ResultSet::select` now internally use `Statement::select`

### Removed

- None

## [9.14.0](https://github.com/thephpleague/csv/compare/9.13.0...9.14.0) - 2023-12-29

### Added
Expand Down
20 changes: 20 additions & 0 deletions docs/9.0/reader/statement.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ $records = Statement::create()

<p class="message-notice">When called multiple times, each call overrides the last setting for these options.</p>

### Selecting columns

<p class="message-info">new in version <code>9.15.0</code>.</p>

You may not always want to select all columns from the tabular data. Using the `select` method,
you can specify which columns to use. The column can be specified by their name, if the instance
`getHeader` returns a non-empty array, or you can default to using the column offset. You
can even mix them both.

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

$reader = Reader::createFromPath('/path/to/file.csv');
$records = Statement::create()
->select(1, 3, 'field')
->process($reader);
// $records is a League\Csv\ResultSet instance with only 3 fields
```

## FragmentFinder

<p class="message-info">This mechanism is introduced with version <code>9.12.0</code>.</p>
Expand Down
1 change: 1 addition & 0 deletions docs/9.0/reader/tabular-data-reader.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ $reader = Reader::createFromPath('/path/to/my/file.csv')
```

<p class="message-notice">Added in version <code>9.12.0</code> for <code>Reader</code> and <code>ResultSet</code>.</p>
<p class="message-info"> Wraps the functionality of <code>Statement::select</code>.</p>

### matching, matchingFirst, matchingFirstOrFail

Expand Down
2 changes: 1 addition & 1 deletion src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ public function matchingFirstOrFail(string $expression): TabularDataReader

public function select(string|int ...$columns): TabularDataReader
{
return ResultSet::createFromTabularDataReader($this)->select(...$columns);
return Statement::create()->select(...$columns)->process($this);
}

/**
Expand Down
30 changes: 1 addition & 29 deletions src/ResultSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,35 +177,7 @@ public function sorted(Closure $orderBy): TabularDataReader

public function select(string|int ...$columns): TabularDataReader
{
$header = [];
$documentHeader = $this->getHeader();
$hasNoHeader = [] === $documentHeader;
foreach ($columns as $field) {
if (is_string($field)) {
if ($hasNoHeader) {
throw new InvalidArgument(__METHOD__.' can only use named column if the tabular data has a non-empty header.');
}

$index = array_search($field, $this->header, true);
if (false === $index) {
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
}

$header[$index] = $field;
continue;
}

if (!$hasNoHeader && !array_key_exists($field, $documentHeader)) {
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
}

$header[$field] = $documentHeader[$field] ?? $field;
}

/** @var array<int, string> $finalHeader */
$finalHeader = $hasNoHeader ? [] : $header;

return new self($this->combineHeader($header), $finalHeader);
return Statement::create()->select(...$columns)->process($this);
}

public function matching(string $expression): iterable
Expand Down
2 changes: 1 addition & 1 deletion src/Serializer/CastToEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private function cast(string $value): BackedEnum|UnitEnum
return $enum->getCase($value)->getValue();
}

$backedValue = 'int' === $enum->getBackingType()?->getName() ? filter_var($value, Type::Int->filterFlag()) : $value;
$backedValue = 'int' === $enum->getBackingType()->getName() ? filter_var($value, Type::Int->filterFlag()) : $value;

return $this->class::from($backedValue); /* @phpstan-ignore-line */
} catch (Throwable $exception) {
Expand Down
73 changes: 72 additions & 1 deletion src/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
use Iterator;
use LimitIterator;

use function array_key_exists;
use function array_reduce;
use function array_search;
use function array_values;
use function is_string;
use function trigger_error;

use const E_USER_DEPRECATED;

Expand All @@ -35,6 +40,8 @@ class Statement
protected int $offset = 0;
/** iterator maximum length. */
protected int $limit = -1;
/** @var array<string|int> */
protected array $select = [];

/**
* @throws Exception
Expand All @@ -49,6 +56,21 @@ public static function create(callable $where = null, int $offset = 0, int $limi
return $stmt->offset($offset)->limit($limit);
}

/**
* Sets the Iterator element columns.
*/
public function select(string|int ...$columns): self
{
if ($columns === $this->select) {
return $this;
}

$clone = clone $this;
$clone->select = $columns;

return $clone;
}

/**
* Sets the Iterator filter method.
*/
Expand Down Expand Up @@ -118,6 +140,7 @@ public function limit(int $limit): self
*
* @param array<string> $header an optional header to use instead of the CSV document header
*
* @throws InvalidArgument
* @throws SyntaxError
*/
public function process(TabularDataReader $tabular_data, array $header = []): TabularDataReader
Expand All @@ -136,7 +159,7 @@ public function process(TabularDataReader $tabular_data, array $header = []): Ta
/** @var Iterator<array-key, array<array-key, string|null>> $iterator */
$iterator = new LimitIterator($iterator, $this->offset, $this->limit);

return new ResultSet($iterator, $header);
return $this->applySelect($iterator, $header);
}

/**
Expand Down Expand Up @@ -172,4 +195,52 @@ protected function buildOrderBy(Iterator $iterator): Iterator

return $it;
}

/**
*
* @throws InvalidArgument
* @throws SyntaxError
*/
protected function applySelect(Iterator $records, array $recordsHeader): TabularDataReader
{
if ([] === $this->select) {
return new ResultSet($records, $recordsHeader);
}

$hasHeader = [] !== $recordsHeader;
$selectColumn = function (array $header, string|int $field) use ($recordsHeader, $hasHeader): array {
if (is_string($field)) {
$index = array_search($field, $recordsHeader, true);
if (false === $index) {
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
}

$header[$index] = $field;

return $header;
}

if ($hasHeader && !array_key_exists($field, $recordsHeader)) {
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
}

$header[$field] = $recordsHeader[$field] ?? $field;

return $header;
};

/** @var array<string> $header */
$header = array_reduce($this->select, $selectColumn, []);
$records = new MapIterator($records, function (array $record) use ($header): array {
$element = [];
$row = array_values($record);
foreach ($header as $offset => $headerName) {
$element[$headerName] = $row[$offset] ?? null;
}

return $element;
});

return new ResultSet($records, $hasHeader ? $header : []);
}
}

0 comments on commit 3fe8c69

Please sign in to comment.