summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorignace nyamagana butera <nyamsprod@gmail.com>2015-11-24 12:00:26 +0100
committerignace nyamagana butera <nyamsprod@gmail.com>2015-11-24 12:00:26 +0100
commitbfcc2d633a0160853684ecd0c9c82b6e3b3d32ef (patch)
tree0782a13735eac74199f11b63493f6e1808e3b2a0
parent69bafa6ff924fbf9effe4275d6eb16be81a853ef (diff)
parentc3a2ae5ccdc2ea05de734716f892d9b7f5f28774 (diff)
downloadcsv-bfcc2d633a0160853684ecd0c9c82b6e3b3d32ef.zip
csv-bfcc2d633a0160853684ecd0c9c82b6e3b3d32ef.tar.gz
csv-bfcc2d633a0160853684ecd0c9c82b6e3b3d32ef.tar.bz2
Merge pull request #137 from nyamsprod/features/improve-reader
Reader class complete rewrite
-rw-r--r--.travis.yml3
-rw-r--r--README.md2
-rw-r--r--composer.json2
-rw-r--r--scrutinizer.yml2
-rw-r--r--src/AbstractCsv.php24
-rw-r--r--src/Config/Controls.php27
-rw-r--r--src/Reader.php225
-rw-r--r--test/AbstractTestCase.php3
-rw-r--r--test/ControlsTest.php12
-rw-r--r--test/FactoryTest.php14
-rw-r--r--test/Plugin/ColumnConsistencyValidatorTest.php2
-rw-r--r--test/Plugin/NullValidatorTest.php2
-rw-r--r--test/Plugin/SkipNullValuesFormatterTest.php2
-rw-r--r--test/ReaderTest.php191
-rw-r--r--test/StreamFilterTest.php18
-rw-r--r--test/WriterTest.php15
-rw-r--r--test/data/foo.csv (renamed from test/foo.csv)0
-rw-r--r--test/data/newline.csv (renamed from test/newline.csv)0
18 files changed, 364 insertions, 180 deletions
diff --git a/.travis.yml b/.travis.yml
index 5798f9d..4235445 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,9 +4,6 @@ sudo: false
matrix:
include:
- - php: 5.4
- env:
- - COLLECT_COVERAGE=true
- php: 5.5
env:
- COLLECT_COVERAGE=true
diff --git a/README.md b/README.md
index 4a9d7bd..dfc0104 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ Full documentation can be found at [csv.thephpleague.com](http://csv.thephpleagu
System Requirements
-------
-You need **PHP >= 5.4.0** or **HHVM >= 3.2** and the `mbstring` extension to use `League\Csv` but the latest stable version of PHP/HHVM is recommended.
+You need **PHP >= 5.5.0** and the `mbstring` extension to use `League\Csv` but the latest stable version of PHP/HHVM is recommended.
Install
-------
diff --git a/composer.json b/composer.json
index 41cdf41..e23e011 100644
--- a/composer.json
+++ b/composer.json
@@ -37,7 +37,7 @@
}
},
"scripts": {
- "test": "vendor/bin/phpunit; vendor/bin/php-cs-fixer fix -v --diff --dry-run;"
+ "test": "phpunit; php-cs-fixer fix -v --diff --dry-run;"
},
"extra": {
"branch-alias": {
diff --git a/scrutinizer.yml b/scrutinizer.yml
index abd0a3d..a680ae9 100644
--- a/scrutinizer.yml
+++ b/scrutinizer.yml
@@ -20,4 +20,4 @@ checks:
tools:
external_code_coverage:
timeout: 1200
- runs: 3
+ runs: 2
diff --git a/src/AbstractCsv.php b/src/AbstractCsv.php
index bd07537..384b2b9 100644
--- a/src/AbstractCsv.php
+++ b/src/AbstractCsv.php
@@ -97,7 +97,7 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
* a path to a file
*
* @param SplFileObject|string $path The file path
- * @param string $open_mode the file open mode flag
+ * @param string $open_mode The file open mode flag
*/
protected function __construct($path, $open_mode = 'r+')
{
@@ -172,7 +172,7 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
* @param mixed $path file path
* @param string $open_mode the file open mode flag
*
- * @throws InvalidArgumentException If $path is a \SplTempFileObject object
+ * @throws InvalidArgumentException If $path is a SplTempFileObject object
*
* @return static
*/
@@ -203,7 +203,6 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
return (string) $str;
}
-
throw new InvalidArgumentException('Expected data must be a string or stringable');
}
@@ -237,27 +236,23 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
* The string must be an object that implements the `__toString` method,
* or a string
*
- * @param string $str the string
- * @param string $newline the newline character
+ * @param string|object $str the string
*
* @return static
*/
- public static function createFromString($str, $newline = "\n")
+ public static function createFromString($str)
{
$file = new SplTempFileObject();
$file->fwrite(static::validateString($str));
- $csv = static::createFromFileObject($file);
- $csv->setNewline($newline);
-
- return $csv;
+ return static::createFromFileObject($file);
}
/**
* Creates a {@link AbstractCsv} instance from another {@link AbstractCsv} object
*
- * @param string $class_name the class to be instantiated
- * @param string $open_mode the file open mode flag
+ * @param string $class the class to be instantiated
+ * @param string $open_mode the file open mode flag
*
* @return static
*/
@@ -285,7 +280,7 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
*/
public function newWriter($open_mode = 'r+')
{
- return $this->newInstance('\League\Csv\Writer', $open_mode);
+ return $this->newInstance(Writer::class, $open_mode);
}
/**
@@ -297,7 +292,7 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
*/
public function newReader($open_mode = 'r+')
{
- return $this->newInstance('\League\Csv\Reader', $open_mode);
+ return $this->newInstance(Reader::class, $open_mode);
}
/**
@@ -316,7 +311,6 @@ abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
if (false === ($int = filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => $minValue]]))) {
throw new InvalidArgumentException($errorMessage);
}
-
return $int;
}
}
diff --git a/src/Config/Controls.php b/src/Config/Controls.php
index 5f2c362..3432974 100644
--- a/src/Config/Controls.php
+++ b/src/Config/Controls.php
@@ -103,33 +103,6 @@ trait Controls
}
/**
- * Detects the CSV file delimiters
- *
- * Returns a associative array where each key represents
- * the number of occurences and each value a delimiter with the
- * given occurence
- *
- * This method returns incorrect informations when two delimiters
- * have the same occurrence count
- *
- * DEPRECATION WARNING! This method will be removed in the next major point release
- *
- * @deprecated deprecated since version 7.2
- *
- * @param int $nb_rows
- * @param string[] $delimiters additional delimiters
- *
- * @return string[]
- */
- public function detectDelimiterList($nb_rows = 1, array $delimiters = [])
- {
- $delimiters = array_merge([$this->delimiter, ',', ';', "\t"], $delimiters);
- $stats = $this->fetchDelimitersOccurrence($delimiters, $nb_rows);
-
- return array_flip(array_filter($stats));
- }
-
- /**
* Detect Delimiters occurences in the CSV
*
* Returns a associative array where each key represents
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'));
}
/**
diff --git a/test/AbstractTestCase.php b/test/AbstractTestCase.php
index a8280b7..9466650 100644
--- a/test/AbstractTestCase.php
+++ b/test/AbstractTestCase.php
@@ -4,9 +4,6 @@ namespace League\Csv\Test;
use PHPUnit_Framework_TestCase;
-/**
- * @group controls
- */
class AbstractTestCase extends PHPUnit_Framework_TestCase
{
protected function checkRequirements()
diff --git a/test/ControlsTest.php b/test/ControlsTest.php
index 4eebed7..f80364a 100644
--- a/test/ControlsTest.php
+++ b/test/ControlsTest.php
@@ -7,8 +7,6 @@ use League\Csv\Writer;
use SplFileObject;
use SplTempFileObject;
-date_default_timezone_set('UTC');
-
/**
* @group controls
*/
@@ -93,7 +91,7 @@ class ControlsTest extends AbstractTestCase
public function testDetectDelimiterList()
{
- $this->assertSame([4 => ','], $this->csv->detectDelimiterList());
+ $this->assertSame([',' => 4], $this->csv->fetchDelimitersOccurrence([',']));
}
/**
@@ -102,7 +100,7 @@ class ControlsTest extends AbstractTestCase
*/
public function testDetectDelimiterListWithInvalidRowLimit()
{
- $this->csv->detectDelimiterList(-4);
+ $this->csv->fetchDelimitersOccurrence([','], -4);
}
public function testDetectDelimiterListWithNoCSV()
@@ -110,7 +108,7 @@ class ControlsTest extends AbstractTestCase
$file = new SplTempFileObject();
$file->fwrite("How are you today ?\nI'm doing fine thanks!");
$csv = Writer::createFromFileObject($file);
- $this->assertSame([], $csv->detectDelimiterList(5, ['toto', '|']));
+ $this->assertSame(['|' => 0], $csv->fetchDelimitersOccurrence(['toto', '|'], 5));
}
public function testDetectDelimiterListWithInconsistentCSV()
@@ -124,7 +122,7 @@ class ControlsTest extends AbstractTestCase
$data->fputcsv(['toto', 'tata', 'tutu']);
$csv = Writer::createFromFileObject($data);
- $this->assertSame([12 => '|', 4 => ';'], $csv->detectDelimiterList(5, ['|']));
+ $this->assertSame(['|' => 12, ';' => 4], $csv->fetchDelimitersOccurrence(['|', ';'], 5));
}
/**
@@ -197,7 +195,7 @@ class ControlsTest extends AbstractTestCase
public function testAppliedFlags($flag, $line_count)
{
$path = __DIR__.'/data/tmp.txt';
- $obj = new SplFileObject($path, 'w+');
+ $obj = new SplFileObject($path, 'w+');
$obj->fwrite("1st\n2nd\n");
$reader = Reader::createFromFileObject($obj);
$reader->setFlags($flag);
diff --git a/test/FactoryTest.php b/test/FactoryTest.php
index e35b402..f696176 100644
--- a/test/FactoryTest.php
+++ b/test/FactoryTest.php
@@ -2,7 +2,7 @@
namespace League\Csv\Test;
-use DateTime;
+use ArrayIterator;
use League\Csv\Reader;
use SplFileInfo;
use SplFileObject;
@@ -15,21 +15,21 @@ class FactoryTest extends AbstractTestCase
{
public function testCreateFromPathWithFilePath()
{
- $path = __DIR__.'/foo.csv';
+ $path = __DIR__.'/data/foo.csv';
$csv = Reader::createFromPath($path);
$this->assertSame($path, $csv->getIterator()->getRealPath());
}
public function testCreateFromPathWithSplFileInfo()
{
- $path = __DIR__.'/foo.csv';
+ $path = __DIR__.'/data/foo.csv';
$csv = Reader::createFromPath(new SplFileInfo($path));
$this->assertSame($path, $csv->getIterator()->getRealPath());
}
public function testCreateFromPathWithPHPWrapper()
{
- $path = __DIR__.'/foo.csv';
+ $path = __DIR__.'/data/foo.csv';
$csv = Reader::createFromPath('php://filter/read=string.toupper/resource='.$path);
$this->assertFalse($csv->getIterator()->getRealPath());
}
@@ -45,9 +45,9 @@ class FactoryTest extends AbstractTestCase
/**
* @expectedException InvalidArgumentException
*/
- public function testCreateFromPathWithUnStringableObject()
+ public function testCreateFromPathWithInvalidObject()
{
- Reader::createFromPath(new DateTime());
+ Reader::createFromPath(new ArrayIterator([]));
}
public function testCreateFromString()
@@ -67,7 +67,7 @@ class FactoryTest extends AbstractTestCase
public function testCreateFromFileObjectWithSplFileObject()
{
- $path = __DIR__.'/foo.csv';
+ $path = __DIR__.'/data/foo.csv';
$obj = new SplFileObject($path);
$reader = Reader::createFromFileObject($obj);
$this->assertInstanceof('League\Csv\Reader', $reader);
diff --git a/test/Plugin/ColumnConsistencyValidatorTest.php b/test/Plugin/ColumnConsistencyValidatorTest.php
index 43f023a..e49e770 100644
--- a/test/Plugin/ColumnConsistencyValidatorTest.php
+++ b/test/Plugin/ColumnConsistencyValidatorTest.php
@@ -22,7 +22,7 @@ class ColumnConsistencyValidatorTest extends AbstractTestCase
public function tearDown()
{
- $csv = new SplFileObject(dirname(__DIR__).'/foo.csv', 'w');
+ $csv = new SplFileObject(dirname(__DIR__).'/data/foo.csv', 'w');
$csv->setCsvControl();
$csv->fputcsv(['john', 'doe', 'john.doe@example.com'], ',', '"');
$this->csv = null;
diff --git a/test/Plugin/NullValidatorTest.php b/test/Plugin/NullValidatorTest.php
index 6e351ce..c4114ad 100644
--- a/test/Plugin/NullValidatorTest.php
+++ b/test/Plugin/NullValidatorTest.php
@@ -23,7 +23,7 @@ class NullValidatorTest extends AbstractTestCase
public function tearDown()
{
- $csv = new SplFileObject(dirname(__DIR__).'/foo.csv', 'w');
+ $csv = new SplFileObject(dirname(__DIR__).'/data/foo.csv', 'w');
$csv->setCsvControl();
$csv->fputcsv(['john', 'doe', 'john.doe@example.com'], ',', '"');
$this->csv = null;
diff --git a/test/Plugin/SkipNullValuesFormatterTest.php b/test/Plugin/SkipNullValuesFormatterTest.php
index 8f203da..3e0787e 100644
--- a/test/Plugin/SkipNullValuesFormatterTest.php
+++ b/test/Plugin/SkipNullValuesFormatterTest.php
@@ -23,7 +23,7 @@ class SkipNullValuesFormatterTest extends AbstractTestCase
public function tearDown()
{
- $csv = new SplFileObject(dirname(__DIR__).'/foo.csv', 'w');
+ $csv = new SplFileObject(dirname(__DIR__).'/data/foo.csv', 'w');
$csv->setCsvControl();
$csv->fputcsv(['john', 'doe', 'john.doe@example.com'], ',', '"');
$this->csv = null;
diff --git a/test/ReaderTest.php b/test/ReaderTest.php
index d650c51..9a0c630 100644
--- a/test/ReaderTest.php
+++ b/test/ReaderTest.php
@@ -3,6 +3,7 @@
namespace League\Csv\Test;
use League\Csv\Reader;
+use League\Csv\Writer;
use SplTempFileObject;
/**
@@ -151,10 +152,21 @@ class ReaderTest extends AbstractTestCase
$this->assertContains(['JANE', 'DOE', 'JANE.DOE@EXAMPLE.COM'], $res);
}
- public function testFetchAssoc()
+ public function testFetchAssocReturnsArray()
{
$keys = ['firstname', 'lastname', 'email'];
$res = $this->csv->fetchAssoc($keys);
+ $this->assertInternalType('array', $res);
+ foreach ($res as $offset => $row) {
+ $this->assertSame($keys, array_keys($row));
+ }
+ }
+
+ public function testFetchAssocReturnsIterator()
+ {
+ $keys = ['firstname', 'lastname', 'email'];
+ $res = $this->csv->setReturnType(Reader::TYPE_ITERATOR)->fetchAssoc($keys);
+ $this->assertInstanceof('\Iterator', $res);
foreach ($res as $offset => $row) {
$this->assertSame($keys, array_keys($row));
}
@@ -216,15 +228,6 @@ class ReaderTest extends AbstractTestCase
}
/**
- * @expectedException \InvalidArgumentException
- */
- public function testFetchAssocThrowsExceptionWithNonUniqueAssocKeys()
- {
- $keys = ['firstname', 'lastname', 'firstname'];
- $this->csv->fetchAssoc($keys);
- }
-
- /**
* @param $expected
* @dataProvider validBOMSequences
*/
@@ -364,21 +367,19 @@ class ReaderTest extends AbstractTestCase
];
}
- public function testFetchCol()
+ public function testFetchColumn()
{
- $this->csv->addFilter(function ($row) {
- return $row != [null];
-
- });
- $this->assertSame(['john', 'jane'], $this->csv->fetchColumn(0));
- $this->csv->addFilter(function ($row) {
- return $row != [null];
+ $this->assertContains('john', $this->csv->fetchColumn(0));
+ $this->assertContains('jane', $this->csv->fetchColumn());
+ }
- });
- $this->assertSame(['john', 'jane'], $this->csv->fetchColumn());
+ public function testFetchColumnReturnsIterator()
+ {
+ $this->assertContains('john', $this->csv->setReturnType(Reader::TYPE_ITERATOR)->fetchColumn(0));
+ $this->assertContains('jane', $this->csv->setReturnType(Reader::TYPE_ITERATOR)->fetchColumn());
}
- public function testFetchColInconsistentColumnCSV()
+ public function testFetchColumnInconsistentColumnCSV()
{
$raw = [
['john', 'doe'],
@@ -390,16 +391,11 @@ class ReaderTest extends AbstractTestCase
$file->fputcsv($row);
}
$csv = Reader::createFromFileObject($file);
- $this->csv->addFilter(function ($row) {
- return $row != [null];
-
- });
$res = $csv->fetchColumn(2);
- $this->assertInternalType('array', $res);
$this->assertCount(1, $res);
}
- public function testFetchColEmptyCol()
+ public function testFetchColumnEmptyCol()
{
$raw = [
['john', 'doe'],
@@ -412,31 +408,23 @@ class ReaderTest extends AbstractTestCase
}
$csv = Reader::createFromFileObject($file);
$res = $csv->fetchColumn(2);
- $this->csv->addFilter(function ($row) {
- return $row != [null];
-
- });
- $this->assertInternalType('array', $res);
$this->assertCount(0, $res);
}
- public function testFetchColCallback()
+ public function testFetchColumnCallback()
{
$func = function ($value) {
- return array_map('strtoupper', $value);
+ return strtoupper($value);
};
- $this->csv->addFilter(function ($row) {
- return $row != [null];
-
- });
- $this->assertSame(['JOHN', 'JANE'], $this->csv->fetchColumn(0, $func));
+ $iterator = $this->csv->fetchColumn(0, $func);
+ $this->assertSame(['JOHN', 'JANE'], $iterator);
}
/**
* @expectedException \InvalidArgumentException
*/
- public function testFetchColFailure()
+ public function testFetchColumnFailure()
{
$this->csv->fetchColumn('toto');
}
@@ -482,6 +470,127 @@ class ReaderTest extends AbstractTestCase
public function testGetWriter()
{
- $this->assertInstanceOf('\League\Csv\Writer', $this->csv->newWriter());
+ $this->assertInstanceOf(Writer::class, $this->csv->newWriter());
+ }
+
+ /**
+ * @dataProvider fetchPairsDataProvider
+ */
+ public function testFetchPairsIteratorMode($key, $value, $callable, $expected)
+ {
+ $iterator = $this->csv->setReturnType(Reader::TYPE_ITERATOR)->fetchPairs($key, $value, $callable);
+ foreach ($iterator as $key => $value) {
+ $res = current($expected);
+ $this->assertSame($value, $res[$key]);
+ next($expected);
+ }
+ }
+
+ public function fetchPairsDataProvider()
+ {
+ return [
+ 'default values' => [
+ 'key' => 0,
+ 'value' => 1,
+ 'callable' => null,
+ 'expected' => [
+ ['john' => 'doe'],
+ ['jane' => 'doe'],
+ ],
+ ],
+ 'changed key order' => [
+ 'key' => 1,
+ 'value' => 0,
+ 'callable' => null,
+ 'expected' => [
+ ['doe' => 'john'],
+ ['doe' => 'jane'],
+ ],
+ ],
+ 'with callback' => [
+ 'key' => 0,
+ 'value' => 1,
+ 'callable' => function ($row) {
+ return [
+ strtoupper($row[0]),
+ strtoupper($row[1]),
+ ];
+ },
+ 'expected' => [
+ ['JOHN' => 'DOE'],
+ ['JANE' => 'DOE'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider fetchPairsArrayDataProvider
+ */
+ public function testFetchPairsArrayMode($key, $value, $callable, $expected)
+ {
+ $array = $this->csv->fetchPairs($key, $value, $callable);
+ $this->assertSame($expected, $array);
+ }
+
+ public function fetchPairsArrayDataProvider()
+ {
+ return [
+ 'default values' => [
+ 'key' => 0,
+ 'value' => 1,
+ 'callable' => null,
+ 'expected' => ['john' => 'doe', 'jane' => 'doe'],
+ ],
+ 'changed key order' => [
+ 'key' => 1,
+ 'value' => 0,
+ 'callable' => null,
+ 'expected' => ['doe' => 'jane'],
+ ],
+ 'with callback' => [
+ 'key' => 0,
+ 'value' => 1,
+ 'callable' => function ($row) {
+ return [
+ strtoupper($row[0]),
+ strtoupper($row[1]),
+ ];
+ },
+ 'expected' => ['JOHN' => 'DOE', 'JANE' => 'DOE'],
+ ],
+ ];
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testReturnTypeThrowsException()
+ {
+ $this->csv->setReturnType('toto');
+ }
+
+ /**
+ * @dataProvider readerReturnTypeProvider
+ */
+ public function testReturnTypeResetBetweenCallToArray($method, array $args = [])
+ {
+ $this->assertSame(Reader::TYPE_ARRAY, $this->csv->getReturnType());
+ $this->csv->setReturnType(Reader::TYPE_ITERATOR);
+ call_user_func_array([$this->csv, $method], $args);
+ $this->assertSame(Reader::TYPE_ARRAY, $this->csv->getReturnType());
+ }
+
+ public function readerReturnTypeProvider()
+ {
+ return [
+ ['fetch'],
+ ['fetchOne'],
+ ['fetchAll'],
+ ['fetchColumn'],
+ ['fetchPairs'],
+ ['fetchAssoc'],
+ ['each', [function (array $row) { return true; }]],
+ ];
}
}
diff --git a/test/StreamFilterTest.php b/test/StreamFilterTest.php
index 7bb8a7b..fe81c48 100644
--- a/test/StreamFilterTest.php
+++ b/test/StreamFilterTest.php
@@ -15,7 +15,7 @@ class StreamFilterTest extends AbstractTestCase
{
public function testInitStreamFilterWithWriterStream()
{
- $filter = 'php://filter/write=string.rot13/resource='.__DIR__.'/foo.csv';
+ $filter = 'php://filter/write=string.rot13/resource='.__DIR__.'/data/foo.csv';
$csv = Reader::createFromPath($filter);
$this->assertTrue($csv->hasStreamFilter('string.rot13'));
$this->assertSame(STREAM_FILTER_WRITE, $csv->getStreamFilterMode());
@@ -23,7 +23,7 @@ class StreamFilterTest extends AbstractTestCase
public function testInitStreamFilterWithReaderStream()
{
- $filter = 'php://filter/read=string.toupper/resource='.__DIR__.'/foo.csv';
+ $filter = 'php://filter/read=string.toupper/resource='.__DIR__.'/data/foo.csv';
$csv = Reader::createFromPath($filter);
$this->assertTrue($csv->hasStreamFilter('string.toupper'));
$this->assertSame(STREAM_FILTER_READ, $csv->getStreamFilterMode());
@@ -31,7 +31,7 @@ class StreamFilterTest extends AbstractTestCase
public function testInitStreamFilterWithBothStream()
{
- $filter = 'php://filter/string.toupper/resource='.__DIR__.'/foo.csv';
+ $filter = 'php://filter/string.toupper/resource='.__DIR__.'/data/foo.csv';
$csv = Reader::createFromPath($filter);
$this->assertTrue($csv->hasStreamFilter('string.toupper'));
$this->assertSame(STREAM_FILTER_ALL, $csv->getStreamFilterMode());
@@ -42,12 +42,12 @@ class StreamFilterTest extends AbstractTestCase
*/
public function testInitStreamFilterWithSplFileObject()
{
- Reader::createFromFileObject(new SplFileObject(__DIR__.'/foo.csv'))->getStreamFilterMode();
+ Reader::createFromFileObject(new SplFileObject(__DIR__.'/data/foo.csv'))->getStreamFilterMode();
}
public function testappendStreamFilter()
{
- $csv = Reader::createFromPath(__DIR__.'/foo.csv');
+ $csv = Reader::createFromPath(__DIR__.'/data/foo.csv');
$csv->appendStreamFilter('string.toupper');
foreach ($csv->getIterator() as $row) {
$this->assertSame($row, ['JOHN', 'DOE', 'JOHN.DOE@EXAMPLE.COM']);
@@ -79,7 +79,7 @@ class StreamFilterTest extends AbstractTestCase
*/
public function testaddMultipleStreamFilter()
{
- $csv = Reader::createFromPath(__DIR__.'/foo.csv');
+ $csv = Reader::createFromPath(__DIR__.'/data/foo.csv');
$csv->appendStreamFilter('string.tolower');
$csv->prependStreamFilter('string.rot13');
$csv->appendStreamFilter('string.toupper');
@@ -105,7 +105,7 @@ class StreamFilterTest extends AbstractTestCase
public function testGetFilterPath()
{
- $csv = Writer::createFromPath(__DIR__.'/foo.csv');
+ $csv = Writer::createFromPath(__DIR__.'/data/foo.csv');
$csv->appendStreamFilter('string.rot13');
$csv->prependStreamFilter('string.toupper');
$this->assertFalse($csv->getIterator()->getRealPath());
@@ -113,7 +113,7 @@ class StreamFilterTest extends AbstractTestCase
public function testGetFilterPathWithAllStream()
{
- $filter = 'php://filter/string.toupper/resource='.__DIR__.'/foo.csv';
+ $filter = 'php://filter/string.toupper/resource='.__DIR__.'/data/foo.csv';
$csv = Reader::createFromPath($filter);
$this->assertFalse($csv->getIterator()->getRealPath());
}
@@ -121,7 +121,7 @@ class StreamFilterTest extends AbstractTestCase
public function testSetStreamFilterWriterNewLine()
{
stream_filter_register(FilterReplace::FILTER_NAME.'*', '\lib\FilterReplace');
- $csv = Writer::createFromPath(__DIR__.'/newline.csv');
+ $csv = Writer::createFromPath(__DIR__.'/data/newline.csv');
$csv->appendStreamFilter(FilterReplace::FILTER_NAME."\r\n:\n");
$this->assertTrue($csv->hasStreamFilter(FilterReplace::FILTER_NAME."\r\n:\n"));
$csv->insertOne([1, 'two', 3, "new\r\nline"]);
diff --git a/test/WriterTest.php b/test/WriterTest.php
index 7ba1ef4..d21a9a6 100644
--- a/test/WriterTest.php
+++ b/test/WriterTest.php
@@ -21,7 +21,7 @@ class WriterTest extends AbstractTestCase
public function tearDown()
{
- $csv = new SplFileObject(__DIR__.'/foo.csv', 'w');
+ $csv = new SplFileObject(__DIR__.'/data/foo.csv', 'w');
$csv->setCsvControl();
$csv->fputcsv(['john', 'doe', 'john.doe@example.com'], ',', '"');
$this->csv = null;
@@ -29,7 +29,7 @@ class WriterTest extends AbstractTestCase
public function testSupportsStreamFilter()
{
- $csv = Writer::createFromPath(__DIR__.'/foo.csv');
+ $csv = Writer::createFromPath(__DIR__.'/data/foo.csv');
$this->assertTrue($csv->isActiveStreamFilter());
$csv->appendStreamFilter('string.toupper');
$csv->insertOne(['jane', 'doe', 'jane@example.com']);
@@ -51,7 +51,7 @@ class WriterTest extends AbstractTestCase
public function testInsertNormalFile()
{
- $csv = Writer::createFromPath(__DIR__.'/foo.csv', 'a+');
+ $csv = Writer::createFromPath(__DIR__.'/data/foo.csv', 'a+');
$csv->insertOne(['jane', 'doe', 'jane.doe@example.com']);
$this->assertContains(['jane', 'doe', 'jane.doe@example.com'], $csv);
}
@@ -112,15 +112,6 @@ class WriterTest extends AbstractTestCase
$this->assertSame("jane,doe\r\n", (string) $csv);
}
- public function testCustomNewlineFromCreateFromString()
- {
- $expected = "\r\n";
- $raw = 'john,doe,john.doe@example.com'.PHP_EOL
- .'jane,doe,jane.doe@example.com'.PHP_EOL;
- $csv = Writer::createFromString($raw, $expected);
- $this->assertSame($expected, $csv->getNewline());
- }
-
public function testAddValidationRules()
{
$func = function (array $row) {
diff --git a/test/foo.csv b/test/data/foo.csv
index 2f6d89f..2f6d89f 100644
--- a/test/foo.csv
+++ b/test/data/foo.csv
diff --git a/test/newline.csv b/test/data/newline.csv
index a9da13c..a9da13c 100644
--- a/test/newline.csv
+++ b/test/data/newline.csv