diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8798936..9d8b0aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ jobs: strategy: matrix: php: + - 8.1 - 8.0 - 7.4 - 7.3 diff --git a/README.md b/README.md index 64359f9..ebbbb38 100644 --- a/README.md +++ b/README.md @@ -317,9 +317,10 @@ test,1,24 ``` The `Encoder` supports the same optional parameters as the underlying -[`fputcsv()`](https://www.php.net/fputcsv) function. +[`fputcsv()`](https://www.php.net/manual/en/function.fputcsv.php) function. This means that, by default, CSV fields will be delimited by a comma (`,`), will -use a quote enclosure character (`"`) and a backslash escape character (`\`). +use a quote enclosure character (`"`), a backslash escape character (`\`), and +a Unix-style EOL (`\n` or `LF`). This behavior can be controlled through the optional constructor parameters: ```php @@ -385,7 +386,7 @@ See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. -It's *highly recommended to use PHP 7+* for this project. +It's *highly recommended to use the latest supported PHP version PHP 7+* for this project. ## Tests diff --git a/src/Encoder.php b/src/Encoder.php index affe4d1..fe5d39f 100644 --- a/src/Encoder.php +++ b/src/Encoder.php @@ -17,26 +17,27 @@ class Encoder extends EventEmitter implements WritableStreamInterface private $delimiter; private $enclosure; private $escapeChar; + private $eol; /** * @param WritableStreamInterface $output * @param string $delimiter * @param string $enclosure * @param string $escapeChar + * @param string $eol * @throws \BadMethodCallException */ - public function __construct(WritableStreamInterface $output, $delimiter = ',', $enclosure = '"', $escapeChar = '\\') + public function __construct(WritableStreamInterface $output, $delimiter = ',', $enclosure = '"', $escapeChar = '\\', $eol = "\n") { - // @codeCoverageIgnoreStart if ($escapeChar !== '\\' && PHP_VERSION_ID < 50504) { - throw new \BadMethodCallException('Custom escape character only supported on PHP 5.5.4+'); + throw new \BadMethodCallException('Custom escape character only supported on PHP 5.5.4+'); // @codeCoverageIgnore } - // @codeCoverageIgnoreEnd $this->output = $output; $this->delimiter = $delimiter; $this->enclosure = $enclosure; $this->escapeChar = $escapeChar; + $this->eol = $eol; if (!$output->isWritable()) { $this->close(); @@ -58,12 +59,14 @@ public function write($data) $written = false; if (is_array($data)) { - // custom escape character requires PHP 5.5.4+ (see constructor check) + // custom EOL requires PHP 8.1+, custom escape character requires PHP 5.5.4+ (see constructor check) // @codeCoverageIgnoreStart - if ($this->escapeChar === '\\') { - $written = fputcsv($this->temp, $data, $this->delimiter, $this->enclosure); - } else { + if (\PHP_VERSION_ID >= 80100) { + $written = fputcsv($this->temp, $data, $this->delimiter, $this->enclosure, $this->escapeChar, $this->eol); + } elseif (\PHP_VERSION_ID >= 50504) { $written = fputcsv($this->temp, $data, $this->delimiter, $this->enclosure, $this->escapeChar); + } else { + $written = fputcsv($this->temp, $data, $this->delimiter, $this->enclosure); } // @codeCoverageIgnoreEnd } @@ -77,6 +80,11 @@ public function write($data) $data = stream_get_contents($this->temp); ftruncate($this->temp, 0); + // manually replace custom EOL on PHP < 8.1 + if (\PHP_VERSION_ID < 80100 && $this->eol !== "\n") { + $data = \substr($data, 0, -1) . $this->eol; + } + return $this->output->write($data); } diff --git a/tests/EncoderTest.php b/tests/EncoderTest.php index c3a7182..bac68b1 100644 --- a/tests/EncoderTest.php +++ b/tests/EncoderTest.php @@ -44,6 +44,17 @@ public function testWriteArrayWithCustomDelimiter() $this->encoder->write(array('hello', 'world')); } + public function testWriteArrayWithCustomEolForWindows() + { + $this->output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + $this->output->expects($this->once())->method('isWritable')->willReturn(true); + $this->encoder = new Encoder($this->output, ',', '"', '\\', "\r\n"); + + $this->output->expects($this->once())->method('write')->with("hello,world\r\n"); + + $this->encoder->write(array('hello', 'world')); + } + public function testWriteArrayTwiceWillSeparateWithNewlineAfterEachWrite() { $this->output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();