From fff45e0e9b1b567d78b87569dbecfd58c60f1de2 Mon Sep 17 00:00:00 2001 From: Kharhamel Date: Thu, 8 Sep 2022 17:27:47 +0200 Subject: [PATCH] FEATURE: fgetcsv now returns false on end of file --- generated/filesystem.php | 46 ------------------- generator/config/specialCasesFunctions.php | 1 + generator/tests/SpecialCasesTest.php | 52 ++++++++++++++++++++++ generator/tests/csv/test.csv | 2 + generator/tests/csv/test2.csv | 2 + lib/special_cases.php | 35 +++++++++++++++ 6 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 generator/tests/csv/test.csv create mode 100644 generator/tests/csv/test2.csv diff --git a/generated/filesystem.php b/generated/filesystem.php index de1a704d..df545f84 100644 --- a/generated/filesystem.php +++ b/generated/filesystem.php @@ -226,52 +226,6 @@ function fflush($stream): void } -/** - * Similar to fgets except that - * fgetcsv parses the line it reads for fields in - * CSV format and returns an array containing the fields - * read. - * - * @param resource $stream A valid file pointer to a file successfully opened by - * fopen, popen, or - * fsockopen. - * @param int $length Must be greater than the longest line (in characters) to be found in - * the CSV file (allowing for trailing line-end characters). Otherwise the - * line is split in chunks of length characters, - * unless the split would occur inside an enclosure. - * - * Omitting this parameter (or setting it to 0, - * or NULL in PHP 8.0.0 or later) the maximum line length is not limited, - * which is slightly slower. - * @param string $separator The optional separator parameter sets the field separator (one single-byte character only). - * @param string $enclosure The optional enclosure parameter sets the field enclosure character (one single-byte character only). - * @param string $escape The optional escape parameter sets the escape character (at most one single-byte character). - * An empty string ("") disables the proprietary escape mechanism. - * @return array|null Returns an indexed array containing the fields read on success. - * @throws FilesystemException - * - */ -function fgetcsv($stream, int $length = null, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): ?array -{ - error_clear_last(); - if ($escape !== "\\") { - $safeResult = \fgetcsv($stream, $length, $separator, $enclosure, $escape); - } elseif ($enclosure !== "\"") { - $safeResult = \fgetcsv($stream, $length, $separator, $enclosure); - } elseif ($separator !== ",") { - $safeResult = \fgetcsv($stream, $length, $separator); - } elseif ($length !== null) { - $safeResult = \fgetcsv($stream, $length); - } else { - $safeResult = \fgetcsv($stream); - } - if ($safeResult === false) { - throw FilesystemException::createFromPhpError(); - } - return $safeResult; -} - - /** * This function is similar to file, except that * file_get_contents returns the file in a diff --git a/generator/config/specialCasesFunctions.php b/generator/config/specialCasesFunctions.php index 7b830d2f..31febcc4 100644 --- a/generator/config/specialCasesFunctions.php +++ b/generator/config/specialCasesFunctions.php @@ -15,4 +15,5 @@ 'simplexml_import_dom', 'simplexml_load_file', 'simplexml_load_string', + 'fgetcsv', // This function need to return false when iterating on an end of file. ]; diff --git a/generator/tests/SpecialCasesTest.php b/generator/tests/SpecialCasesTest.php index c90a7343..e5f97232 100644 --- a/generator/tests/SpecialCasesTest.php +++ b/generator/tests/SpecialCasesTest.php @@ -3,6 +3,7 @@ namespace Safe; use PHPUnit\Framework\TestCase; +use Safe\Exceptions\FilesystemException; use Safe\Exceptions\PcreException; class SpecialCasesTest extends TestCase @@ -18,4 +19,55 @@ public function testPregReplace() $this->expectExceptionMessage('PREG_BAD_UTF8_ERROR: Invalid UTF8 character'); preg_replace("/([\s,]+)/u", "foo", "\xc3\x28"); } + + public function testFgetcsvWithTrailingNewline() + { + require_once __DIR__.'/../../lib/special_cases.php'; + require_once __DIR__.'/../../lib/Exceptions/SafeExceptionInterface.php'; + require_once __DIR__.'/../../generated/Exceptions/FilesystemException.php'; + + + if (($handle = \fopen(__DIR__."/csv/test.csv", "r")) === false) { + throw new \RuntimeException('Test file could not be opened.'); + } + + while (($data = fgetcsv($handle, 1000, ",")) !== false) { + $this->assertEquals(['test', 'test'], $data); + } + \fclose($handle); + } + + public function testFgetcsvReturnFalseonEndOfFile() + { + require_once __DIR__.'/../../lib/special_cases.php'; + require_once __DIR__.'/../../lib/Exceptions/SafeExceptionInterface.php'; + require_once __DIR__.'/../../generated/Exceptions/FilesystemException.php'; + + + if (($handle = \fopen(__DIR__."/csv/test2.csv", "r")) === false) { + throw new \RuntimeException('Test file could not be opened.'); + } + + while (($data = fgetcsv($handle, 1000, ",")) !== false) { + $this->assertEquals(['test', 'test'], $data); + } + $this->assertEquals(false, $data); + \fclose($handle); + } + + /*public function testFgetcsvThrowsOnError() + { + require_once __DIR__.'/../../lib/special_cases.php'; + require_once __DIR__.'/../../lib/Exceptions/SafeExceptionInterface.php'; + require_once __DIR__.'/../../generated/Exceptions/FilesystemException.php'; + + if (($handle = \fopen(__DIR__."/csv/test3.csv", "r")) === false) { + throw new \RuntimeException('Test file could not be opened.'); + } + + $this->expectException(FilesystemException::class); + while (($data = fgetcsv($handle, 1000, ",")) !== false) { + echo var_export($data, true); + } + }*/ } diff --git a/generator/tests/csv/test.csv b/generator/tests/csv/test.csv new file mode 100644 index 00000000..5a1d59be --- /dev/null +++ b/generator/tests/csv/test.csv @@ -0,0 +1,2 @@ +test,test +test,test diff --git a/generator/tests/csv/test2.csv b/generator/tests/csv/test2.csv new file mode 100644 index 00000000..790c42bc --- /dev/null +++ b/generator/tests/csv/test2.csv @@ -0,0 +1,2 @@ +test,test +test,test \ No newline at end of file diff --git a/lib/special_cases.php b/lib/special_cases.php index 2f21ed00..a4e85a93 100644 --- a/lib/special_cases.php +++ b/lib/special_cases.php @@ -405,3 +405,38 @@ function fputcsv($stream, array $fields, string $separator = ",", string $enclos } return $result; } + +/** + * Similar to fgets except that + * fgetcsv parses the line it reads for fields in + * CSV format and returns an array containing the fields + * read. + * + * @param resource $stream A valid file pointer to a file successfully opened by + * fopen, popen, or + * fsockopen. + * @param int<0, max>|null $length Must be greater than the longest line (in characters) to be found in + * the CSV file (allowing for trailing line-end characters). Otherwise the + * line is split in chunks of length characters, + * unless the split would occur inside an enclosure. + * + * Omitting this parameter (or setting it to 0, + * or NULL in PHP 8.0.0 or later) the maximum line length is not limited, + * which is slightly slower. + * @param string $separator The optional separator parameter sets the field separator (one single-byte character only). + * @param string $enclosure The optional enclosure parameter sets the field enclosure character (one single-byte character only). + * @param string $escape The optional escape parameter sets the escape character (at most one single-byte character). + * An empty string ("") disables the proprietary escape mechanism. + * @return mixed[]|false Returns an indexed array containing the fields read on success or false when there is no more lines. + * @throws FilesystemException + * + */ +function fgetcsv($stream, int $length = null, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array|false +{ + error_clear_last(); + $safeResult = \fgetcsv($stream, $length, $separator, $enclosure, $escape); + if ($safeResult === false && \feof($stream) === false) { + throw FilesystemException::createFromPhpError(); + } + return $safeResult; +}