PHP Exceptions

This is an English translation version of this artivcle [PHPの例外](https://qiita.com/koriym/items/17cfd1bbbaead1a2e5a9)

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

* [Predefined Exceptions](http://php.net/manual/en/reserved.exceptions.php)
* [SPL Exceptions](http://php.net/manual/en/spl.exceptions.php#spl.exceptions.tree)

# 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"

1. Exceptions are exceptions
2. Never use exceptions for control flow
3. 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](http://php.net/manual/en/language.exceptions.extending.php))

```php
class YourException extends Exception
{
}
```

It's recommended to create a `FileNotExistsException` by inheriting from the exception class rather than using `RuntimeException` directly:

```php
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
<?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

```php
$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.

```php
$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.

```php
!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

```php
$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](https://wiki.php.net/rfc/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."
> - http://stackoverflow.com/questions/14171714/php-runtime-or-logic-exception

While I've used SPL exceptions for a long time, I think Gordon's opinion has merit. In fact, [AuraPHP](http://auraphp.com/packages/) 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, the `Exceptions` folder can show what exceptions might occur in the project.

## References

* [Standard PHP Library slides]()
* [Exceptional PHP: Introduction to Exceptions](http://www.brandonsavage.net/exceptional-php-introduction-to-exceptions/)
* [SPL Improvements: Exceptions](https://wiki.php.net/rfc/spl-improvements/exceptions)
* [An Overview of Exceptions in PHP](https://tournasdimitrios1.wordpress.com/2014/07/05/an-overview-of-exceptions-in-php-2/)
* [Exception Advent Calendar 2014](http://qiita.com/advent-calendar/2014/exception)
* [OutOfRangeException vs. OutOfBoundsException](http://stackoverflow.com/questions/8193798/outofrangeexception-vs-outofboundsexception)