[PHP-DEV] [RFC] Optional Catch Block Body

On Thu, Jul 31, 2025, at 15:32, Christian Schneider wrote:

Am 31.07.2025 um 09:10 schrieb Rob Landers <rob@bottled.codes>:

I can see something like this being useful in niche applications. For example, I have a proxy generation class that creates something like this:

public function remoteCall() {
$this->operation = nameof($this->remoteCall(…));
$this->arguments = func_get_args();
throw new SpecialException();
}

This SpecialException gets caught to let me know the application has consumed the one-use proxy. Using it looks something like

rpc(fn(RemoteObject $o) => $o->remoteCall());

which just provides type-safe rpc at the expense of some boilerplate.

I also use empty exceptions to “jump” the stack when my framework knows there is nothing more to do until outside events happen. I think I could probably use fibers to do the same, but if the user is using a fiber library; there is no guarantee they’ll play nice together. This is true with exceptions as well, but the user has direct control over exceptions (do not catch Throwable, for instance).

I’m not 100% sure I understood your examples but this smells like you are using exceptions for flow (or maybe state?) control which I’m not sure I would encourage.

Regards,

  • Chris

It’s explicitely to jump/unwind the stack – PHP has no other way (other than suspending a fiber or yielding a generator) to jump/unwind the stack to a given point. Fibers are the superior way here, but even then, throwing from the fiber is still the best way to terminate it and force a GC of the stack there. This is basically using it as a longjmp (in C terms) which PHP doesn’t have any other way to express. They’re not used for flow control, or state. They merely force PHP to unwind the stack to a known place (all possible side-effects have already been done and it is either throw – or wait for an external event, potentially forever – which consumes memory and a thread).

— Rob

On Thu, Jul 31, 2025, at 06:53, Mihail Liahimov wrote:

Proposal

Allow the following syntax where the curly braces can be omitted when no exception variable is specified and no handling is needed:

try {
// code that may throw
} catch (SomeError);

Can you write out how this would interoperate with the finally keyword?

On 31/07/2025 15:17, Rob Landers wrote:

They're not used for flow control, or state. They merely force PHP to unwind the stack to a known place

I don't understand the distinction here; you want to continue code at a particular point (control flow), and unwind the stack (state management).

That doesn't mean your use case isn't valid, but it's like saying "it's not a knife, it's merely a sharpened metallic implement used to cut things".

--
Rowan Tommins
[IMSoP]

