[DOC-CVS] [doc-en] master: Document the exception handler stack behavior of PHP 8.3.0 and 8.3.5 (#5222)

[email protected] (Louis-Arnaud via GitHub)
Newsgroups php.doc.cvs
Message-ID <[email protected]>
Author: Louis-Arnaud (lacatoire)
Committer: GitHub (web-flow)
Pusher: lacatoire
Date: 2026-08-25T11:24:23+02:00

Commit: https://github.com/php/doc-en/commit/1cc803aaa07ac94ce8e3bfdf56f4b9ddd23326d3
Raw diff: https://github.com/php/doc-en/commit/1cc803aaa07ac94ce8e3bfdf56f4b9ddd23326d3.diff

Document the exception handler stack behavior of PHP 8.3.0 and 8.3.5 (#5222)

Changed paths:
  M  reference/errorfunc/functions/restore-exception-handler.xml
  M  reference/errorfunc/functions/set-exception-handler.xml


Diff:

diff --git a/reference/errorfunc/functions/restore-exception-handler.xml b/reference/errorfunc/functions/restore-exception-handler.xml
index 0ccf779c33a0..a2d8251b5fcc 100644
--- a/reference/errorfunc/functions/restore-exception-handler.xml
+++ b/reference/errorfunc/functions/restore-exception-handler.xml
@@ -20,6 +20,27 @@
    exception handler (which could be the built-in or a user defined
    function).
   </para>
+  <note>
+   <simpara>
+    While an exception handler is running, no exception handler is active: as of
+    PHP 8.3.0 the engine unsets it before invoking it, so that an exception
+    thrown by the handler is not passed back to it.
+   </simpara>
+   <simpara>
+    As of PHP 8.3.5 the engine also pushes the handler it is about to invoke onto
+    the handler stack. A <function>restore_exception_handler</function> call made
+    from within an exception handler therefore pops that entry and re-installs
+    the handler that is currently running; a second call is needed to install the
+    handler that was active before it.
+   </simpara>
+   <simpara>
+    The running handler is re-installed automatically when it returns, but only
+    if no exception handler is active at that point: a handler that calls
+    <function>set_exception_handler</function> or
+    <function>restore_exception_handler</function> is left in charge of the
+    handler stack itself.
+   </simpara>
+  </note>
  </refsect1>
 
  <refsect1 role="parameters">
@@ -34,6 +55,40 @@
   </para>
  </refsect1>
 
+ <refsect1 role="changelog">
+  &reftitle.changelog;
+  <para>
+   <informaltable>
+    <tgroup cols="2">
+     <thead>
+      <row>
+       <entry>&Version;</entry>
+       <entry>&Description;</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry>8.3.5</entry>
+       <entry>
+        The exception handler being invoked is now pushed onto the handler
+        stack, so <function>restore_exception_handler</function> called from
+        within an exception handler re-installs the handler that is currently
+        running; restoring the handler that was active before it now requires
+        two calls.
+       </entry>
+      </row>
+      <row>
+       <entry>8.3.0</entry>
+       <entry>
+        The active exception handler is now unset while it runs.
+       </entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </informaltable>
+  </para>
+ </refsect1>
+
  <refsect1 role="examples">
   &reftitle.examples;
   <para>
@@ -70,18 +125,16 @@
    </example>
   </para>
  </refsect1>
- 
+
  <refsect1 role="seealso">
   &reftitle.seealso;
-  <para>
-   <simplelist>
-    <member><function>set_exception_handler</function></member>
-    <member><function>get_exception_handler</function></member>
-    <member><function>set_error_handler</function></member>
-    <member><function>restore_error_handler</function></member>
-    <member><function>error_reporting</function></member>
-   </simplelist>
-  </para>
+  <simplelist>
+   <member><function>set_exception_handler</function></member>
+   <member><function>get_exception_handler</function></member>
+   <member><function>set_error_handler</function></member>
+   <member><function>restore_error_handler</function></member>
+   <member><function>error_reporting</function></member>
+  </simplelist>
  </refsect1>
 </refentry>
 <!-- Keep this comment at the end of the file
diff --git a/reference/errorfunc/functions/set-exception-handler.xml b/reference/errorfunc/functions/set-exception-handler.xml
index ce282ee63515..df06f4462c14 100644
--- a/reference/errorfunc/functions/set-exception-handler.xml
+++ b/reference/errorfunc/functions/set-exception-handler.xml
@@ -19,6 +19,32 @@
    try/catch block. Execution will stop after the
    <parameter>callback</parameter> is called.
   </para>
+
+  <note>
+   <simpara>
+    While an exception handler is running, no exception handler is active: as of
+    PHP 8.3.0 the engine unsets it before invoking it, so that an exception
+    thrown by the handler is not passed back to it. Called from within a handler,
+    <function>set_exception_handler</function> therefore reports no previously
+    defined handler.
+   </simpara>
+   <simpara>
+    As of PHP 8.3.5 the engine also pushes the handler it is about to invoke onto
+    the handler stack, so <function>restore_exception_handler</function> called
+    from within a handler re-installs the handler that is currently running
+    rather than the one that was active before it.
+   </simpara>
+   <simpara>
+    The running handler is re-installed automatically when it returns, but only
+    if no exception handler is active at that point. As soon as the handler calls
+    <function>set_exception_handler</function> or
+    <function>restore_exception_handler</function>, this automatic restoration is
+    skipped and the active handler is whatever the handler itself left in place.
+   </simpara>
+   <simpara>
+    Modifying the exception handler from within itself is therefore discouraged.
+   </simpara>
+  </note>
  </refsect1>
 
  <refsect1 role="parameters">
@@ -59,6 +85,40 @@
   </para>
  </refsect1>
 
+ <refsect1 role="changelog">
+  &reftitle.changelog;
+  <para>
+   <informaltable>
+    <tgroup cols="2">
+     <thead>
+      <row>
+       <entry>&Version;</entry>
+       <entry>&Description;</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry>8.3.5</entry>
+       <entry>
+        The exception handler being invoked is now pushed onto the handler stack
+        and re-installed once it returns, unless the handler modified the stack
+        itself.
+       </entry>
+      </row>
+      <row>
+       <entry>8.3.0</entry>
+       <entry>
+        The active exception handler is now unset while it runs, so
+        <function>set_exception_handler</function> called from within a handler
+        reports no previously defined handler.
+       </entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </informaltable>
+  </para>
+ </refsect1>
+
  <refsect1 role="examples">
   &reftitle.examples;
   <para>
@@ -84,15 +144,13 @@ echo "Not Executed\n";
 
  <refsect1 role="seealso"><!-- {{{ -->
   &reftitle.seealso;
-  <para>
-   <simplelist>
-    <member><function>get_exception_handler</function></member>
-    <member><function>restore_exception_handler</function></member>
-    <member><function>restore_error_handler</function></member>
-    <member><function>error_reporting</function></member>
-    <member><link linkend="language.exceptions">Exceptions</link></member>
-   </simplelist>
-  </para>
+  <simplelist>
+   <member><function>get_exception_handler</function></member>
+   <member><function>restore_exception_handler</function></member>
+   <member><function>restore_error_handler</function></member>
+   <member><function>error_reporting</function></member>
+   <member><link linkend="language.exceptions">Exceptions</link></member>
+  </simplelist>
  </refsect1><!-- }}} -->
 
 </refentry>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.