Skip to content

Commit bee6cf8

Browse files
authored
Merge pull request thephpleague#379 from thephpleague/feature/tabular-data-reader-interface
Introducing the TabularDataReader interface
2 parents a3df6b7 + 62503d3 commit bee6cf8

File tree

9 files changed

+160
-120
lines changed

9 files changed

+160
-120
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@ All Notable changes to `Csv` will be documented in this file
77
### Added
88

99
- More return types and type parameters as supported in PHP7.2+
10+
- `League\Csv\Statement::create` named constructor to ease constraint builder instantiation
11+
- `League\Csv\Statement` can now also process `League\Csv\ResultSet` instances.
12+
- `League\Csv\TabularDataReader` interface to represent how to read tabular data
13+
- `League\Csv\ResultSet::getRecords` has an optional `$header` second argument to make the method works like `League\Csv\Reader::getRecords`
14+
- `League\Csv\ResultSet::createFromReader` create a new instance from `League\Csv\Reader`
1015

1116
### Deprecated
1217

1318
- Nothing
1419

1520
### Fixed
1621

17-
- Nothing
22+
- `League\Csv\Reader` no longer uses `__call` to implement `fetchOne`, `fetchPairs` and `fetchColumn` methods.
1823

1924
### Removed
2025

phpstan.tests.neon

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,5 @@ parameters:
99
path: tests/DetectDelimiterTest.php
1010
- message: '#Parameter \#1 \$document of static method League\\Csv\\Polyfill\\EmptyEscapeParser::parse\(\) expects League\\Csv\\Stream\|SplFileObject, stdClass given.#'
1111
path: tests/Polyfill/EmptyEscapeParserTest.php
12-
- message: '#Parameter \#1 \$records of method League\\Csv\\Statement::process\(\) expects League\\Csv\\Reader\|League\\Csv\\ResultSet, stdClass given.#'
13-
path: tests/ResultSetTest.php
14-
1512
reportUnmatchedIgnoredErrors: true
1613
checkMissingIterableValueType: false

src/Reader.php

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,8 @@
1313

1414
namespace League\Csv;
1515

16-
use BadMethodCallException;
1716
use CallbackFilterIterator;
18-
use Countable;
19-
use Generator;
2017
use Iterator;
21-
use IteratorAggregate;
2218
use JsonSerializable;
2319
use League\Csv\Polyfill\EmptyEscapeParser;
2420
use SplFileObject;
@@ -41,12 +37,8 @@
4137

