---
title: Use Custom Error Classes
impact: HIGH
impactDescription: enables proper error categorization and handling
tags: error-handling, custom-errors, exceptions, patterns, quality, php
---

## Use Custom Error Classes

Defining your own exception classes allows you to categorize errors, attach relevant metadata, and implement specific handling strategies for different parts of your application. It provides a clear contract for the callers of your methods.

**Incorrect (generic exceptions):**

```php
throw new \Exception("User not found");
throw new \InvalidArgumentException("Invalid input data");
throw new \RuntimeException("Database connection failed");
```

**Correct (custom exception hierarchy):**

```php
// Base application exception
abstract class AppServiceException extends \Exception {
    protected int $statusCode = 500;
    protected string $errorCode = 'INTERNAL_ERROR';
    protected array $context = [];

    public function __construct(string $message, array $context = [], ?\Throwable $previous = null) {
        parent::__construct($message, 0, $previous);
        $this->context = $context;
    }

    public function getStatusCode(): int { return $this->statusCode; }
    public function getErrorCode(): string { return $this->errorCode; }
    public function getContext(): array { return $this->context; }
}

// Specific domain exceptions
class UserNotFoundException extends AppServiceException {
    protected int $statusCode = 404;
    protected string $errorCode = 'USER_NOT_FOUND';
}

class InsufficientBalanceException extends AppServiceException {
    protected int $statusCode = 422;
    protected string $errorCode = 'INSUFFICIENT_BALANCE';
}

// Usage
if (!$user) {
    throw new UserNotFoundException("User {$userId} does not exist", ['id' => $userId]);
}
```

**Benefits:**
- **Granular Catching**: You can catch specific domain errors (e.g., `catch (UserNotFoundException $e)`) while letting others bubble up.
- **Structured Metadata**: Attach request IDs, user IDs, or input data directly to the exception.
- **HTTP Mapping**: Easily map exceptions to HTTP status codes in a global error handler (e.g., in Laravel `Handler.php` or Symfony `ExceptionSubscriber`).
- **Observability**: Search logs for specific exception class names.

**Tools:** PHPUnit (expectException), PHPStan, Psalm
