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

Introduction

Currently, PHP requires a block body for catch clauses even when the caught exception is not used. This results in unnecessary boilerplate code. This RFC proposes allowing catch clauses without a body when the exception variable is omitted.

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);

This would be equivalent to:

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

Motivation

Reduced Boilerplate: Eliminates unnecessary empty blocks when exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the important parts.

Backward Incompatible Changes

None. This is purely an additive change to the syntax.

On 31. Jul 2025, at 11:53, Mihail Liahimov 91liahim@gmail.com wrote:

Introduction

Currently, PHP requires a block body for catch clauses even when the caught exception is not used. This results in unnecessary boilerplate code. This RFC proposes allowing catch clauses without a body when the exception variable is omitted.

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);

This would be equivalent to:

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

Motivation

Reduced Boilerplate: Eliminates unnecessary empty blocks when exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the important parts.

Backward Incompatible Changes

None. This is purely an additive change to the syntax.

Hey Mihail,

Why would the catch (SomeError) still be required if it isn’t used?

Cheers,
Nick

Hi, Nick!

It still would be required because we’re catching this concrete exception. Not catching all of throwables.

чт, 31 июл. 2025 г. в 10:43, Nick <php@nicksdot.dev>:

On 31. Jul 2025, at 11:53, Mihail Liahimov <91liahim@gmail.com> wrote:

Introduction

Currently, PHP requires a block body for catch clauses even when the caught exception is not used. This results in unnecessary boilerplate code. This RFC proposes allowing catch clauses without a body when the exception variable is omitted.

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);

This would be equivalent to:

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

Motivation

Reduced Boilerplate: Eliminates unnecessary empty blocks when exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the important parts.

Backward Incompatible Changes

None. This is purely an additive change to the syntax.

Hey Mihail,

Why would the catch (SomeError) still be required if it isn’t used?

Cheers,
Nick

Motivation

Reduced Boilerplate: Eliminates unnecessary empty blocks when exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the important parts.

Empty `catch` blocks are a code-smell. If the `try` block is not ready
to handle the exception, it should let the exception bubble up; not
silence it. I think we should not encourage this further.

Hey!

I even tried recently to code it. Looked really good and I thought about publishing it and creating the RFC.

I have use-cases where I need to try to send a request to a server and forget about an exception if so:

try { $client->post(…); }

If it fails I’m ok with it.
For sure, there are many pitfalls when you allow users to ignore any exceptions and errors, but it’s up to users, right?

About the proposal:

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

How can I catch AnotherError?

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

OR

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

What about “finally” construction?

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

Should the error flow into the “finally” construction?

···

Best regards,

Dmitrii Derepko.
@xepozz

Hey, Ayesh! Thank you for noticing an important point.

Of course, empty catch blocks are bad practice in most cases. But this particular proposal is not intended to encourage these bad practices. I’m just suggesting that you don’t have to write an extra boiler plate at the syntax level in situations where it’s necessary.

чт, 31 июл. 2025 г. в 11:21, Ayesh Karunaratne ayesh@php.watch:

Motivation

Reduced Boilerplate: Eliminates unnecessary empty blocks when exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the important parts.

Empty catch blocks are a code-smell. If the try block is not ready
to handle the exception, it should let the exception bubble up; not
silence it. I think we should not encourage this further.

Hi, Dmitry

I’ve been thinking about all these problems. I can’t say a definite decision yet, but I think it will be possible to discuss specific solutions if the community approves this proposal in principle.

At the moment, I was thinking about a similar syntaxis.:

try {
} catch (ErrorA) catch (ErrorB) {
// some handling
}

or with finally:

try {
} catch (Error) finally {
// …
}

чт, 31 июл. 2025 г. в 11:33, Dmitry Derepko <xepozzd@gmail.com>:

···

Best regards,

Dmitrii Derepko.
@xepozz

Of course, empty catch blocks are bad practice in most cases. But this particular proposal is not intended to encourage these bad practices. I'm just suggesting that you don't have to write an extra boiler plate at the syntax level in situations where it's necessary.

From what I can tell, it's very rare to have empty catch blocks, and
even more being done rightfully. I'd be very suspicious if I came
across such code.

I think the extra boilerplate is actually a good thing, showing the
explicit intent to not catch the error. I get that the proposal is not
intended to encourage such a thing, but I agree with Ayesh: it would
definitely will.