4238
/**
4339
* A class to parse and read records from a CSV document.
44-
*
45-
* @method array fetchOne(int $nth_record = 0) Returns a single record from the CSV
46-
* @method Generator fetchColumn(string|int $column_index) Returns the next value from a single CSV record field
47-
* @method Generator fetchPairs(string|int $offset_index = 0, string|int $value_index = 1) Fetches the next key-value pairs from the CSV document
4840
*/
49-
class Reader extends AbstractCsv implements Countable, IteratorAggregate, JsonSerializable
41+
class Reader extends AbstractCsv implements TabularDataReader, JsonSerializable
5042
{
5143
/**
5244
* header offset.
@@ -109,11 +101,7 @@ public function getHeaderOffset(): ?int
109101
}
110102

111103
/**
112-
* Returns the CSV record used as header.
113-
*
114-
* The returned header is represented as an array of string values
115-
*
116-
* @return string[]
104+
* {@inheritDoc}
117105
*/
118106
public function getHeader(): array
119107
{
@@ -153,7 +141,6 @@ protected function setHeader(int $offset): array
153141

154142
/**
155143
* Returns the row at a given offset.
156-
*
157144
*/
158145
protected function seekRow(int $offset): array
159146
{
@@ -210,14 +197,25 @@ protected function removeBOM(array $record, int $bom_length, string $enclosure):
210197
/**
211198
* {@inheritdoc}
212199
*/
213-
public function __call(string $method, array $arguments)
200+
public function fetchColumn($index = 0): Iterator
214201
{
215-
static $whitelisted = ['fetchColumn' => 1, 'fetchOne' => 1, 'fetchPairs' => 1];
216-
if (isset($whitelisted[$method])) {
217-
return (new ResultSet($this->getRecords(), $this->getHeader()))->$method(...$arguments);
218-
}
202+
return ResultSet::createFromReader($this)->fetchColumn($index);
203+
}
219204

220-
throw new BadMethodCallException(sprintf('%s::%s() method does not exist', static::class, $method));
205+
/**
206+
* {@inheritdoc}
207+
*/
208+
public function fetchOne(int $nth_record = 0): array
209+
{
210+
return ResultSet::createFromReader($this)->fetchOne($nth_record);
211+
}
212+
213+
/**
214+
* {@inheritdoc}
215+
*/
216+
public function fetchPairs($offset_index = 0, $value_index = 1): Iterator
217+
{
218+
return ResultSet::createFromReader($this)->fetchPairs($offset_index, $value_index);
221219
}
222220

223221
/**
@@ -249,18 +247,7 @@ public function jsonSerialize(): array
249247
}
250248

251249
/**
252-
* Returns the CSV records as an iterator object.
253-
*
254-
* Each CSV record is represented as a simple array containing strings or null values.
255-
*
256-
* If the CSV document has a header record then each record is combined
257-
* to the header record and the header record is removed from the iterator.
258-
*
259-
* If the CSV document is inconsistent. Missing record fields are
260-
* filled with null values while extra record fields are strip from
261-
* the returned object.
262-
*
263-
* @param string[] $header an optional header to use instead of the CSV document header
250+
* {@inheritDoc}
264251
*/
265252
public function getRecords(array $header = []): Iterator
266253
{

src/ResultSet.php

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
namespace League\Csv;
1515

1616
use CallbackFilterIterator;
17-
use Countable;
18-
use Generator;
1917
use Iterator;
20-
use IteratorAggregate;
2118
use JsonSerializable;
2219
use LimitIterator;
2320
use function array_flip;
@@ -30,7 +27,7 @@
3027
/**
3128
* Represents the result set of a {@link Reader} processed by a {@link Statement}.
3229
*/
33-
class ResultSet implements Countable, IteratorAggregate, JsonSerializable
30+
class ResultSet implements TabularDataReader, JsonSerializable
3431
{
3532
/**
3633
* The CSV records collection.
@@ -51,8 +48,9 @@ class ResultSet implements Countable, IteratorAggregate, JsonSerializable
5148
*/
5249
public function __construct(Iterator $records, array $header)
5350
{
54-
$this->records = $records;
5551
$this->validateHeader($header);
52+
53+
$this->records = $records;
5654
$this->header = $header;
5755
}
5856

@@ -74,6 +72,14 @@ public function __destruct()
7472
unset($this->records);
7573
}
7674

75+
/**
76+
* Returns a new instance from a League\Csv\Reader object.
77+
*/
78+
public static function createFromReader(Reader $reader): self
79+
{
80+
return new self($reader->getRecords(), $reader->getHeader());
81+
}
82+
7783
/**
7884
* Returns the header associated with the result set.
7985
*
@@ -87,15 +93,15 @@ public function getHeader(): array
8793
/**
8894
* {@inheritdoc}
8995
*/
90-
public function getIterator(): Generator
96+
public function getIterator(): Iterator
9197
{
9298
return $this->getRecords();
9399
}
94100

95101
/**
96102
* {@inheritdoc}
97103
*/
98-
public function getRecords(array $header = []): Generator
104+
public function getRecords(array $header = []): Iterator
99105
{
100106
$this->validateHeader($header);
101107
$records = $this->combineHeader($header);
@@ -145,13 +151,7 @@ public function jsonSerialize(): array
145151
}
146152

147153
/**
148-
* Returns the nth record from the result set.
149-
*
150-
* By default if no index is provided the first record of the resultet is returned
151-
*
152-
* @param int $nth_record the CSV record offset
153-
*
154-
* @throws Exception if argument is lesser than 0
154+
* {@inheritdoc}
155155
*/
156156
public function fetchOne(int $nth_record = 0): array
157157
{
@@ -166,13 +166,9 @@ public function fetchOne(int $nth_record = 0): array
166166
}
167167

168168
/**
169-
* Returns a single column from the next record of the result set.
170-
*
171-
* By default if no value is supplied the first column is fetch
172-
*
173-
* @param string|int $index CSV column index
169+
* {@inheritdoc}
174170
*/
175-
public function fetchColumn($index = 0): Generator
171+
public function fetchColumn($index = 0): Iterator
176172
{
177173
$offset = $this->getColumnIndex($index, __METHOD__.'() expects the column index to be a valid string or integer, `%s` given');
178174
$filter = static function (array $record) use ($offset): bool {
@@ -246,17 +242,9 @@ protected function getColumnIndexByKey(int $index, string $error_message)
246242
}
247243

248244
/**
249-
* Returns the next key-value pairs from a result set (first
250-
* column is the key, second column is the value).
251-
*
252-
* By default if no column index is provided:
253-
* - the first column is used to provide the keys
254-
* - the second column is used to provide the value
255-
*
256-
* @param string|int $offset_index The column index to serve as offset
257-
* @param string|int $value_index The column index to serve as value
245+
* {@inheritdoc}
258246
*/
259-
public function fetchPairs($offset_index = 0, $value_index = 1): Generator
247+
public function fetchPairs($offset_index = 0, $value_index = 1): Iterator
260248
{
261249
$offset = $this->getColumnIndex($offset_index, __METHOD__.'() expects the offset index value to be a valid string or integer, `%s` given');
262250
$value = $this->getColumnIndex($value_index, __METHOD__.'() expects the value index value to be a valid string or integer, `%s` given');

src/Statement.php

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use CallbackFilterIterator;
1818
use Iterator;
1919
use LimitIterator;
20-
use TypeError;
2120
use function array_reduce;
2221

2322
/**
@@ -71,21 +70,21 @@ public static function create(callable $where = null, int $offset = 0, int $limi
7170
/**
7271
* Set the Iterator filter method.
7372
*/
74-
public function where(callable $callable): self
73+
public function where(callable $where): self
7574
{
7675
$clone = clone $this;
77-
$clone->where[] = $callable;
76+
$clone->where[] = $where;
7877

7978
return $clone;
8079
}
8180

8281
/**
8382
* Set an Iterator sorting callable function.
8483
*/
85-
public function orderBy(callable $callable): self
84+
public function orderBy(callable $order_by): self
8685
{
8786
$clone = clone $this;
88-
$clone->order_by[] = $callable;
87+
$clone->order_by[] = $order_by;
8988

9089
return $clone;
9190
}
@@ -135,26 +134,15 @@ public function limit(int $limit): self
135134
/**
136135
* Execute the prepared Statement on the {@link Reader} object.
137136
*
138-
* @param Reader|ResultSet $records
139-
* @param string[] $header an optional header to use instead of the CSV document header
137+
* @param string[] $header an optional header to use instead of the CSV document header
140138
*/
141-
public function process($records, array $header = []): ResultSet
139+
public function process(TabularDataReader $tabular_data, array $header = []): TabularDataReader
142140
{
143-
if (!($records instanceof Reader) && !($records instanceof ResultSet)) {
144-
throw new TypeError(sprintf(
145-
'%s expects parameter 1 to be a %s or a %s object, %s given',
146-
__METHOD__,
147-
ResultSet::class,
148-
Reader::class,
149-
get_class($records)
150-
));
151-
}
152-
153141
if ([] === $header) {
154-
$header = $records->getHeader();
142+
$header = $tabular_data->getHeader();
155143
}
156144

157-
$iterator = $records->getRecords($header);
145+
$iterator = $tabular_data->getRecords($header);
158146
$iterator = array_reduce($this->where, [$this, 'filter'], $iterator);
159147
$iterator = $this->buildOrderBy($iterator);
160148

0 commit comments

Comments
 (0)