$fh = @fopen($filePath, ‘w’); if ( $fh === false ) { // probably something else had the lock; could also be an invalid file path, or a catastrophic disk failure ¯_(ツ)_/¯ }

···

On 31/07/2025 14:46, Christian Schneider wrote:


> ```
> $fh = try fopen($filePath, 'w') ignore (FileLockedException);
> 
> ```

```
First of all: I'm wary because partial error handling seems dangerous to mel do I know all possible Exception types and which ones should abort and which ones should continue?
```

That’s kind of the point: it’s for when you know how to handle some specific cases, but want anything else to abort.

In this hypothetical example, the code is using an exclusive lock to avoid two processes writing to the file; it wants to gracefully handle the specific scenario of “some other process has the lock”. If there’s some other error, like “invalid file path”, that should not be suppressed.

But the current PHP I/O functions give no way to distinguish:

I have to admit that I'm also a sceptic of converting every possible info/warning/error to an Exception but that's a different topic

I didn’t intend to imply that this would replace all Warnings. Think of it more as replacing the “returns false on error” part of the fopen() signature.

If we decide to add something like the above I would very much prefer the try ... ignore block to be an expression with value null on error, making the first line obsolete.

Good point, I agree.

-- 
Rowan Tommins
[IMSoP]

Am 31.07.2025 um 17:53 schrieb Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>:

On 31/07/2025 14:46, Christian Schneider wrote:

$fh = try fopen($filePath, 'w') ignore (FileLockedException);

First of all: I'm wary because partial error handling seems dangerous to mel do I know all possible Exception types and which ones should abort and which ones should continue?

That's kind of the point: it's for when you know how to handle some specific cases, but want *anything else* to abort.
In this hypothetical example, the code is using an exclusive lock to avoid two processes writing to the file; it wants to gracefully handle the specific scenario of "some other process has the lock". If there's some other error, like "invalid file path", that *should not* be suppressed.
But the current PHP I/O functions give no way to distinguish:
$fh = @fopen($filePath, 'w');
if ( $fh === false ) {
    // probably something else had the lock; could also be an invalid file path, or a catastrophic disk failure ¯\_(ツ)_/¯
}

If you have to handle null afterwards (e.g. avoiding writing to invalid $fh) then the "ignore" version does not really help much IMHO

Realistically I think it would be something like

if ($fh = try fopen($filePath, 'w') ignore (FileLockedException)) {
  ...
} [else
  ...]

vs. currently

try {
  $fh = try fopen($filePath, 'w');
  ...
} catch (FileLockedException) {
  [...]
}

which does not seem much clearer to me.

I didn't intend to imply that this would replace all Warnings. Think of it more as replacing the "returns false on error" part of the fopen() signature.

... so you would replace false for (some) errors by Exceptions, that's what I meant. Plus having some stuff return false and some things throw Exceptions seems weird too, that's why I said "all" in the first place. But false/null vs. Exceptions is a different, much broader topic, let's skip that for now :wink:

Regards,
- Chris

On Thu, Jul 31, 2025, at 17:33, Rowan Tommins [IMSoP] wrote:

On 31/07/2025 15:17, Rob Landers wrote:

They’re not used for flow control, or state. They merely force PHP to
unwind the stack to a known place

I don’t understand the distinction here; you want to continue code at a
particular point (control flow), and unwind the stack (state management).

Unconditionally, yes. This is more like a bailout of user code, not throwing the exception would enter an infinite loop or cause an error. So, it’s kind of a gray area between flow control and exceptional.

That doesn’t mean your use case isn’t valid, but it’s like saying “it’s
not a knife, it’s merely a sharpened metallic implement used to cut things”.


Rowan Tommins
[IMSoP]

lol. Fair enough.

— Rob

On 31/07/2025 17:24, Christian Schneider wrote:

If you have to handle null afterwards (e.g. avoiding writing to invalid $fh) then the "ignore" version does not really help much IMHO

In a simple case, yes, but you don't necessarily want the check directly under the fopen. Maybe you want to try opening multiple files, and succeed if at least one is unlocked; or try in a loop; or try in the constructor of an object, and handle it being null in a later method.

It's still mostly syntax sugar (and maybe a hint to the compiler to optimise), but it's saving more than the original proposal, even skipping newlines:

$fh=null; try { $fh = fopen($filePath, 'w'); } catch (FileLockedException) {}
$fh = try fopen($filePath, 'w') ignore (FileLockedException);

But, I'm not volunteering to write a formal proposal, let alone an implementation; I'm just exploring what kinds of short-hand might be useful.

--
Rowan Tommins
[IMSoP]

Hi!

At the moment, I can suggest this syntax:

try {
// do something
} catch (SomeIgnorableException) finally {
// do something
}

I also generally like the above idea with the keyword ignore.

чт, 31 июл. 2025 г. в 20:24, Casper Langemeijer <langemeijer@php.net>:

On Thu, Jul 31, 2025, at 06:53, Mihail Liahimov wrote:

Proposal

Allow the following syntax where the curly braces can be omitted when no exception variable is specified and no handling is needed:

try {
// code that may throw
} catch (SomeError);

Can you write out how this would interoperate with the finally keyword?

Hey Mihail

Am 06.08.25 um 13:34 schrieb Mihail Liahimov:

Hi!

At the moment, I can suggest this syntax:

try {
// do something
} catch (SomeIgnorableException) finally {
// do something
}

I find that ... challenging

When reading the code I now have to go to the end of the line to understand that the second

// do something

does not belong to the `try` but to the `finally`...

And

try {
   // break stuff
} catch (SomeIgnorableException)
finally {
   // do something regardless
}

seems boken due to the missing }

But well....

We just have to adapt to something like this:

try {
   // break stuff
}
catch (SomeIgnorableException)
catch (Some OtherIgnorableException)
finally {
   // do something regardless
}

--
                                                               ,
                                                              (o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22'59.5" E 08°23'58" |
| https://andreas.heigl.org |
+---------------------------------------------------------------------+
| Appointments - Nextcloud |
+---------------------------------------------------------------------+
| GPG-Key: https://hei.gl/keyandreasheiglorg |
+---------------------------------------------------------------------+

Yeah, it seems to be problematic to use this syntax with finally blocks. Perhaps the optional catch block should be allowed only at the very end, but it sounds somehow doubtful.

ср, 6 авг. 2025 г. в 16:52, Andreas Heigl <andreas@heigl.org>:

Hey Mihail

Am 06.08.25 um 13:34 schrieb Mihail Liahimov:

Hi!

At the moment, I can suggest this syntax:

try {
// do something
} catch (SomeIgnorableException) finally {
// do something
}

I find that … challenging

When reading the code I now have to go to the end of the line to
understand that the second

// do something

does not belong to the try but to the finally

And

try {
// break stuff
} catch (SomeIgnorableException)
finally {
// do something regardless
}

seems boken due to the missing }

