Reading — step 1 of 7
Learn
Custom Exceptions and the Hierarchy
throw new Exception('bad input') works, but it tells the catcher nothing it can
act on — every failure arrives as the same type, so the only way to tell them
apart is to read the message string and hope nobody rewords it.
Your own exception classes fix that: the type carries the meaning, so a caller can catch exactly the failure it knows how to handle and let the rest keep travelling. To place those classes correctly you need PHP's built-in hierarchy, which is where this lesson ends.
Custom exception classes
<?php
class ValidationException extends Exception {
private string $field;
public function __construct(string $field, string $message) {
parent::__construct($message);
$this->field = $field;
}
public function getField(): string {
return $this->field;
}
}
try {
throw new ValidationException('email', 'must contain @');
} catch (ValidationException $e) {
echo "field ", $e->getField(), ": ", $e->getMessage(), "\n";
}
//> field email: must contain @
Two things make that work. extends Exception places your class inside the hierarchy, so a catch (Exception $e) further out still finds it. And parent::__construct($message) is what gives getMessage() something to return — write your own constructor without that call and getMessage() hands back string(0) "".
Grader note: modern PHP compresses that constructor with 8.0 property promotion, often adding 8.1's
readonly. Both are parse errors on this course's PHP 7.4 grader, and the file will not run at all. Declare the property in the class body and assign it, exactly as above.
Most of the time you need far less. A body-less marker class is already a complete, useful exception:
<?php
class AgeException extends Exception {}
try {
throw new AgeException('not a number');
} catch (AgeException $e) {
echo $e->getMessage(), "\n";
}
//> not a number
It inherits Exception's constructor, so the message still works — and that is precisely the shape your exercise needs.
The exception hierarchy
PHP 7 unified errors and exceptions. Everything throwable implements \Throwable:
Throwable
├─ Error (raised by the engine)
│ ├─ TypeError (PHP 7.0)
│ ├─ ValueError (PHP 8.0 — absent on this grader)
│ ├─ ParseError
│ └─ ArithmeticError
│ └─ DivisionByZeroError
└─ Exception (your application's errors)
├─ LogicException
│ ├─ InvalidArgumentException
│ └─ DomainException
└─ RuntimeException
├─ OutOfBoundsException
└─ UnexpectedValueException
The split bites the first time an engine error slips past a catch:
<?php
function needInt(int $n): int { return $n; }
try {
needInt([]);
} catch (Exception $e) {
echo "caught as Exception\n";
} catch (Throwable $t) {
echo "only Throwable caught it: ", get_class($t), "\n";
}
//> only Throwable caught it: TypeError
catch (Exception $e) handles your application's failures. catch (\Throwable $e) handles those and the engine's. Wrapping everything in Throwable hides bugs; using it once at the very top of a program is reasonable.
Common mistakes
- Losing the message in a custom constructor — if you write
__construct, callparent::__construct($message)orgetMessage()comes back empty. - Extending
Errorfor an application failure —Erroris the engine's half of the hierarchy. Your classes belong underException. - Forgetting the leading
\—Exceptionresolves fine at global scope; inside a namespace write\Exceptionor import it withuse. - Catching
Exceptionand expecting to see aTypeError— that one is anError. Only\Throwablecovers both halves.
Your exercise
Validate Age wants class AgeException extends \Exception {} and a validateAge(string $s): int that throws an AgeException carrying the message empty, not a number, or negative, and otherwise returns the number.
The mistake the grader catches is the message text: the tests compare error: not a number byte for byte, so not numeric, a capital letter, or appending the offending input all fail. The second is check order — test $s === '' before is_numeric($s), because the hidden empty-input case expects error: empty while is_numeric('') is already false, so the wrong order reports not a number there instead. Cast with (int) only once the numeric test has passed, print age: 42 on success and error: <message> on failure, and close both lines with "\n". The starter's try / catch (\Exception $e) frame is already written; uncomment its lines once your function exists.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…