summaryrefslogtreecommitdiffstats
path: root/src/Reader.php
diff options
context:
space:
mode:
Diffstat (limited to 'src/Reader.php')
-rw-r--r--src/Reader.php225
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'));
}
/**