Back to Tutorials
PHPBeginner

Modern Exception Handling

Learn how to manage errors and exceptions gracefully in modern PHP applications.

Alex Rivers
December 28, 2025
25 min read

Modern Exception Handling in PHP

Learn how to handle errors and exceptions gracefully in modern PHP applications for better debugging and user experience.

Understanding Exceptions

Basic Try-Catch

php
1try {
2    $result = riskyOperation();
3} catch (Exception $e) {
4    echo "Error: " . $e->getMessage();
5}

Multiple Catch Blocks

php
1try {
2    $user = findUser($id);
3    $user->sendEmail();
4} catch (UserNotFoundException $e) {
5    echo "User not found";
6} catch (EmailException $e) {
7    echo "Failed to send email";
8} catch (Exception $e) {
9    echo "An unexpected error occurred";
10}

Creating Custom Exceptions

Basic Custom Exception

php
1class UserNotFoundException extends Exception
2{
3    public function __construct(int $userId)
4    {
5        parent::__construct("User with ID $userId not found");
6    }
7}

Advanced Custom Exception

php
1class ValidationException extends Exception
2{
3    public function __construct(
4        private array $errors,
5        string $message = "Validation failed"
6    ) {
7        parent::__construct($message);
8    }
9    
10    public function getErrors(): array
11    {
12        return $this->errors;
13    }
14}
15
16// Usage
17throw new ValidationException([
18    'email' => 'Invalid email format',
19    'password' => 'Password too short'
20]);

Exception Hierarchy

php
1class AppException extends Exception {}
2
3class DatabaseException extends AppException {}
4class ValidationException extends AppException {}
5class AuthenticationException extends AppException {}
6
7// Specific exceptions
8class UserNotFoundException extends DatabaseException {}
9class InvalidCredentialsException extends AuthenticationException {}

Practical Examples

User Service with Exceptions

php
1class UserService
2{
3    public function __construct(
4        private Database $db,
5        private Validator $validator
6    ) {}
7    
8    public function register(array $data): User
9    {
10        // Validate input
11        $errors = $this->validator->validate($data, [
12            'email' => 'required|email|unique:users',
13            'password' => 'required|min:8'
14        ]);
15        
16        if (!empty($errors)) {
17            throw new ValidationException($errors);
18        }
19        
20        // Create user
21        try {
22            $user = new User(
23                email: $data['email'],
24                password: password_hash($data['password'], PASSWORD_ARGON2ID)
25            );
26            
27            $this->db->execute(
28                'INSERT INTO users (email, password) VALUES (?, ?)',
29                [$user->email, $user->password]
30            );
31            
32            $user->id = $this->db->lastInsertId();
33            
34            return $user;
35        } catch (PDOException $e) {
36            throw new DatabaseException('Failed to create user', 0, $e);
37        }
38    }
39    
40    public function login(string $email, string $password): User
41    {
42        $users = $this->db->query(
43            'SELECT * FROM users WHERE email = ?',
44            [$email]
45        );
46        
47        if (empty($users)) {
48            throw new InvalidCredentialsException('Invalid email or password');
49        }
50        
51        $user = User::fromArray($users[0]);
52        
53        if (!password_verify($password, $user->password)) {
54            throw new InvalidCredentialsException('Invalid email or password');
55        }
56        
57        return $user;
58    }
59}

Controller with Exception Handling

php
1class UserController
2{
3    public function __construct(private UserService $userService) {}
4    
5    public function register(): void
6    {
7        try {
8            $user = $this->userService->register($_POST);
9            
10            http_response_code(201);
11            echo json_encode([
12                'success' => true,
13                'user' => [
14                    'id' => $user->id,
15                    'email' => $user->email
16                ]
17            ]);
18        } catch (ValidationException $e) {
19            http_response_code(422);
20            echo json_encode([
21                'success' => false,
22                'errors' => $e->getErrors()
23            ]);
24        } catch (DatabaseException $e) {
25            http_response_code(500);
26            echo json_encode([
27                'success' => false,
28                'message' => 'Server error. Please try again later.'
29            ]);
30            
31            // Log the actual error
32            error_log($e->getMessage());
33        }
34    }
35}

Global Exception Handler

php
1set_exception_handler(function (Throwable $e) {
2    // Log the exception
3    error_log($e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
4    
5    // Send appropriate response
6    http_response_code(500);
7    
8    if ($e instanceof ValidationException) {
9        http_response_code(422);
10        echo json_encode([
11            'error' => 'Validation failed',
12            'details' => $e->getErrors()
13        ]);
14    } elseif ($e instanceof AuthenticationException) {
15        http_response_code(401);
16        echo json_encode(['error' => 'Authentication failed']);
17    } else {
18        echo json_encode(['error' => 'Internal server error']);
19    }
20});

Finally Block

php
1$file = fopen('data.txt', 'r');
2
3try {
4    $content = fread($file, filesize('data.txt'));
5    processContent($content);
6} catch (Exception $e) {
7    echo "Error processing file: " . $e->getMessage();
8} finally {
9    // Always executes, even if exception is thrown
10    fclose($file);
11}

Best Practices

1. Be Specific

php
1// BAD - Too generic
2throw new Exception('Error');
3
4// GOOD - Specific and informative
5throw new UserNotFoundException($userId);

2. Don't Catch What You Can't Handle

php
1// BAD - Swallowing exceptions
2try {
3    criticalOperation();
4} catch (Exception $e) {
5    // Do nothing
6}
7
8// GOOD - Let it bubble up or handle properly
9try {
10    criticalOperation();
11} catch (Exception $e) {
12    log($e);
13    throw $e; // Re-throw if you can't handle it
14}

3. Provide Context

php
1// BAD
2throw new Exception('Invalid data');
3
4// GOOD
5throw new ValidationException([
6    'field' => 'email',
7    'value' => $email,
8    'rule' => 'email format',
9    'message' => 'Email must be valid'
10]);

Logging Exceptions

php
1class Logger
2{
3    public function logException(Throwable $e): void
4    {
5        $message = sprintf(
6            "[%s] %s: %s in %s:%d\nStack trace:\n%s",
7            date('Y-m-d H:i:s'),
8            get_class($e),
9            $e->getMessage(),
10            $e->getFile(),
11            $e->getLine(),
12            $e->getTraceAsString()
13        );
14        
15        error_log($message);
16    }
17}

Conclusion

Proper exception handling makes your code more robust, easier to debug, and provides better user experience. Always use specific exceptions, handle errors gracefully, and log appropriately!