advanced php language
Predefined Exceptions
PHP includes exception classes for common failure categories. Using the right one makes code easier to review because the exception name tells the next developer what kind of problem happened.
You do not need a custom exception for every failed check. Start with the predefined exceptions when they describe the problem clearly, then create a custom exception when the business meaning needs its own name.
Logic Problems And Runtime Problems
The two broad families you will meet are LogicException and RuntimeException.
LogicException means the program was asked to do something invalid. The caller or code path is wrong.
RuntimeException means the code was reasonable, but something failed while the program was running: a file was missing, a service returned bad data, or an external dependency failed.
<?php
declare(strict_types=1);
function requirePositiveId(int $id): int
{
if ($id < 1) {
throw new InvalidArgumentException('ID must be positive.');
}
return $id;
}
try {
echo requirePositiveId(0);
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// ID must be positive.
InvalidArgumentException extends LogicException because the caller supplied a value that does not satisfy the function contract.
InvalidArgumentException
Use InvalidArgumentException when a parameter has the wrong value for a function or method.
<?php
declare(strict_types=1);
function perPage(int $value): int
{
if ($value < 1 || $value > 100) {
throw new InvalidArgumentException('Per page must be between 1 and 100.');
}
return $value;
}
try {
echo perPage(500);
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Per page must be between 1 and 100.
This is one of the most common predefined exceptions in day-to-day PHP application code.
DomainException
Use DomainException when a value has the correct PHP type but is outside the allowed domain for the model.
<?php
declare(strict_types=1);
final class OrderStatus
{
public function __construct(
public string $value,
) {
if (!in_array($value, ['basket', 'paid', 'shipped'], true)) {
throw new DomainException('Unknown order status.');
}
}
}
try {
new OrderStatus('archived');
} catch (DomainException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Unknown order status.
For richer business failures, a custom exception may be clearer. For a compact value-object guard, DomainException is often enough.
LengthException
Use LengthException when the length of a value is invalid.
<?php
declare(strict_types=1);
function username(string $value): string
{
$length = strlen($value);
if ($length < 3 || $length > 20) {
throw new LengthException('Username must be 3 to 20 characters.');
}
return $value;
}
try {
echo username('Al');
} catch (LengthException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Username must be 3 to 20 characters.
The exception name tells the caller the failure is about size, not format or missing data.
OutOfBoundsException
Use OutOfBoundsException when a requested index, offset, or key does not exist.
<?php
declare(strict_types=1);
final class ProductList
{
/**
* @param list<string> $products
*/
public function __construct(
private array $products,
) {
}
public function at(int $index): string
{
if (!array_key_exists($index, $this->products)) {
throw new OutOfBoundsException('Product index does not exist.');
}
return $this->products[$index];
}
}
try {
echo (new ProductList(['Keyboard']))->at(3);
} catch (OutOfBoundsException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Product index does not exist.
Throwing can be clearer than returning null when null might also be a valid value.
UnexpectedValueException
Use UnexpectedValueException when a value from another source does not match what your code expected.
<?php
declare(strict_types=1);
function parseApiStatus(array $payload): string
{
$status = $payload['status'] ?? null;
if (!is_string($status)) {
throw new UnexpectedValueException('API status must be a string.');
}
return $status;
}
try {
echo parseApiStatus(['status' => ['paid']]);
} catch (UnexpectedValueException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// API status must be a string.
This is useful around decoded JSON, queues, configuration files, and third-party API responses.
RuntimeException
Use RuntimeException for runtime failures that do not have a better built-in exception.
<?php
declare(strict_types=1);
function readRequiredFile(string $path): string
{
$contents = @file_get_contents($path);
if ($contents === false) {
throw new RuntimeException('Required file could not be read.');
}
return $contents;
}
try {
readRequiredFile('/path/that/does-not-exist');
} catch (RuntimeException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Required file could not be read.
External resources fail. Throwing a runtime exception is often cleaner than letting false travel through the rest of the application.
When A Custom Exception Is Better
Create a custom exception when the caller needs to catch a business-specific failure or when the name carries important meaning.
<?php
declare(strict_types=1);
final class PaymentDeclined extends RuntimeException
{
}
function chargeCard(bool $approved): string
{
if (!$approved) {
throw new PaymentDeclined('The payment provider declined the card.');
}
return 'charged';
}
try {
echo chargeCard(false);
} catch (PaymentDeclined $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// The payment provider declined the card.
The custom exception lets the application handle declined payments differently from file, database, or configuration failures.
What You Should Be Able To Do
After this lesson, you should be able to choose a useful predefined exception, explain whether a failure is caller logic or runtime state, and know when a custom exception is worth adding.
For junior PHP work, this matters because exception names are part of communication. They make debugging, reviews, logs, and catch blocks easier to understand.
Practice
Practice: Choose Specific Exceptions
Create a small product lookup and validation example that uses predefined exceptions deliberately.
Task
Build:
- a
ProductListclass that stores product names - an
at()method that throwsOutOfBoundsExceptionfor a missing index - a
validatePerPage()function that throwsInvalidArgumentExceptionwhen the value is outside1to100 - examples that catch each exception and print the message
Use strict types. Keep the expected output inside the PHP code block as printed lines or comments.
Afterward, add a short note explaining why these exceptions are clearer than throwing generic Exception.
Show solution
This solution uses one exception for an invalid argument and another for a missing collection index.
<?php
declare(strict_types=1);
final class ProductList
{
/**
* @param list<string> $products
*/
public function __construct(
private array $products,
) {
}
public function at(int $index): string
{
if (!array_key_exists($index, $this->products)) {
throw new OutOfBoundsException('Product index does not exist.');
}
return $this->products[$index];
}
}
function validatePerPage(int $value): int
{
if ($value < 1 || $value > 100) {
throw new InvalidArgumentException('Per page must be between 1 and 100.');
}
return $value;
}
try {
validatePerPage(500);
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
try {
echo (new ProductList(['Keyboard']))->at(3);
} catch (OutOfBoundsException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Per page must be between 1 and 100.
// Product index does not exist.
These exceptions are clearer than generic Exception because their names describe the category of failure. A reviewer can see the first problem is a bad argument, while the second is a missing collection index.