This is an English translation version of this artivcle PHPの例外
Predefined Exceptions and SPL Exceptions
PHP has two types of exceptions: predefined exceptions that come with PHP core and Standard PHP Library (SPL) exceptions that are bundled with PHP by default. While SPL is included by default and can be used as a standard PHP feature, it has its own separate documentation.
SPL Exceptions
The author of SPL is Marcus Börger, who has led many packages including PDO (refer to PECL site: https://pecl.php.net/user/helly). Here are two excerpts about exceptions from Marcus's slide "Standard PHP Library":
"Three Rules to Follow"
- Exceptions are exceptions
- Never use exceptions for control flow
- Never ever use exceptions for parameter passing
These three rules essentially mean that exceptions should only be used for truly exceptional cases and not for normal control flow.
Examples of exceptions include: (from "An Overview of Exceptions in PHP")
- Database connection failure
- Web API access failure
- File system errors
- Template or configuration file parsing errors
"Creating Special Exceptions"
- Exceptions should be specialized = Create your own
- Extend built-in exceptions (PHP Manual: Extending Exceptions)
class YourException extends Exception
{
}
It's recommended to create a FileNotExistsException
by inheriting from the exception class rather than using RuntimeException
directly:
throw new \RuntimeException("$file does not exist"); // OK
class FileNotExistsException extends \RuntimeException
{
}
throw new FileNotExistsException($file); // Better
Distinguishing LogicException from RuntimeException
While PHP manual's explanations are often considered unclear, Marcus provides clearer explanations with examples:
- LogicException: Exceptions that can be detected at compile time or during application design
- RuntimeException: Exceptions that can only be detected at runtime. Base class for all database exceptions
LogicExceptions are exceptions (bugs) that can be found through code inspection, while RuntimeExceptions are exceptions that can only be discovered by running the program - exceptions that occur due to external conditions or inputs that the program assumes.
LogicException Example
<?php
abstract class MyIteratorWrapper implements Iterator
{
private $it;
public function __construct(Iterator $it)
{
$this->it = $it;
}
public function __call($func, $args)
{
$callee = [$this->it, $func];
if (!is_callable($callee)) {
throw new BadMethodCallException(); // LogicException
}
return call_user_func_array($callee, $args);
}
}
Calling a non-existent method is an application design issue that can be detected through program inspection before execution.
RuntimeException Examples
$fo = new SplFileObject($file); // RuntimeException
Files might be inaccessible or non-existent, but the program itself is correct. SplFileObject throws a RuntimeException when it can't open a file.
$fo = new SplFileObject($file);
$fo->setFlags(SplFileObject::DROP_NEWLINE);
$data = array();
foreach($fo as $l) {
if (/*** CHECK DATA ***/) {
throw new Exception((); // RuntimeException
}
$data[] = $l;
}
The data changes each time it's read since it uses an external file. This is a runtime exception.
!preg_match($regex, $l) // UnexpectValueException
count($l=split(',', $l)) !=3 // RangeException
count($data) < 10) // UnderflowException
count($data) > 99) // OverflowException
count($data) < 10 || count($data) > 99) // OutOfBoundsException
These are all RuntimeExceptions.
Mixed Example
$fo = new SplFileObject($file); // RuntimeException
$fo->setFlags(SplFileObject::DROP_NEWLINE);
$data = array();
foreach($fo as $l) {
if (!preg_match('/\d,\d/', $l)) {
throw new UnexpectedValueException(); // RuntimeException
}
$data[] = $l;
}
if (count($data) < 10) throw new UnderflowException(); // RuntimeException
// maybe more processing code
foreach($data as $v) {
if (count($v) !== 2) {
throw new DomainException(); // LogicException
}
$v = $v[0] * $v[1];
}
It throws a data domain exception when the precondition of "needing two numbers for multiplication" is not met.
Note: DomainException's explanation has been criticized for being unclear (#47097) and was subsequently updated (#291058). User notes in the manual explain it as exceptions for when data falls outside its handling range (data domain), such as "0 cannot be a divisor" or "'foo' is not a valid day of the week."
LogicExceptions should never be thrown in production sites, while RuntimeExceptions may occur in production.
Distinguishing OutOfRangeException from OutOfBoundsException
These two exceptions have similar names and roles:
OutOfRangeException
- Exception thrown when an invalid index is requested. This must be detected at compile time. (LogicException)
OutOfBoundsException
- Exception thrown when a value is not a valid key. This cannot be detected at compile time. (RuntimeException)
The distinction follows the same principle as LogicException/RuntimeException:
OutOfRangeException is for violations of ranges determined by code alone, while OutOfBoundsException is for invalid keys discovered at runtime when data is read from external sources. OutOfRangeException indicates a bug, while OutOfBoundsException is thrown based on runtime conditions.
The Pros and Cons of Using SPL Exceptions
As noted in SPL Improvements: Exceptions, one drawback of SPL exceptions is insufficient documentation, which may affect its effectiveness as a common language among developers.
While many libraries and frameworks use SPL exceptions, and numerous blog posts recommend their use, there are dissenting opinions. @go_oh, a frequent Stack Overflow contributor, states:
"Frankly speaking, SPL's exception hierarchy is messy. That's why I think it's better to create your own exceptions (by extending \Exception) as you like."
While I've used SPL exceptions for a long time, I think Gordon's opinion has merit. In fact, AuraPHP doesn't use SPL exceptions and instead inherits all custom exceptions from the built-in \Exception
, yet remains clean and functional.
Summary
-
PHP provides a hierarchical exception mechanism through SPL, but like other SPL features, using SPL exceptions is optional. Exception concepts vary among languages and users, leading to diverse discussions and opinions.
-
The principle "Exceptions are exceptions" is crucial. Using exceptions as alternatives to goto for changing program execution position, or as replacements for switch or if statements, are anti-patterns of using exceptions for control flow. Using them to pass values is similarly problematic.
-
Instead of explaining exceptions with messages like "No write permission," use special error classes (like
NotWritableException
). By preparing domain-specific exceptions, theExceptions
folder can show what exceptions might occur in the project.