Hello, Alexandre.

I’ve heard your concerns, but I don’t think this proposal will encourage other developers to ignore exceptions more often. Basically, even now, nothing can stop a developer from ignoring exceptions. He can simply leave the body of the capture block empty. And in this RFC is proposed just to allow not to write extra brackets {}.

If we talk from the point of view of bad practices and applicability, then the same nullsafe operator may encourage developers to check for null less often. And this leads to problems, I personally observed this.

It seems to me that the issue of bad and good practices should be the responsibility of developers. A programming language is just a tool. And there are many ways in which it can be “badly” applied.

чт, 31 июл. 2025 г. в 11:51, Alexandre Daubois <alex.daubois+php@gmail.com>:

Of course, empty catch blocks are bad practice in most cases. But this particular proposal is not intended to encourage these bad practices. I’m just suggesting that you don’t have to write an extra boiler plate at the syntax level in situations where it’s necessary.

From what I can tell, it’s very rare to have empty catch blocks, and
even more being done rightfully. I’d be very suspicious if I came
across such code.

I think the extra boilerplate is actually a good thing, showing the
explicit intent to not catch the error. I get that the proposal is not
intended to encourage such a thing, but I agree with Ayesh: it would
definitely will.

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

Introduction

Currently, PHP requires a block body for catch clauses even when the caught exception is not used. This results in unnecessary boilerplate code. This RFC proposes allowing catch clauses without a body when the exception variable is omitted.

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);

This would be equivalent to:

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

Motivation

Reduced Boilerplate: Eliminates unnecessary empty blocks when exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the important parts.

Backward Incompatible Changes

None. This is purely an additive change to the syntax.

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).

That being said, I’m doing niche things. Outside of this framework/SDK, I have not ever really used empty catches, and even in my case, my empty catches have lengthy comments describing why they are empty in case a user steps into it, so they can better understand how the framework works.

— Rob

Hi, Rob!

Good point! I also know one another case when empty catch block are useful.

When we want to avoid returning null values, we resort to using exceptions so that the client code can somehow handle the absence of values.
For example:

if ($user->getName() !== null) {
$this->doSomethingWithUserName($user->getName());
}

We usually add an exception inside such getters if there is no value:

try {
$this->doSomethingWithUserName($user->getName());

} catch (UserNameIsNull);

This is useful when we need to “do nothing” when there is no value. In my opinion, this code looks more declarative.

We can do something like this also with repositories:

try {
$post = $this->postRepository->get($postId);

// do something with post
} catch (PostNotFound);

But again, there is a fine line between situations where an empty body of a catch block is reasonable and when it is not.

чт, 31 июл. 2025 г. в 12:13, Rob Landers rob@bottled.codes:

even in my case, my empty catches have lengthy comments describing why they are empty in case a user steps into it, so they can better understand how the framework works.

— Rob

Interesting catch, but i have been thinking of a better approach of catching errors without the try and catch block madness.

Here is my suggestion:

try (Exception $e) {
// define logic
}

if ($e) {
// execute code...
}

looks way more clean and structured, what do y’all think?

Just a suggestion though.

On Thu, 31 Jul 2025, 8:41 am Mihail Liahimov, <91liahim@gmail.com> wrote:

Hi, Rob!

Good point! I also know one another case when empty catch block are useful.

When we want to avoid returning null values, we resort to using exceptions so that the client code can somehow handle the absence of values.
For example:

if ($user->getName() !== null) {
$this->doSomethingWithUserName($user->getName());
}

We usually add an exception inside such getters if there is no value:

try {
$this->doSomethingWithUserName($user->getName());

} catch (UserNameIsNull);

This is useful when we need to “do nothing” when there is no value. In my opinion, this code looks more declarative.

We can do something like this also with repositories:

try {
$post = $this->postRepository->get($postId);

// do something with post
} catch (PostNotFound);

But again, there is a fine line between situations where an empty body of a catch block is reasonable and when it is not.

чт, 31 июл. 2025 г. в 12:13, Rob Landers rob@bottled.codes:

even in my case, my empty catches have lengthy comments describing why they are empty in case a user steps into it, so they can better understand how the framework works.

— Rob

On 31/07/2025 05:53, Mihail Liahimov wrote:

