Commit 9a530a1c5e8 for php.net
commit 9a530a1c5e88be9d17e00b0204d9e8614689d6ed
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Thu Apr 23 12:55:15 2026 -0400
Fix GH-21742: SplFileObject::fgets() throws at EOF in eof/fgets loop (#21853)
The SplFileObject iterator-desync fix in 08dad097025 made
spl_filesystem_file_read_ex throw "Cannot read from file" on the
NULL-buffer path. SplFileObject::fgets() now throws inside the
documented while (!$spl->eof()) $spl->fgets() idiom, because eof()
returns false until a read attempt returns zero bytes.
Keep the stricter semantics for next(), seek(), current(), fscanf().
Narrow fgets() to silent=true and return empty string on FAILURE,
restoring the PHP-8.5 contract.
Fixes GH-21742
diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c
index 1468cec6ccf..89af25dd9d3 100644
--- a/ext/spl/spl_directory.c
+++ b/ext/spl/spl_directory.c
@@ -2099,7 +2099,11 @@ PHP_METHOD(SplFileObject, fgets)
spl_filesystem_file_free_line(intern);
intern->u.file.current_line_num++;
} else {
- if (spl_filesystem_file_read_ex(intern, /* silent */ false, /* line_add */ 1, /* csv */ false) == FAILURE) {
+ if (spl_filesystem_file_read_ex(intern, /* silent */ true, /* line_add */ 1, /* csv */ false) == FAILURE) {
+ if (php_stream_eof(intern->u.file.stream)) {
+ RETURN_EMPTY_STRING();
+ }
+ spl_filesystem_file_cannot_read(intern);
RETURN_THROWS();
}
RETVAL_STR_COPY(intern->u.file.current_line);
diff --git a/ext/spl/tests/SplFileObject/gh21742.phpt b/ext/spl/tests/SplFileObject/gh21742.phpt
new file mode 100644
index 00000000000..7ea60b53915
--- /dev/null
+++ b/ext/spl/tests/SplFileObject/gh21742.phpt
@@ -0,0 +1,35 @@
+--TEST--
+GH-21742 (SplFileObject::fgets() throws at EOF in while (!$spl->eof()) loop)
+--FILE--
+<?php
+$file = tempnam(sys_get_temp_dir(), 'spl');
+file_put_contents($file, "Line 0\nLine 1\nLine 2\nLine 3\nLine 4\n");
+
+$spl = new SplFileObject($file, 'r');
+while (!$spl->eof()) {
+ echo $spl->fgets();
+}
+echo "clean exit\n";
+
+$empty = tempnam(sys_get_temp_dir(), 'spl');
+file_put_contents($empty, '');
+$spl2 = new SplFileObject($empty, 'r');
+$iter = 0;
+while (!$spl2->eof()) {
+ $iter++;
+ $spl2->fgets();
+ if ($iter > 3) break;
+}
+echo "empty-file iters=$iter\n";
+
+unlink($file);
+unlink($empty);
+?>
+--EXPECT--
+Line 0
+Line 1
+Line 2
+Line 3
+Line 4
+clean exit
+empty-file iters=1