Hi
On 9/5/26 01:53, Osama Aldemeery wrote:
Now what I would suggest instead of breaking that guarantee, is to pull
enriching the anemic `preg_last_error_msg()` error message forward into
this RFC instead of leaving it for later, store the real reason in the
error state, and the exception inherits it through the very same channel,
with the guarantee intact.
That would also work for me. But the E_WARNING should remain when the
PREG_THROW_ON_ERROR flag is not set, because some users might rely on
the warning being emitted to turn it into an Exception themselves by
means of an error handler.
What is important to me is that the new flag cleanly results in an
Exception and only an Exception for all possible errors, because this is
what users will expect from it.
On your second point, if this is a violation of a policy, then there isn't
much to argue. I will just retract the vote and fix that.
But I think I got confused here, and I would appreciate you explaining how
that violates the policy.
To make sure we're on the same ground, this is what I understood from your
statement about wrapping exceptions thrown in user callbacks:
```
preg_replace_callback(
$pattern,
fn () => throw new CustomException(), // <- You want this wrapped in
PregException?
$subject,
flags: PREG_THROW_ON_ERROR,
);
```
Yes. I expect a PregException where $e->getPrevious() instanceof
CustomException().
If I got it right (and I suspect I did), then how does that violate the
policy?
A user callback isn't external functionality, is it? Because as far as I
understand, external functionality is something the extension itself
depends on as part of its own implementation.
Arguably this specific case is a bit debatable, but as the author of the
throwable policy RFC, I believe that it is at least violated in spirit.
The goal of the throwable policy generally, and also with regard to that
specific paragraph is to allow reliably handling groups of errors
without needing to wrap every individual statement into its own
try-catch block. Consider this:
try {
$contents = get_from_api('http://example.com');
// sanitize credit card numbers
$contents = preg_replace_callback(
'/[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}/',
function ($matches) {
return mask_credit_card($matches[0]);
},
$contents,
falgs: PREG_THROW_ON_ERROR,
);
echo $contents;
} catch (PregException $e) {
echo "Sanitization failed\n";
} catch (HttpException $e) {
echo "Download failed\n";
}
I am catching the PregException to handle failures during the credit
card sanitization step. If mask_credit_card() throws its own exception
that is not wrapped, my catch blocks are insufficient and I would
instead need to write it something like this:
try {
$contents = get_from_api('http://example.com');
} catch (HttpException $e) {
echo "Download failed\n";
return;
}
try {
// sanitize credit card numbers
$contents = preg_replace_callback(
'/[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}/',
function ($matches) {
return mask_credit_card($matches[0]);
},
$contents,
falgs: PREG_THROW_ON_ERROR,
);
} catch (Exception $e) {
echo "Sanitization failed\n";
return;
}
echo $contents;
To reliably handle just the exceptions that happen during sanitization
and nothing else. This is a lot of extra boilerplate code and noise.
Now if I am still interested in the inner exception for the callback
failure, something like this would work:
} catch (PregException $e) {
if ($e->getCode() === PregException::CALLBACK_FAILURE) {
echo "Sanitization callback failed: ",
$e->getPrevious()->getMessage();
} else {
echo "Sanitization failed\n";
}
}
Because if the error code is callback failure, I know that there is a
previous Exception. So I don't lose any functionality / information.
I am also unaware of any functions that behave like that (wraps exceptions
thrown in user callbacks in its own exception).
There are a few cases where the CSPRNG (which throws RandomException on
failure) is used internally and the exception on CSPRNG failure is
wrapped. However much of the standard library predates the throwable
policy (which was accepted in May 2025;
https://wiki.php.net/rfc/extension_exceptions), that's why it doesn't
follow it.
In fact, the opposite is the case for one of the precedents this RFC
follows (`json_encode()` with `JSON_THROW_ON_ERROR` - although it doesn't
accept a user callback): https://3v4l.org/CtHYH#v8.5.10
Yes, that flag and JsonSerializable itself is much older than the policy.
Best regards
Tim Düsterhus