Reduced Boilerplate: Eliminates unnecessary empty blocks when exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the important parts.

The "boilerplate" in question is a single saved byte - ";" instead of "{}". Depending on keyboard layout one or the other might be slightly easier to type. Readability is always subjective, but I think most PHP programmers would quite easily read "{}" as "do nothing".

I suspect the real motivation here is that *coding standards* require more boilerplate than the language in this case, such as a newline between the opening and closing brace.

But PHP is not Python, where whitespace is enforced by the compiler; nor is it Go, where a coding standard is part of the standard library; so this is not the right place to change coding standards. Even if we add the short-hand syntax, there is nothing to stop those coding standards forbidding its use.

As others have said, good uses of empty catch blocks are rare - generally, you want to at least log that the exception happened, and where it was caught. And even those which are functionally empty often benefit from a comment explaining why they're not doing anything, which sits naturally between the braces.

If for some reason your project is commonly using completely empty catch blocks, you can document where and why "catch(Foo){}" is allowed in your local coding standard.

--
Rowan Tommins
[IMSoP]

On Thu, Jul 31, 2025 at 10:11 AM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 31/07/2025 05:53, Mihail Liahimov wrote:

Reduced Boilerplate: Eliminates unnecessary empty blocks when
exceptions only need to be caught and ignored.
Improved Readability: Makes the code more concise and focuses on the
important parts.

The “boilerplate” in question is a single saved byte - “;” instead of
“{}”. Depending on keyboard layout one or the other might be slightly
easier to type. Readability is always subjective, but I think most PHP
programmers would quite easily read “{}” as “do nothing”.

I suspect the real motivation here is that coding standards require
more boilerplate than the language in this case, such as a newline
between the opening and closing brace.

But PHP is not Python, where whitespace is enforced by the compiler; nor
is it Go, where a coding standard is part of the standard library; so
this is not the right place to change coding standards. Even if we add
the short-hand syntax, there is nothing to stop those coding standards
forbidding its use.

As others have said, good uses of empty catch blocks are rare -
generally, you want to at least log that the exception happened, and
where it was caught. And even those which are functionally empty often
benefit from a comment explaining why they’re not doing anything, which
sits naturally between the braces.

If for some reason your project is commonly using completely empty catch
blocks, you can document where and why “catch(Foo){}” is allowed in your
local coding standard.


Rowan Tommins
[IMSoP]

When I read catch (Foo); I read the intention of it being on purpose, whereas catch (Foo) {} I can easily interpret as “the developer forgot to handle the error” or “I can’t tell if this is a bug or not”. This often requires something like:

try {
// ...
} catch (Foo) {
// do nothing
}

Now the boilerplate has increased from {} vs ; to quite some extra, just to ensure the intention. I don’t know if omitting the body for this is the solution, or if there’s something else we can do like: ignore(Foo, Bar). When I read the code I want the intention to be clear, and an empty body just makes me doubt whether or not it is intended and/or correct. I do agree that empty catches are usually a sign of trouble, but realistically speaking we don’t always have control over the situation due to legacy or vendor code.

Just tossing some ideas out here:

$foo = try $this->doSomething() ignore (Foo, Bar);

ignore(Foo, Bar) {
$foo = $this->doSomething();
} catch (Throwable) {
// ....
} finally {

// ...

}

try ignore(Foo, Bar) {
$foo = $this->doSomething();
} catch (Throwable) {
// ...
} finally {

// ...

}

On Thu 31 Jul 2025, 11:32 Lynn, <kjarli@gmail.com> wrote:

When I read catch (Foo); I read the intention of it being on purpose, whereas catch (Foo) {} I can easily interpret as “the developer forgot to handle the error” or “I can’t tell if this is a bug or not”. This often requires something like:

try {
// ...
} catch (Foo) {
// do nothing
}

I much more prefer the long version and I’d rather see this in my code than just a semicolon or empty braces. If the developer must leave the catch block empty they better have a pretty good reason and they should clearly explain that in the code comment.

Now the boilerplate has increased from {} vs ; to quite some extra, just to ensure the intention. I don’t know if omitting the body for this is the solution, or if there’s something else we can do like: ignore(Foo, Bar). When I read the code I want the intention to be clear, and an empty body just makes me doubt whether or not it is intended and/or correct. I do agree that empty catches are usually a sign of trouble, but realistically speaking we don’t always have control over the situation due to legacy or vendor code.

