diff options
Diffstat (limited to 'src/Reader.php')
-rw-r--r-- | src/Reader.php | 225 |
1 files changed, 175 insertions, 50 deletions
diff --git a/src/Reader.php b/src/Reader.php index e422d31..db075c4 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -12,12 +12,13 @@ */ namespace League\Csv; -use CallbackFilterIterator; +use Generator; use InvalidArgumentException; use Iterator; use League\Csv\Modifier\MapIterator; use LimitIterator; use SplFileObject; +use UnexpectedValueException; /** * A class to manage extracting and filtering a CSV @@ -28,29 +29,56 @@ use SplFileObject; */ class Reader extends AbstractCsv { + const TYPE_ARRAY = 1; + + const TYPE_ITERATOR = 2; + /** * @inheritdoc */ protected $stream_filter_mode = STREAM_FILTER_READ; /** - * Returns a Filtered Iterator + * Reader return type + * + * @var int + */ + protected $returnType = self::TYPE_ARRAY; + + /** + * Returns the return type for the next fetch call * - * DEPRECATION WARNING! This method will be removed in the next major point release + * @return int + */ + public function getReturnType() + { + return $this->returnType; + } + + /** + * Set the return type for the next fetch call * - * @deprecated deprecated since version 7.2 + * @param int $type * - * @return Iterator + * @throws UnexpectedValueException If the value is not one of the defined constant + * + * @return static */ - public function query(callable $callable = null) + public function setReturnType($type) { - return $this->fetch($callable); + $modes = [self::TYPE_ARRAY => 1, self::TYPE_ITERATOR => 1]; + if (!isset($modes[$type])) { + throw new UnexpectedValueException('Unknown return type'); + } + $this->returnType = $type; + + return $this; } /** * Return a Filtered Iterator * - * @param callable $callable a callable function to be applied to each Iterator item + * @param callable|null $callable a callable function to be applied to each Iterator item * * @return Iterator */ @@ -64,6 +92,7 @@ class Reader extends AbstractCsv $iterator = $this->applyIteratorFilter($iterator); $iterator = $this->applyIteratorSortBy($iterator); $iterator = $this->applyIteratorInterval($iterator); + $this->returnType = self::TYPE_ARRAY; if (!is_null($callable)) { return new MapIterator($iterator, $callable); } @@ -72,6 +101,38 @@ class Reader extends AbstractCsv } /** + * Returns a sequential array of all CSV lines + * + * The callable function will be applied to each Iterator item + * + * @param callable $callable a callable function + * + * @return array + */ + public function fetchAll(callable $callable = null) + { + return $this->applyReturnType(self::TYPE_ARRAY, $this->fetch($callable), false); + } + + /** + * Convert the Iterator into an array depending on the selected return type + * + * @param int $type + * @param Iterator $iterator + * @param bool $use_keys Whether to use the iterator element keys as index + * + * @return Iterator|array + */ + protected function applyReturnType($type, Iterator $iterator, $use_keys = true) + { + if (self::TYPE_ARRAY == $type) { + return iterator_to_array($iterator, $use_keys); + } + + return $iterator; + } + + /** * Applies a callback function on the CSV * * The callback function must return TRUE in order to continue @@ -102,6 +163,8 @@ class Reader extends AbstractCsv /** * Returns a single row from the CSV * + * By default if no offset is provided the first row of the CSV is selected + * * @param int $offset * * @throws InvalidArgumentException If the $offset is not a valid Integer @@ -119,49 +182,104 @@ class Reader extends AbstractCsv } /** - * Returns a sequential array of all CSV lines + * Returns a single column from the CSV data * - * The callable function will be applied to each Iterator item + * The callable function will be applied to each value to be return * - * @param callable $callable a callable function + * By default if no column index is provided the first column of the CSV is selected * - * @return array + * @param int $column_index field Index + * @param callable|null $callable a callable function + * + * @throws InvalidArgumentException If the column index is not a positive integer or 0 + * + * @return Iterator|array */ - public function fetchAll(callable $callable = null) + public function fetchColumn($columnIndex = 0, callable $callable = null) { - return iterator_to_array($this->fetch($callable), false); + $this->assertValidColumnIndex($columnIndex); + + $filterColumn = function ($row) use ($columnIndex) { + return array_key_exists($columnIndex, $row); + }; + + $selectColumn = function ($row) use ($columnIndex) { + return $row[$columnIndex]; + }; + + $this->addFilter($filterColumn); + $type = $this->returnType; + $iterator = $this->fetch($selectColumn); + if (!is_null($callable)) { + $iterator = new MapIterator($iterator, $callable); + } + + return $this->applyReturnType($type, $iterator, false); } /** - * Returns a single column from the CSV data + * Validate a CSV row index * - * The callable function will be applied to each value to be return - * - * @param int $column_index field Index - * @param callable $callable a callable function + * @param int $index * * @throws InvalidArgumentException If the column index is not a positive integer or 0 - * - * @return array */ - public function fetchColumn($column_index = 0, callable $callable = null) + protected function assertValidColumnIndex($index) { - if (false === filter_var($column_index, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { - throw new InvalidArgumentException( - 'the column index must be a positive integer or 0' - ); + if (false === filter_var($index, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { + throw new InvalidArgumentException('the column index must be a positive integer or 0'); } - $filterColumn = function ($row) use ($column_index) { - return array_key_exists($column_index, $row); + } + + /** + * Retrive CSV data as pairs + * + * Fetches an associative array of all rows as key-value pairs (first + * column is the key, second column is the value). + * + * By default if no column index is provided: + * - the first CSV column is used to provide the keys + * - the second CSV column is used to provide the value + * + * @param int $offsetColumnIndex The column index to server as offset + * @param int $valueColumnIndex The column index to server as value + * @param callable|null $callable Callback function to run for each element in each array + * + * @return Generator|array + */ + public function fetchPairs($offsetColumnIndex = 0, $valueColumnIndex = 1, callable $callable = null) + { + $this->assertValidColumnIndex($offsetColumnIndex); + $this->assertValidColumnIndex($valueColumnIndex); + $filterPairs = function ($row) use ($offsetColumnIndex, $valueColumnIndex) { + return array_key_exists($offsetColumnIndex, $row) && array_key_exists($valueColumnIndex, $row); }; - $selectColumn = function ($row) use ($column_index) { - return $row[$column_index]; + $selectPairs = function ($row) use ($offsetColumnIndex, $valueColumnIndex) { + return [$row[$offsetColumnIndex], $row[$valueColumnIndex]]; }; + $this->addFilter($filterPairs); + $type = $this->returnType; + $iterator = $this->fetch($selectPairs); + + if (!is_null($callable)) { + $iterator = new MapIterator($iterator, $callable); + } - $iterator = $this->fetch($callable); - $iterator = new CallbackFilterIterator($iterator, $filterColumn); + return $this->applyReturnType($type, $this->fetchPairsGenerator($iterator)); + } - return iterator_to_array(new MapIterator($iterator, $selectColumn), false); + /** + * Return the key/pairs as a PHP generator + * + * @param Iterator $iterator + * + * @return Generator + */ + protected function fetchPairsGenerator(Iterator $iterator) + { + foreach ($iterator as $row) { + yield $row[0] => $row[1]; + } } /** @@ -177,7 +295,7 @@ class Reader extends AbstractCsv * * @throws InvalidArgumentException If the submitted keys are invalid * - * @return Iterator + * @return Iterator|array */ public function fetchAssoc($offset_or_keys = 0, callable $callable = null) { @@ -191,7 +309,11 @@ class Reader extends AbstractCsv return array_combine($keys, $row); }; - return iterator_to_array(new MapIterator($this->fetch($callable), $combineArray), false); + return $this->applyReturnType( + $this->returnType, + new MapIterator($this->fetch($callable), $combineArray), + false + ); } /** @@ -207,7 +329,9 @@ class Reader extends AbstractCsv protected function getAssocKeys($offset_or_keys) { if (is_array($offset_or_keys)) { - return $this->validateAssocKeys($offset_or_keys); + $this->assertValidAssocKeys($offset_or_keys); + + return $offset_or_keys; } if (false === filter_var($offset_or_keys, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) { @@ -215,7 +339,7 @@ class Reader extends AbstractCsv } $keys = $this->getRow($offset_or_keys); - $keys = $this->validateAssocKeys($keys); + $this->assertValidAssocKeys($keys); $filterOutRow = function ($row, $rowIndex) use ($offset_or_keys) { return is_array($row) && $rowIndex != $offset_or_keys; }; @@ -231,22 +355,23 @@ class Reader extends AbstractCsv * * @throws InvalidArgumentException If the submitted array fails the assertion */ - protected function validateAssocKeys(array $keys) + protected function assertValidAssocKeys(array $keys) { - if (empty($keys)) { - throw new InvalidArgumentException('The array can not be empty'); - } - - foreach ($keys as &$str) { - $str = $this->validateString($str); - } - unset($str); - - if ($keys == array_unique($keys)) { - return $keys; + if (empty($keys) || $keys !== array_unique(array_filter($keys, [$this, 'isValidKey']))) { + throw new InvalidArgumentException('Use a flat array with unique string values'); } + } - throw new InvalidArgumentException('The array must contain unique values'); + /** + * Returns whether the submitted value can be used as string + * + * @param mixed $value + * + * @return bool + */ + protected function isValidKey($value) + { + return is_scalar($value) || (is_object($value) && method_exists($value, '__toString')); } /** |