From daf5451311fe1b454420f71b3475996d5497e43b Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sat, 22 Mar 2014 13:34:59 +0100 Subject: [PATCH 1/3] adding null value handling in Writer class #28 --- src/Writer.php | 72 +++++++++++++++++++++++++++++++++++++++++++-- test/WriterTest.php | 46 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/Writer.php b/src/Writer.php index c78900c4..21de07fc 100644 --- a/src/Writer.php +++ b/src/Writer.php @@ -32,8 +32,9 @@ */ namespace League\Csv; -use InvalidArgumentException; use Traversable; +use InvalidArgumentException; +use OutOfBoundsException; /** * A class to manage data insertion into a CSV @@ -44,6 +45,15 @@ */ class Writer extends AbstractCsv { + + const NULL_AS_EXCEPTION = 1; + + const NULL_AS_SKIP_CELL = 2; + + const NULL_AS_EMPTY = 3; + + private $null_handling = self::NULL_AS_EXCEPTION; + /** * {@inheritdoc} */ @@ -52,6 +62,62 @@ public function __construct($path, $open_mode = 'w') parent::__construct($path, $open_mode); } + /** + * Tell the class how to handle null value + * + * @param integer $value a Writer null behavior constant + * + * @return self + * + * @throws OutOfBoundsException If the Integer is not valid + */ + public function setNullHandling($value) + { + if (!in_array($value, [self::NULL_AS_SKIP_CELL, self::NULL_AS_EXCEPTION, self::NULL_AS_EMPTY])) { + throw new OutOfBoundsException( + 'invalid value for null behavior' + ); + } + $this->null_handling = $value; + + return $this; + } + + /** + * null handling getter + * + * @return integer + */ + public function getNullHandling() + { + return $this->null_handling; + } + + /** + * Format the row according to the null handling behavior + * + * @param array $row + * + * @return array + */ + private function formatRow(array $row) + { + if (self::NULL_AS_EMPTY == $this->null_handling) { + foreach ($row as &$value) { + if (is_null($value)) { + $value = ''; + } + } + unset($value); + + return $row; + } + + return array_filter($row, function ($value) { + return ! is_null($value); + }); + } + /** * Add a new CSV row to the generated CSV * @@ -72,9 +138,11 @@ public function insertOne($row) ); } $check = array_filter($row, function ($value) { - return self::isValidString($value); + return (is_null($value) && self::NULL_AS_EXCEPTION != $this->null_handling) + || self::isValidString($value); }); if (count($check) == count($row)) { + $row = $this->formatRow($row); $this->csv->fputcsv($row, $this->delimiter, $this->enclosure); return $this; diff --git a/test/WriterTest.php b/test/WriterTest.php index e22cd9a9..2e87c91e 100644 --- a/test/WriterTest.php +++ b/test/WriterTest.php @@ -4,6 +4,7 @@ use SplTempFileObject; use ArrayIterator; +use LimitIterator; use PHPUnit_Framework_TestCase; use DateTime; use League\Csv\Writer; @@ -38,6 +39,51 @@ public function testInsert() } } + /** + * @expectedException OutOfBoundsException + */ + public function testSetterGetterNullBehavior() + { + $this->csv->setNullHandling(Writer::NULL_AS_SKIP_CELL); + $this->assertSame(Writer::NULL_AS_SKIP_CELL, $this->csv->getNullHandling()); + + $this->csv->setNullHandling(23); + } + + public function testInsertNullToSkipCell() + { + $expected = [ + ['john', 'doe', 'john.doe@example.com'], + 'john,doe,john.doe@example.com', + ['john', null, 'john.doe@example.com'], + ]; + $this->csv->setNullHandling(Writer::NULL_AS_SKIP_CELL); + foreach ($expected as $row) { + $this->csv->insertOne($row); + } + $iterator = new LimitIterator($this->csv->getIterator(), 2, 1); + $iterator->rewind(); + $res = $iterator->getInnerIterator()->current(); + $this->assertSame(['john', 'john.doe@example.com'], $res); + } + + public function testInsertNullToEmpty() + { + $expected = [ + ['john', 'doe', 'john.doe@example.com'], + 'john,doe,john.doe@example.com', + ['john', null, 'john.doe@example.com'], + ]; + $this->csv->setNullHandling(Writer::NULL_AS_EMPTY); + foreach ($expected as $row) { + $this->csv->insertOne($row); + } + $iterator = new LimitIterator($this->csv->getIterator(), 2, 1); + $iterator->rewind(); + $res = $iterator->getInnerIterator()->current(); + $this->assertSame(['john', '', 'john.doe@example.com'], $res); + } + /** * @expectedException InvalidArgumentException */ From b7397dfe766d2d0e8851674742fa6ec88e14e709 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 23 Mar 2014 15:18:34 +0100 Subject: [PATCH 2/3] bug fix null --- src/Writer.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Writer.php b/src/Writer.php index 9318f233..bc96607b 100644 --- a/src/Writer.php +++ b/src/Writer.php @@ -78,7 +78,7 @@ public function setNullHandling($value) { if (!in_array($value, [self::NULL_AS_SKIP_CELL, self::NULL_AS_EXCEPTION, self::NULL_AS_EMPTY])) { throw new OutOfBoundsException( - 'invalid value for null behavior' + 'invalid value for null handling' ); } $this->null_handling = $value; @@ -105,7 +105,9 @@ public function getNullHandling() */ private function formatRow(array $row) { - if (self::NULL_AS_EMPTY == $this->null_handling) { + if (self::NULL_AS_EXCEPTION == $this->null_handling) { + return $row; + } elseif (self::NULL_AS_EMPTY == $this->null_handling) { foreach ($row as &$value) { if (is_null($value)) { $value = ''; From a2a0cd98f867b545885faa4e33821428043e3f9d Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 23 Mar 2014 15:25:10 +0100 Subject: [PATCH 3/3] Bump to version 5.3 --- README.md | 12 ++---------- src/AbstractCsv.php | 2 +- src/ConverterTrait.php | 2 +- src/Iterator/IteratorFilter.php | 2 +- src/Iterator/IteratorInterval.php | 2 +- src/Iterator/IteratorQuery.php | 2 +- src/Iterator/IteratorSortBy.php | 2 +- src/Iterator/MapIterator.php | 2 +- src/Reader.php | 2 +- src/Writer.php | 2 +- 10 files changed, 11 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a9f84c86..31a8ae1e 100644 --- a/README.md +++ b/README.md @@ -50,22 +50,14 @@ Contribute to this documentation in the [sculpin branch](https://github.com/thep * When creating a file using the library, first insert all the data that need to be inserted before starting manipulating the CSV. If you manipulate your data before insertion, you may change the file cursor position and get unexpected results. -* If you are dealing with non-unicode data, specify the encoding parameter using the `setEncoding` method otherwise your output conversions may no work. - -* If you have your LC_CTYPE set to a locale that's using UTF-8 and you try to parse a file that's not in UTF-8, PHP will cut your fields the moment it encounters a byte it can't understand (i.e. any outside of ASCII that doesn't happen to be part of a UTF-8 character which it likely isn't). [This gist will show you a possible solution](https://gist.github.com/pilif/9137146) to this problem by using [PHP stream filter](http://www.php.net/manual/en/stream.filters.php). This tip is from [Philip Hofstetter](https://github.com/pilif) - * When merging multiples CSV documents don't forget to set the main CSV object as a `League\Csv\Writer` object with the `$open_mode = 'a+'` to preserve its content. This setting is of course not required when your main `League\Csv\Writer` object is created from String -* **If you are on a Mac OS X Server**, add the following lines before using the library to help [PHP detect line ending in Mac OS X](http://php.net/manual/en/function.fgetcsv.php#refsect1-function.fgetcsv-returnvalues). +* If you are dealing with non-unicode data, specify the encoding parameter using the `setEncoding` method otherwise your output conversions may no work. -```php -if (! ini_get("auto_detect_line_endings")) { - ini_set("auto_detect_line_endings", true); -} -``` +* If you have your LC_CTYPE set to a locale that's using UTF-8 and you try to parse a file that's not in UTF-8, PHP will cut your fields the moment it encounters a byte it can't understand (i.e. any outside of ASCII that doesn't happen to be part of a UTF-8 character which it likely isn't). [This gist will show you a possible solution](https://gist.github.com/pilif/9137146) to this problem by using [PHP stream filter](http://www.php.net/manual/en/stream.filters.php). This tip is from [Philip Hofstetter](https://github.com/pilif) Testing ------- diff --git a/src/AbstractCsv.php b/src/AbstractCsv.php index 7d1ee8d2..a7dc8e48 100644 --- a/src/AbstractCsv.php +++ b/src/AbstractCsv.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/ConverterTrait.php b/src/ConverterTrait.php index a870dc89..0c2cbb5f 100644 --- a/src/ConverterTrait.php +++ b/src/ConverterTrait.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/Iterator/IteratorFilter.php b/src/Iterator/IteratorFilter.php index 91742061..69da87b8 100644 --- a/src/Iterator/IteratorFilter.php +++ b/src/Iterator/IteratorFilter.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/Iterator/IteratorInterval.php b/src/Iterator/IteratorInterval.php index b5c05a0d..fcf93bba 100644 --- a/src/Iterator/IteratorInterval.php +++ b/src/Iterator/IteratorInterval.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/Iterator/IteratorQuery.php b/src/Iterator/IteratorQuery.php index 3f7b52d5..357336ce 100644 --- a/src/Iterator/IteratorQuery.php +++ b/src/Iterator/IteratorQuery.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/Iterator/IteratorSortBy.php b/src/Iterator/IteratorSortBy.php index c5d2897a..cb97d534 100644 --- a/src/Iterator/IteratorSortBy.php +++ b/src/Iterator/IteratorSortBy.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/Iterator/MapIterator.php b/src/Iterator/MapIterator.php index 35140de8..f31ee3a6 100644 --- a/src/Iterator/MapIterator.php +++ b/src/Iterator/MapIterator.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/Reader.php b/src/Reader.php index de7e10d5..a66c2477 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE diff --git a/src/Writer.php b/src/Writer.php index bc96607b..9a2765ff 100644 --- a/src/Writer.php +++ b/src/Writer.php @@ -6,7 +6,7 @@ * @copyright 2014 Ignace Nyamagana Butera * @link https://github.com/nyamsprod/League.csv * @license http://opensource.org/licenses/MIT -* @version 5.2.0 +* @version 5.3.0 * @package League.csv * * MIT LICENSE