Just tossing some ideas out here:

$foo = try $this->doSomething() ignore (Foo, Bar);

ignore(Foo, Bar) {
$foo = $this->doSomething();
} catch (Throwable) {
// ....
} finally {

// ...

}

try ignore(Foo, Bar) {
$foo = $this->doSomething();
} catch (Throwable) {
// ...
} finally {

// ...

}

I read that as catch all exceptions apart from these which should bubble up to the next exception handler. It could be useful in its own too.

I would vote NO to the proposal of short syntax for empty catch, not only because it’s bad practice but also because it’s one of the places where being very explicit is a good thing. An empty catch should be an eyesore and have a mandatory comment. It’s akin to a door with a sign “don’t open” versus a sign with “don’t open, angry lions inside”.

Agree. I prefer to describe why I’ve wrote it:
try { … } catch (…) {
/**

  • We don’t care If it’s broken, because we should go further.
    */
    }

A few cases from my side:

$decoded = ;
try {
$decoded = json_decode($unknownData, asArray: true);
} catch (JsonException);

Almost the same as

try {
$decoded = json_decode($unknownData, asArray: true);
} catch (JsonException) {
$decoded = ;
}

try {
doSmth()
} catch (RaceConditionException $e) {
// ok, skip 'cause another worker did the same
}

···

Best regards,

Dmitrii Derepko.
@xepozz

Hi, Rob!

When we want to avoid returning null values, we resort to using exceptions so that the client code can somehow handle the absence of values.
For example:

if ($user->getName() !== null) {
$this->doSomethingWithUserName($user->getName());
}

We usually add an exception inside such getters if there is no value:

try {
$this->doSomethingWithUserName($user->getName());

} catch (UserNameIsNull);

There will be TypeError because “doSomethingWithUserName” should accept strings.
Using exceptions to check types is rough practice because exceptions aren’t cheap. “if” is cheaper.

This is useful when we need to “do nothing” when there is no value. In my opinion, this code looks more declarative.

We can do something like this also with repositories:

try {
$post = $this->postRepository->get($postId);

// do something with post
} catch (PostNotFound);

Instead of idents inside another block I’d prefer to use early returns:

try {
– …
– …
– …
– …
– …
} catch (…)

vs

try {
– …
} catch (…) {
– return
}



Or even “if”

if (!..) {
– return …
}



···

Best regards,

Dmitrii Derepko.
@xepozz

On 31/07/2025 09:31, Lynn wrote:

When I read `catch (Foo);` I read the intention of it being on purpose, whereas `catch (Foo) {}` I can easily interpret as "the developer forgot to handle the error" or "I can't tell if this is a bug or not".

As I say, readability is always subjective, but I would just see ";" vs "{}" as a style choice, no obvious difference in intention. If anything, it would risk me misreading the code, because I'd be looking for the braces and not finding them.

A similar argument was made for keeping "var" as an alias of "public", to indicate ... something; I argued against that reasoning at the time: [RFC Discussion] "var" Deprecation - Externals

I don't think the language needs to allow multiple ways of writing everything, just so that projects can have subtle conventions of when to use which one.

Now the boilerplate has increased from {} vs ; to quite some extra, just to ensure the intention. I don't know if omitting the body for this is the solution, or if there's something else we can do like: `ignore(Foo, Bar)`. When I read the code I want the intention to be clear, and an empty body just makes me doubt whether or not it is intended and/or correct.

If the aim is to make the intention explicit, we need some syntax that makes the intention explicit, not just different punctuation we hope will become established as a convention.

$foo = try $this->doSomething() ignore (Foo, Bar);

I think an inline syntax like this would be a powerful feature, and a potential replacement for the @ operator:

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

One challenge would be optimising it so that the exception didn't build a full stack trace then immediately discard it - this was touched on in Larry's thread a while back Concept: Lightweight error channels - Externals

--
Rowan Tommins
[IMSoP]

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

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

I think an inline syntax like this would be a powerful feature, and a potential replacement for the @ operator:

$fh = null;
$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? Or will it encourage try ... ignore (Exception)? 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 :wink:

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. One could still use
  $lines = try file($filepath) ignore (FileLockedException) ?? ;
if another default value is preferred.

Regards,
- Chris