But well…

We just have to adapt to something like this:

try {
// break stuff
}
catch (SomeIgnorableException)
catch (Some OtherIgnorableException)
finally {
// do something regardless
}


,
(o o)
±--------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22’59.5" E 08°23’58" |
| https://andreas.heigl.org |
±--------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
±--------------------------------------------------------------------+
| GPG-Key: https://hei.gl/keyandreasheiglorg |
±--------------------------------------------------------------------+

On Fri, Aug 8, 2025, at 07:57, Mihail Liahimov wrote:

Yeah, it seems to be problematic to use this syntax with finally blocks. Perhaps the optional catch block should be allowed only at the very end, but it sounds somehow doubtful.

ср, 6 авг. 2025 г. в 16:52, Andreas Heigl <andreas@heigl.org>:

Hey Mihail

Am 06.08.25 um 13:34 schrieb Mihail Liahimov:

Hi!

At the moment, I can suggest this syntax:

try {
// do something
} catch (SomeIgnorableException) finally {
// do something
}

I find that … challenging

When reading the code I now have to go to the end of the line to
understand that the second

// do something

does not belong to the try but to the finally

And

try {
// break stuff
} catch (SomeIgnorableException)
finally {
// do something regardless
}

seems boken due to the missing }

But well…

We just have to adapt to something like this:

try {
// break stuff
}
catch (SomeIgnorableException)
catch (Some OtherIgnorableException)
finally {
// do something regardless
}


,
(o o)
±--------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22’59.5" E 08°23’58" |
| https://andreas.heigl.org |
±--------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
±--------------------------------------------------------------------+
| GPG-Key: https://hei.gl/keyandreasheiglorg |
±--------------------------------------------------------------------+

What about instead of at the end, we do it at the beginning?

try ignore (SomeIgnorableException, OtherException) {
// break stuff
} catch(UnexpectedException) {
// fix stuff
} finally {
// do this anyway
}

Since ignore only can follow a try and any symbol other than a “{” is currently an error, I don’t think we don’t need to reserve “ignore” as a keyword.

It appears to be relatively easy to scan/read as well.

— Rob

On Fri, Aug 8, 2025, at 08:50, Rob Landers wrote:

On Fri, Aug 8, 2025, at 07:57, Mihail Liahimov wrote:

Yeah, it seems to be problematic to use this syntax with finally blocks. Perhaps the optional catch block should be allowed only at the very end, but it sounds somehow doubtful.

ср, 6 авг. 2025 г. в 16:52, Andreas Heigl <andreas@heigl.org>:

Hey Mihail

Am 06.08.25 um 13:34 schrieb Mihail Liahimov:

Hi!

At the moment, I can suggest this syntax:

try {
// do something
} catch (SomeIgnorableException) finally {
// do something
}

I find that … challenging

When reading the code I now have to go to the end of the line to
understand that the second

// do something

does not belong to the try but to the finally

And

try {
// break stuff
} catch (SomeIgnorableException)
finally {
// do something regardless
}

seems boken due to the missing }

But well…

We just have to adapt to something like this:

try {
// break stuff
}
catch (SomeIgnorableException)
catch (Some OtherIgnorableException)
finally {
// do something regardless
}


,
(o o)
±--------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22’59.5" E 08°23’58" |
| https://andreas.heigl.org |
±--------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
±--------------------------------------------------------------------+
| GPG-Key: https://hei.gl/keyandreasheiglorg |
±--------------------------------------------------------------------+

What about instead of at the end, we do it at the beginning?

try ignore (SomeIgnorableException, OtherException) {
// break stuff
} catch(UnexpectedException) {
// fix stuff
} finally {
// do this anyway
}

Since ignore only can follow a try and any symbol other than a “{” is currently an error, I don’t think we don’t need to reserve “ignore” as a keyword.

It appears to be relatively easy to scan/read as well.

— Rob

Sorry, it should have read: “I don’t think we need to reserve “ignore” as a keyword”.

— Rob