Commit 0b7f50ad2e4 for php.net

commit 0b7f50ad2e48161c3d4e8b3ddd6d547ea72ed691
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sun Aug 9 19:44:21 2026 -0400

    Balance stream error ops on file_put_contents LOCK_EX URL reject (#23186)

    file_put_contents begins a stream error operation then returns early
    when LOCK_EX is used with a non-file URL, without ending the operation.
    After ~1000 such failures the depth limit is exhausted and later stream
    ops fail. Call php_stream_error_operation_end before that RETURN_FALSE.

    Closes GH-23186

diff --git a/NEWS b/NEWS
index 39d5a948e26..b091ea27614 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.6.0beta1

+- Streams:
+  . Fixed file_put_contents() LOCK_EX early return leaking stream error
+    operation depth. (iliaal)
+
 - Core:
   . Changed run-tests.php to run test subprocesses without a shell where
     possible. (NickSdot)
diff --git a/ext/standard/file.c b/ext/standard/file.c
index fd6f578e4ef..d6a8b9f1d0e 100644
--- a/ext/standard/file.c
+++ b/ext/standard/file.c
@@ -474,6 +474,7 @@ PHP_FUNCTION(file_put_contents)
 		if (php_memnstr(filename, "://", sizeof("://") - 1, filename + filename_len)) {
 			if (strncasecmp(filename, "file://", sizeof("file://") - 1)) {
 				php_error_docref(NULL, E_WARNING, "Exclusive locks may only be set for regular files");
+				php_stream_error_operation_end(context);
 				RETURN_FALSE;
 			}
 		}
diff --git a/ext/standard/tests/file/file_put_contents_lock_ex_url.phpt b/ext/standard/tests/file/file_put_contents_lock_ex_url.phpt
new file mode 100644
index 00000000000..6bd346f39f4
--- /dev/null
+++ b/ext/standard/tests/file/file_put_contents_lock_ex_url.phpt
@@ -0,0 +1,20 @@
+--TEST--
+file_put_contents LOCK_EX on non-file URL balances stream error operation
+--INI--
+allow_url_fopen=1
+--FILE--
+<?php
+for ($i = 0; $i < 1005; $i++) {
+    @file_put_contents('http://127.0.0.1/x', 'data', LOCK_EX);
+}
+set_error_handler(function (int $errno, string $errstr): bool {
+    if (str_contains($errstr, 'Stream error operation depth exceeded')) {
+        echo "depth_exceeded\n";
+    }
+    return true;
+});
+file_put_contents('http://127.0.0.1/x', 'data', LOCK_EX);
+echo "done\n";
+?>
+--EXPECT--
+done