Skip to content

Commit 3fe8c69

Browse files
committed
Adding Statement::select method
1 parent 73038ac commit 3fe8c69

File tree

7 files changed

+114
-32
lines changed

7 files changed

+114
-32
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22

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

5+
## [Next](https://github.com/thephpleague/csv/compare/9.14.0...master) - TBD
6+
7+
### Added
8+
9+
- `Statement::select`
10+
11+
### Deprecated
12+
13+
- None
14+
15+
### Fixed
16+
17+
- `Reader::select` and `ResultSet::select` now internally use `Statement::select`
18+
19+
### Removed
20+
21+
- None
22+
523
## [9.14.0](https://github.com/thephpleague/csv/compare/9.13.0...9.14.0) - 2023-12-29
624

725
### Added

docs/9.0/reader/statement.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ $records = Statement::create()
107107

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

110+
### Selecting columns
111+
112+
<p class="message-info">new in version <code>9.15.0</code>.</p>
113+
114+
You may not always want to select all columns from the tabular data. Using the `select` method,
115+
you can specify which columns to use. The column can be specified by their name, if the instance
116+
`getHeader` returns a non-empty array, or you can default to using the column offset. You
117+
can even mix them both.
118+
119+
```php
120+
use League\Csv\Reader;
121+
use League\Csv\Statement;
122+
123+
$reader = Reader::createFromPath('/path/to/file.csv');
124+
$records = Statement::create()
125+
->select(1, 3, 'field')
126+
->process($reader);
127+
// $records is a League\Csv\ResultSet instance with only 3 fields
128+
```
129+
110130
## FragmentFinder
111131

112132
<p class="message-info">This mechanism is introduced with version <code>9.12.0</code>.</p>

docs/9.0/reader/tabular-data-reader.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ $reader = Reader::createFromPath('/path/to/my/file.csv')
447447
```
448448

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

451452
### matching, matchingFirst, matchingFirstOrFail
452453

src/Reader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ public function matchingFirstOrFail(string $expression): TabularDataReader
395395

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

401401
/**

src/ResultSet.php

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -177,35 +177,7 @@ public function sorted(Closure $orderBy): TabularDataReader
177177

178178
public function select(string|int ...$columns): TabularDataReader
179179
{
180-
$header = [];
181-
$documentHeader = $this->getHeader();
182-
$hasNoHeader = [] === $documentHeader;
183-
foreach ($columns as $field) {
184-
if (is_string($field)) {
185-
if ($hasNoHeader) {
186-
throw new InvalidArgument(__METHOD__.' can only use named column if the tabular data has a non-empty header.');
187-
}
188-
189-
$index = array_search($field, $this->header, true);
190-
if (false === $index) {
191-
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
192-
}
193-
194-
$header[$index] = $field;
195-
continue;
196-
}
197-
198-
if (!$hasNoHeader && !array_key_exists($field, $documentHeader)) {
199-
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
200-
}
201-
202-
$header[$field] = $documentHeader[$field] ?? $field;
203-
}
204-
205-
/** @var array<int, string> $finalHeader */
206-
$finalHeader = $hasNoHeader ? [] : $header;
207-
208-
return new self($this->combineHeader($header), $finalHeader);
180+
return Statement::create()->select(...$columns)->process($this);
209181
}
210182

211183
public function matching(string $expression): iterable

src/Serializer/CastToEnum.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private function cast(string $value): BackedEnum|UnitEnum
8686
return $enum->getCase($value)->getValue();
8787
}
8888

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

9191
return $this->class::from($backedValue); /* @phpstan-ignore-line */
9292
} catch (Throwable $exception) {

src/Statement.php

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
use Iterator;
1919
use LimitIterator;
2020

21+
use function array_key_exists;
2122
use function array_reduce;
23+
use function array_search;
24+
use function array_values;
25+
use function is_string;
26+
use function trigger_error;
2227

2328
use const E_USER_DEPRECATED;
2429

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

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

59+
/**
60+
* Sets the Iterator element columns.
61+
*/
62+
public function select(string|int ...$columns): self
63+
{
64+
if ($columns === $this->select) {
65+
return $this;
66+
}
67+
68+
$clone = clone $this;
69+
$clone->select = $columns;
70+
71+
return $clone;
72+
}
73+
5274
/**
5375
* Sets the Iterator filter method.
5476
*/
@@ -118,6 +140,7 @@ public function limit(int $limit): self
118140
*
119141
* @param array<string> $header an optional header to use instead of the CSV document header
120142
*
143+
* @throws InvalidArgument
121144
* @throws SyntaxError
122145
*/
123146
public function process(TabularDataReader $tabular_data, array $header = []): TabularDataReader
@@ -136,7 +159,7 @@ public function process(TabularDataReader $tabular_data, array $header = []): Ta
136159
/** @var Iterator<array-key, array<array-key, string|null>> $iterator */
137160
$iterator = new LimitIterator($iterator, $this->offset, $this->limit);
138161

139-
return new ResultSet($iterator, $header);
162+
return $this->applySelect($iterator, $header);
140163
}
141164

142165
/**
@@ -172,4 +195,52 @@ protected function buildOrderBy(Iterator $iterator): Iterator
172195

173196
return $it;
174197
}
198+
199+
/**
200+
*
201+
* @throws InvalidArgument
202+
* @throws SyntaxError
203+
*/
204+
protected function applySelect(Iterator $records, array $recordsHeader): TabularDataReader
205+
{
206+
if ([] === $this->select) {
207+
return new ResultSet($records, $recordsHeader);
208+
}
209+
210+
$hasHeader = [] !== $recordsHeader;
211+
$selectColumn = function (array $header, string|int $field) use ($recordsHeader, $hasHeader): array {
212+
if (is_string($field)) {
213+
$index = array_search($field, $recordsHeader, true);
214+
if (false === $index) {
215+
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
216+
}
217+
218+
$header[$index] = $field;
219+
220+
return $header;
221+
}
222+
223+
if ($hasHeader && !array_key_exists($field, $recordsHeader)) {
224+
throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__);
225+
}
226+
227+
$header[$field] = $recordsHeader[$field] ?? $field;
228+
229+
return $header;
230+
};
231+
232+
/** @var array<string> $header */
233+
$header = array_reduce($this->select, $selectColumn, []);
234+
$records = new MapIterator($records, function (array $record) use ($header): array {
235+
$element = [];
236+
$row = array_values($record);
237+
foreach ($header as $offset => $headerName) {
238+
$element[$headerName] = $row[$offset] ?? null;
239+
}
240+
241+
return $element;
242+
});
243+
244+
return new ResultSet($records, $hasHeader ? $header : []);
245+
}
175246
}

0 commit comments

Comments
 (0)