diff options
author | ignace nyamagana butera <nyamsprod@gmail.com> | 2015-11-24 12:00:26 +0100 |
---|---|---|
committer | ignace nyamagana butera <nyamsprod@gmail.com> | 2015-11-24 12:00:26 +0100 |
commit | bfcc2d633a0160853684ecd0c9c82b6e3b3d32ef (patch) | |
tree | 0782a13735eac74199f11b63493f6e1808e3b2a0 | |
parent | 69bafa6ff924fbf9effe4275d6eb16be81a853ef (diff) | |
parent | c3a2ae5ccdc2ea05de734716f892d9b7f5f28774 (diff) | |
download | csv-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.yml | 3 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | composer.json | 2 | ||||
-rw-r--r-- | scrutinizer.yml | 2 | ||||
-rw-r--r-- | src/AbstractCsv.php | 24 | ||||
-rw-r--r-- | src/Config/Controls.php | 27 | ||||
-rw-r--r-- | src/Reader.php | 225 | ||||
-rw-r--r-- | test/AbstractTestCase.php | 3 | ||||
-rw-r--r-- | test/ControlsTest.php | 12 | ||||
-rw-r--r-- | test/FactoryTest.php | 14 | ||||
-rw-r--r-- | test/Plugin/ColumnConsistencyValidatorTest.php | 2 | ||||
-rw-r--r-- | test/Plugin/NullValidatorTest.php | 2 | ||||
-rw-r--r-- | test/Plugin/SkipNullValuesFormatterTest.php | 2 | ||||
-rw-r--r-- | test/ReaderTest.php | 191 | ||||
-rw-r--r-- | test/StreamFilterTest.php | 18 | ||||
-rw-r--r-- | test/WriterTest.php | 15 | ||||
-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 @@ -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 |