# [RFC] [Discussion] PREG_THROW_ON_ERROR

- Thread: https://externals.io/message/132115
- Original archive: https://news-web.php.net/php.internals/132115
- Started by Osama Aldemeery on 2026-07-28T00:56:51+00:00
- Messages: 9
- RFC: PREG_THROW_ON_ERROR (Under discussion) — https://externals.io/rfc/preg_throw_on_error.md
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #132115 — Osama Aldemeery — 2026-07-28T00:56:51+00:00

URL: https://externals.io/message/132115#132115

Hi all,

Following the earlier pre-RFC discussion (
https://news-web.php.net/php.internals/131783), I've written this up as a
proper RFC: https://wiki.php.net/rfc/preg_throw_on_error

Short version:

a `PREG_THROW_ON_ERROR` flag you pass to any `preg_*()` call, so a PCRE
error throws a `\PregException` you can catch
instead of a warning plus a `false` / `null` you have to notice and then
chase through `preg_last_error()`.
Same thing `JSON_THROW_ON_ERROR` and `FILTER_THROW_ON_FAILURE` already do
for their functions.
It's opt-in, so nothing changes without the flag.

The flag only changes how an error is delivered. A call does exactly the
same thing with it or without it, byte for byte.
The only difference is that at the end, any error the call would have
recorded is additionally thrown, carrying the same code and
message `preg_last_error()`/`preg_last_error_msg()` would report.

For array arguments that means the flag mirrors `preg_last_error()`...it
throws whatever error the same call leaves there,
which for `preg_replace()` over an array is the last-processed entry, not
the first. (In the pre-RFC I'd floated throwing on the first failing
element instead).
Whether the `preg_*()` functions should stop at the first failing entry is
a separate question about those functions, one that applies with or without
the flag, so it's out of scope here.

The exception name is settled from the pre-RFC as `\PregException` (thanks
Tim) and the flag as `PREG_THROW_ON_ERROR`.

Target is the next feature release after 8.6 since 8.6's beta1 is too close
for this to conclude discussion and a vote in time.

I also saw Larry's note asking to hold new business that isn't targeting
8.6 until the freeze rush is over.
This won't catch 8.6, so I'm not asking for any priority while the freeze
is on. But the pre-RFC discussion was already open before
Larry's note, so I'd rather keep it ticking over quietly than restart it in
September.
If someone prefers I park it until then, please say so and I will.

Implementation and tests: https://github.com/php/php-src/pull/22797

Thanks,
Osama

---

## #132129 — B.J.Scharp — 2026-07-30T00:21:29+00:00

URL: https://externals.io/message/132115#132129 · In reply to: #132115

> Hi all,

 > The exception name is settled from the pre-RFC as `\PregException`
 > (thanks Tim) and the flag as `PREG_THROW_ON_ERROR`.

 From the RFC:
 > The flag covers both classes of PCRE failure.

have you considered throwing different Exception types for the two 
failure types? (e.g. PregExecutionException and PregCompilationException)


You could have both be child classes of PregException, but in actual 
fact, the second should probably be a child of LogicException, while the 
former really is a RuntimeException...

Regards,
Bernard

---

## #132133 — Rowan Tommins [IMSoP] — 2026-07-30T14:22:24+00:00

URL: https://externals.io/message/132115#132133 · In reply to: #132129

>
> > […]
>
> > […]
>
>From the RFC:
> > […]
>
>have you considered throwing different Exception types for the two failure types? (e.g. PregExecutionException and PregCompilationException)
>
>
>You could have both be child classes of PregException, but in actual fact, the second should probably be a child of LogicException, while the former really is a RuntimeException...

Hi,

We actually have an agreed policy for this: <https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#throwables>

According to that, there has to be a single base extension, which extends directly from \Exception.

Having more specific extensions is definitely encouraged, but these must all extend the extension's base exception (or each other), not any of the SPL classes like RuntimeException.

If it was a new extension the base exception name would be \Pcre\PcreException but there's flexibility when adding to existing extensions.

Thanks for bringing up this point,

Rowan Tommins
[IMSoP]

---

## #132135 — Christian Schneider — 2026-07-30T15:08:33+00:00

URL: https://externals.io/message/132115#132135 · In reply to: #132115

Am 28.07.2026 um 02:56 schrieb Osama Aldemeery <aldemeery@gmail.com>:
> The flag only changes how an error is delivered. A call does exactly the same thing with it or without it, byte for byte.
> The only difference is that at the end, any error the call would have recorded is additionally thrown, carrying the same code and
> message `preg_last_error()`/`preg_last_error_msg()` would report.

Reading the RFC and specifically the line

> // The detailed "Compilation failed: ..." warning is still emitted, as always.

I assume compilation (and other?) failures will then trigger BOTH the `E_WARNING` and an Exception?

This sounds weird to me, do we have a precedent for this behavior?
I would have expected the Exception to replace the E_WARNING.

Regards,
- Chris

---

## #132142 — Osama Aldemeery — 2026-07-30T18:36:52+00:00

URL: https://externals.io/message/132115#132142 · In reply to: #132129

On Thu, Jul 30, 2026 at 3:22 AM B.J.Scharp
<php.list.internals@bitwise-operators.com> wrote:
>
> 
> > […]
>
> > […]
>
>  From the RFC:
> > […]
>
> have you considered throwing different Exception types for the two
> failure types? (e.g. PregExecutionException and PregCompilationException)
>
>
> You could have both be child classes of PregException, but in actual
> fact, the second should probably be a child of LogicException, while the
> former really is a RuntimeException...
>
> Regards,
> Bernard

Hi Bernard,

Thanks for bringing this up.

As Rowan pointed out, the throwables policy settles half of it:
additional exceptions must extend the extension's base or each other,
not the SPL classes.
So the LogicException / RuntimeException parents are out regardless.

That leaves one `PregException` versus two under it (like your
suggested `PregCompilationException` / `PregExecutionException`),
which is a judgment call.
My starting point for the whole flag is that it only changes how an
error is delivered and nothing more.
That is the code and the message are exactly what `preg_last_error()`
and `preg_last_error_msg()` already give you.
A single exception sits most naturally on top of that.

There's a concrete nudge the same way....A compile failure only
carries the generic
`PREG_INTERNAL_ERROR` / `"Internal error"` through the error functions.
The real detail (`"Compilation failed: ... at offset N"`) is in a
warning, not `preg_last_error_msg()`.
So a `PregCompilationException` would carry `"Internal error"` and
little else until that detail is surfaced properly, which I've left as
future scope.

The upside of two types is letting people catch the two failure modes
separately.
I'm not sure how often that's needed in practice (given that
`pre_last_error()` already does not differentiate between them), but I
don't object if people feel
it's the better design. For now I'd still favor the single
`PregException`, with the door open.

Thanks,
Osama

---

## #132143 — Osama Aldemeery — 2026-07-30T18:45:14+00:00

URL: https://externals.io/message/132115#132143 · In reply to: #132129

On Thu, Jul 30, 2026 at 3:22 AM B.J.Scharp <
php.list.internals@bitwise-operators.com> wrote:
>
> 
> > […]
>
> > […]
>
>  From the RFC:
> > […]
>
> have you considered throwing different Exception types for the two
> failure types? (e.g. PregExecutionException and PregCompilationException)
>
>
> You could have both be child classes of PregException, but in actual
> fact, the second should probably be a child of LogicException, while the
> former really is a RuntimeException...
>
> Regards,
> Bernard

Hi Bernard,

Sorry, my last message got misformatted, so I am sending it again.

Thanks again for bringing this up.

As Rowan pointed out, the throwables policy settles half of it: additional
exceptions must extend the extension's base or each other, not the SPL
classes.
So the LogicException / RuntimeException parents are out regardless.

That leaves one `PregException` versus two under it (like your suggested
`PregCompilationException` / `PregExecutionException`), which is a judgment
call.
My starting point for the whole flag is that it only changes how an error
is delivered and nothing more.
That is the code and the message are exactly what `preg_last_error()` and
`preg_last_error_msg()` already give you.
A single exception sits most naturally on top of that.

There's a concrete nudge the same way....A compile failure only carries the
generic `PREG_INTERNAL_ERROR` / `"Internal error"` through the error
functions.
The real detail (`"Compilation failed: ... at offset N"`) is in a warning,
not `preg_last_error_msg()`.
So a `PregCompilationException` would carry `"Internal error"` and little
else until that detail is surfaced properly, which I've left as future
scope.

The upside of two types is letting people catch the two failure modes
separately.
I'm not sure how often that's needed in practice (given that
`pre_last_error()` already does not differentiate between them), but I
don't object if people feel
it's the better design.
For now I'd still favor the single `PregException`, with the door open.

Thanks,
Osama

---

## #132144 — Osama Aldemeery — 2026-07-30T18:56:40+00:00

URL: https://externals.io/message/132115#132144 · In reply to: #132135

On Thu, Jul 30, 2026 at 6:09 PM Christian Schneider
<cschneid@cschneid.com> wrote:
>
> Am 28.07.2026 um 02:56 schrieb Osama Aldemeery <aldemeery@gmail.com>:
> > […]
>
> Reading the RFC and specifically the line
>
> > […]
>
> I assume compilation (and other?) failures will then trigger BOTH the `E_WARNING` and an Exception?
>
> This sounds weird to me, do we have a precedent for this behavior?
> I would have expected the Exception to replace the E_WARNING.
>
> Regards,
> - Chris

Hi Chris,

Good question, and you're reading it right: under the flag, a compile
failure emits both the `E_WARNING` and the exception.
One clarification though...That doubling only happens for compile
errors, which warn today.
Execution errors on the other hand (e.g. bad UTF-8, backtrack limit,
and so on) don't emit a warning at all, so those just throw.

On precedent, as far as I know there isn't a precedent for somethig like this.
But the reason it's currently warning-plus-exception rather than
exception-instead-of-warning is because the useful detail only lives
in the warning.
A compile failure sets the generic `PREG_INTERNAL_ERROR` / `"Internal
error"` in the error functions, while the actual `"Compilation failed:
... at offset N"` text is only in the warning.
So if the exception replaced the warning today, you'd get an exception
saying `"Internal error"` and nothing about where or why.
Keeping the warning is what preserves that detail for now.

It's worth mentioning though that I do think
exception-instead-of-warning is the cleaner end state.
But it becomes the right move once the exception can carry that detail
itself, and that requires changing the message `preg_last_message()`
reports, which is the richer
compile-error reporting I've put under future scope.
Until then, dropping the warning would make the flag strictly worse
for debugging a bad pattern.

Happy to hear if you or others see it differently.

Thanks,
Osama

---

## #132245 — Osama Aldemeery — 2026-08-18T01:58:51+00:00

URL: https://externals.io/message/132115#132245 · In reply to: #132115

>
> Hi all,
>
> Following the earlier pre-RFC discussion (https://news-web.php.net/php.internals/131783), I've written this up as a proper RFC: https://wiki.php.net/rfc/preg_throw_on_error
>
> Short version:
>
> a `PREG_THROW_ON_ERROR` flag you pass to any `preg_*()` call, so a PCRE error throws a `\PregException` you can catch
> instead of a warning plus a `false` / `null` you have to notice and then chase through `preg_last_error()`.
> Same thing `JSON_THROW_ON_ERROR` and `FILTER_THROW_ON_FAILURE` already do for their functions.
> It's opt-in, so nothing changes without the flag.
>
> The flag only changes how an error is delivered. A call does exactly the same thing with it or without it, byte for byte.
> The only difference is that at the end, any error the call would have recorded is additionally thrown, carrying the same code and
> message `preg_last_error()`/`preg_last_error_msg()` would report.
>
> For array arguments that means the flag mirrors `preg_last_error()`...it throws whatever error the same call leaves there,
> which for `preg_replace()` over an array is the last-processed entry, not the first. (In the pre-RFC I'd floated throwing on the first failing element instead).
> Whether the `preg_*()` functions should stop at the first failing entry is a separate question about those functions, one that applies with or without the flag, so it's out of scope here.
>
> The exception name is settled from the pre-RFC as `\PregException` (thanks Tim) and the flag as `PREG_THROW_ON_ERROR`.
>
> Target is the next feature release after 8.6 since 8.6's beta1 is too close for this to conclude discussion and a vote in time.
>
> I also saw Larry's note asking to hold new business that isn't targeting 8.6 until the freeze rush is over.
> This won't catch 8.6, so I'm not asking for any priority while the freeze is on. But the pre-RFC discussion was already open before
> Larry's note, so I'd rather keep it ticking over quietly than restart it in September.
> If someone prefers I park it until then, please say so and I will.
>
> Implementation and tests: https://github.com/php/php-src/pull/22797
>
> Thanks,
> Osama

Hi all,

This has gone quiet, which I'm taking as the freeze crunch and people
being busy, not as everyone being fine with it as-is.
And it isn't targeting 8.6 anyway, so there's no rush on my side.

So I'll park it and bring it back early September just like Larry
asked, once the release dust has settled, to give anyone who hasn't
weighed in a clear window to do so.
The points raised so far (the exception hierarchy, the warning
behavior) are addressed in the RFC, so if it's still quiet after I
resurface it, the natural next step is to move toward a vote.
I'd rather that happen once everyone's had a fair chance than while
half the list is heads-down on the release.

If there's something you'd want settled before a vote, now or early
September both work for me.
I'll ping the thread again then.

Thanks,
Osama

---

## #132411 — Osama Aldemeery — 2026-09-02T18:55:06+00:00

URL: https://externals.io/message/132115#132411 · In reply to: #132115

>
> Hi all,
>
> Following the earlier pre-RFC discussion (https://news-web.php.net/php.internals/131783), I've written this up as a proper RFC: https://wiki.php.net/rfc/preg_throw_on_error
>
> Short version:
>
> a `PREG_THROW_ON_ERROR` flag you pass to any `preg_*()` call, so a PCRE error throws a `\PregException` you can catch
> instead of a warning plus a `false` / `null` you have to notice and then chase through `preg_last_error()`.
> Same thing `JSON_THROW_ON_ERROR` and `FILTER_THROW_ON_FAILURE` already do for their functions.
> It's opt-in, so nothing changes without the flag.
>
> The flag only changes how an error is delivered. A call does exactly the same thing with it or without it, byte for byte.
> The only difference is that at the end, any error the call would have recorded is additionally thrown, carrying the same code and
> message `preg_last_error()`/`preg_last_error_msg()` would report.
>
> For array arguments that means the flag mirrors `preg_last_error()`...it throws whatever error the same call leaves there,
> which for `preg_replace()` over an array is the last-processed entry, not the first. (In the pre-RFC I'd floated throwing on the first failing element instead).
> Whether the `preg_*()` functions should stop at the first failing entry is a separate question about those functions, one that applies with or without the flag, so it's out of scope here.
>
> The exception name is settled from the pre-RFC as `\PregException` (thanks Tim) and the flag as `PREG_THROW_ON_ERROR`.
>
> Target is the next feature release after 8.6 since 8.6's beta1 is too close for this to conclude discussion and a vote in time.
>
> I also saw Larry's note asking to hold new business that isn't targeting 8.6 until the freeze rush is over.
> This won't catch 8.6, so I'm not asking for any priority while the freeze is on. But the pre-RFC discussion was already open before
> Larry's note, so I'd rather keep it ticking over quietly than restart it in September.
> If someone prefers I park it until then, please say so and I will.
>
> Implementation and tests: https://github.com/php/php-src/pull/22797
>
> Thanks,
> Osama

Hi all,

Picking this back up...Quick recap first, since it's been quiet for a
couple of weeks.

`PREG_THROW_ON_ERROR` is an opt-in flag you pass to any `preg_*()` call.
With it, a PCRE error throws a `\PregException` you can catch, instead
of the current warning plus a `false` or `null` you have to notice and
then chase through `preg_last_error()`.
Same thing `JSON_THROW_ON_ERROR` and `FILTER_THROW_ON_FAILURE` already
do for their functions.

Where things landed:

- Nothing changes without the flag, byte for byte. All it changes is
how an error is delivered. At the end of a call, any error it would
have recorded is thrown as well, carrying the same code and message
`preg_last_error()`/`preg_last_error_msg()` would report.
- The exception is `\PregException` and the flag is
`PREG_THROW_ON_ERROR`, both settled back in the pre-RFC (thanks Tim).
- For array arguments the flag mirrors `preg_last_error()`. It throws
whatever error the call leaves there, which for `preg_replace()` over
an array is the last-processed entry, not the first. Whether the
functions should stop at the first failing entry is a separate
question about those functions, so it's out of scope here.

The two points raised during discussion, the exception hierarchy and
the warning behavior, are addressed in the RFC as it stands.

RFC: https://wiki.php.net/rfc/preg_throw_on_error
PR: https://github.com/php/php-src/pull/22797

So consider this my intent to vote...
Unless something new comes up, I'll open the vote in about two days.
If there's anything you'd want settled first, please bring it up now.

Thanks,
Osama
