PHP has had unwritten rules about exceptions in extensions for a while. New code shouldn't throw SPL exceptions, for example, but none of it was written down, so someone had to remember the rule and dig up old mailing list links to back it up. This RFC from Tim Düsterhus and Gina Peter Banyard adds those rules to PHP's official policies repository.
Where the rules come from
The policy follows the design ext/random used in PHP 8.2. That was the first new part of PHP's standard library built from scratch with its own namespace, and its exception design was later reused by the Date/Time exceptions RFC and the new URI parsing RFC. So the rules were already standard practice before this vote.
What the policy says
The policy applies to new extensions. Existing extensions should follow it too, but they can deviate to stay consistent with what they already have.
- Exception class names end in
ExceptionorError. - Exceptions can't be
final. - Each extension has its own base
Exceptionand baseError, named after the extension, and they extend the global\Exceptionand\Errordirectly. - All other exceptions extend the extension's own base classes.
- An extension only throws exceptions it defines itself, except for
TypeErrorandValueErroron invalid arguments. - If code the extension calls throws something else, the extension wraps it in one of its own exceptions and sets
$previousto the original.
Here is the example from the policy, for an extension called Example:
namespace Example; class ExampleException extends \Exception { } class ExampleError extends \Error { }
The policy also says names should be specific without repeating the extension name for no reason. Curl\FailedHttpRequestException is fine, while Curl\CurlFailedHttpRequestException is not.
What it means for you
Nothing in your code changes, since this is a rule for people who build PHP extensions. As a PHP developer, you get exceptions that follow the same pattern across new extensions, which means you can catch one base class to handle every error an extension throws.
The vote
The vote asked whether to accept the policy pull request at a specific commit. It passed 25 to 1, and voting closed on May 27, 2025.