skip to content

In PHP, what separates SPL's LogicException family from its RuntimeException family, and which would you throw for a bad argument versus a missing record?

level: middleimportance: should knowfreq 38%

answer

  1. both extend Exception
  2. logic: fix the calling code
  3. runtime: only detectable while running
  4. InvalidArgumentException is a LogicException
  5. PDOException extends RuntimeException

basics

~20 s

LogicException subclasses signal a programming mistake that should lead to a code fix, such as InvalidArgumentException for a bad argument. RuntimeException subclasses signal conditions only detectable while running, such as OutOfBoundsException or UnexpectedValueException for a missing record or bad data.

solid answer

~40 s

Both families extend `Exception`. The manual defines `LogicException` as an error in program logic that "should lead directly to a fix in your code"; its subclasses are `InvalidArgumentException`, `DomainException`, `LengthException`, `OutOfRangeException` and `BadFunctionCallException` (with `BadMethodCallException`). `RuntimeException` is for errors "which can only be found on runtime": `OutOfBoundsException`, `OverflowException`, `UnderflowException`, `RangeException` and `UnexpectedValueException`; `PDOException` and `mysqli_sql_exception` extend it too. So a caller passing a negative page size to your method gets `InvalidArgumentException` — their code is wrong. A lookup for an ID that does not exist is a runtime condition: `OutOfBoundsException` or, better, a domain exception extending `RuntimeException`. The distinction tells callers whether catching and handling makes sense or whether the stack trace is a bug report.

code

php · 22 lines
php
<?php
declare(strict_types=1);

final class OrderNotFound extends RuntimeException {}

final class OrderRepository
{
    public function __construct(private array $rows) {}

    public function page(int $size): array
    {
        if ($size < 1) {
            throw new InvalidArgumentException("Page size must be >= 1, got $size");  // caller bug
        }
        return array_slice($this->rows, 0, $size);
    }

    public function get(int $id): array
    {
        return $this->rows[$id] ?? throw new OrderNotFound("Order $id not found");  // runtime fact
    }
}

go deeper

for a junior

Know that InvalidArgumentException and RuntimeException are standard SPL exceptions, both extending Exception.

for a middle

Sort the SPL subclasses into logic and runtime families and choose correctly between a bad argument and a missing record.

for a senior

Design catch sites that handle runtime failures while letting logic exceptions surface, and base domain exceptions on the right SPL parent.

for a principal

Define an organisation-wide exception taxonomy on top of SPL so libraries signal caller bugs and operational failures consistently.

## Two families under Exception The SPL extension ships a small set of exception classes so that code does not have to invent a class for every common failure. They split into two families, both extending `Exception` (not `Error`): | Family | Manual definition | Subclasses | |---|---|---| | `LogicException` | an error in the program logic; should lead directly to a fix in your code | `InvalidArgumentException`, `DomainException`, `LengthException`, `OutOfRangeException`, `BadFunctionCallException` > `BadMethodCallException` | | `RuntimeException` | an error that can only be found at run time | `OutOfBoundsException`, `OverflowException`, `UnderflowException`, `RangeException`, `UnexpectedValueException` | Several extensions build on the runtime side: `PDOException` and `mysqli_sql_exception` extend `RuntimeException`, because a lost connection or a failed query is a runtime condition. ## What each subclass means **Logic side** — the caller made a mistake the code could have prevented: - `InvalidArgumentException` — an argument is not of the expected type or form; - `DomainException` — a value is outside a defined valid domain; - `LengthException` — a length is invalid; - `OutOfRangeException` — an illegal index was requested, an error that should be detectable before running; - `BadFunctionCallException` / `BadMethodCallException` — a callback refers to an undefined function or method, or arguments are missing. **Runtime side** — the program is correct but the world is not as expected: - `OutOfBoundsException` — a value is not a valid key, which cannot be known in advance; - `OverflowException` — adding to a full container; - `UnderflowException` — removing from an empty container; - `RangeException` — a range error during execution, the runtime counterpart of `DomainException`; - `UnexpectedValueException` — a value, often a return value from another call, is not one of the expected ones. ## Choosing in practice 1. **Bad argument from calling code** → `InvalidArgumentException`. A `Paginator` given a page size of `-5` has been misused; the fix is in the caller. 2. **Record not found by ID** → a runtime-side exception. The ID may have been valid a moment ago; nobody's code is wrong. `OutOfBoundsException` fits literally, but most applications define `OrderNotFound extends RuntimeException` so the name carries the meaning. 3. **Third-party data malformed** → `UnexpectedValueException`, for example an API returning a status your code does not know. 4. **Invalid user input** → validation errors that the UI shows, usually a dedicated exception or a result object, not `InvalidArgumentException`, because the user did nothing wrong in your code's terms. ## Why the distinction pays off - **Handling strategy.** Runtime exceptions are candidates for catch-and-recover: show a 404, retry, fall back. Logic exceptions should surface loudly in logs and tests, because retrying the same call reproduces the same bug. - **Readable catch sites.** `catch (RuntimeException $e)` around a repository call says "I expect operational failures here", without swallowing programming errors. - **Library contracts.** Documenting that a method throws `InvalidArgumentException` for bad input and a `RuntimeException` subclass for I/O failures tells callers what to handle. ## Common misuses - **Throwing the bare parent.** `throw new RuntimeException('failed')` everywhere gives callers nothing to catch selectively; extend it with a named class. - **Using `InvalidArgumentException` for everything.** Missing records, network failures and bad upstream data are not argument errors. - **Catching `LogicException` to recover.** A logic exception means the calling code is wrong; catching it to carry on hides a bug that will recur. - **Confusing the pairs.** `DomainException` versus `RangeException`, and `OutOfRangeException` versus `OutOfBoundsException`, differ exactly by the logic-versus-runtime axis. ## Relation to the Error branch The engine's `TypeError` and `ValueError` are close cousins of `InvalidArgumentException`, but they live on the `Error` branch and are thrown by PHP itself. Libraries validating their own arguments conventionally throw `InvalidArgumentException`, so callers' existing `catch (Exception)` handlers still see the failure.

  • What is the difference between OutOfRangeException and OutOfBoundsException in PHP's SPL?
    `OutOfRangeException` is a `LogicException`: an illegal index that should have been caught before running, such as a hard-coded position beyond a fixed-size structure. `OutOfBoundsException` is a `RuntimeException`: a key that is invalid only given run-time data, such as an ID absent from the loaded set.
  • Which SPL parent does PDOException have, and why does it matter for catch blocks?
    `PDOException` extends `RuntimeException`, so `catch (RuntimeException $e)` around data access catches database failures along with other runtime conditions. It does not catch `InvalidArgumentException` or any `Error`, so programming mistakes still surface.

saying these in an interview costs you the question

  • InvalidArgumentException extends RuntimeException.
  • LogicException and RuntimeException are on the Error branch.
  • A missing database row is best reported as InvalidArgumentException.
  • PDOException extends LogicException because queries are code.
  • The SPL families differ only in name and carry no meaning.