PHP Fundamentals

Exceptions & Try-Catch

13 min Lesson 27 of 45

Exceptions & Try-Catch

Exceptions provide a powerful way to handle errors in PHP. They allow you to separate error-handling code from normal code, making your applications more maintainable and robust.

What are Exceptions?

Exceptions are objects that represent errors or exceptional conditions. When an error occurs, an exception is "thrown" and can be "caught" by exception handlers.

<?php // Basic exception structure try { // Code that might throw an exception throw new Exception("Something went wrong!"); } catch (Exception $e) { // Handle the exception echo "Caught exception: " . $e->getMessage(); } // Output: Caught exception: Something went wrong! ?>
Note: Unlike errors, exceptions can be caught and handled gracefully, allowing your application to recover or fail elegantly.

Try-Catch-Finally Blocks

The try-catch-finally structure provides complete control over exception handling:

<?php function readConfigFile($filename) { $file = null; try { // Try to open the file if (!file_exists($filename)) { throw new Exception("Config file not found: $filename"); } $file = fopen($filename, 'r'); if ($file === false) { throw new Exception("Cannot open file: $filename"); } $content = fread($file, filesize($filename)); return $content; } catch (Exception $e) { // Handle the exception error_log($e->getMessage()); return false; } finally { // Always executed, even if exception occurs if ($file !== null) { fclose($file); } echo "Cleanup completed"; } } $config = readConfigFile('config.ini'); ?>
Tip: The finally block is perfect for cleanup operations like closing files or database connections, as it runs regardless of whether an exception was thrown.

Exception Properties and Methods

Exception objects have useful properties and methods for debugging:

<?php try { throw new Exception("Test exception", 100); } catch (Exception $e) { echo "Message: " . $e->getMessage() . "\n"; // Error message echo "Code: " . $e->getCode() . "\n"; // Error code echo "File: " . $e->getFile() . "\n"; // File where thrown echo "Line: " . $e->getLine() . "\n"; // Line number echo "Trace: " . $e->getTraceAsString() . "\n"; // Stack trace echo "String: " . $e->__toString() . "\n"; // Full exception info } // Output: // Message: Test exception // Code: 100 // File: /path/to/file.php // Line: 3 // Trace: #0 {main} // String: Exception: Test exception in /path/to/file.php:3 ?>

Custom Exception Classes

You can create custom exception classes for specific error types:

<?php // Base custom exception class DatabaseException extends Exception {} // Specific database exceptions class ConnectionException extends DatabaseException {} class QueryException extends DatabaseException {} // Usage class Database { private $connection; public function connect($host, $username, $password) { $this->connection = @mysqli_connect($host, $username, $password); if (!$this->connection) { throw new ConnectionException( "Failed to connect: " . mysqli_connect_error(), mysqli_connect_errno() ); } } public function query($sql) { if (!$this->connection) { throw new ConnectionException("No database connection"); } $result = mysqli_query($this->connection, $sql); if ($result === false) { throw new QueryException( "Query failed: " . mysqli_error($this->connection) ); } return $result; } } // Handling custom exceptions try { $db = new Database(); $db->connect('localhost', 'user', 'wrong_password'); $result = $db->query('SELECT * FROM users'); } catch (ConnectionException $e) { echo "Connection error: " . $e->getMessage(); } catch (QueryException $e) { echo "Query error: " . $e->getMessage(); } catch (DatabaseException $e) { echo "Database error: " . $e->getMessage(); } ?>
Note: Catch more specific exceptions before general ones. PHP matches catch blocks in order from top to bottom.

Multiple Catch Blocks

You can catch different exception types with different handlers:

<?php class ValidationException extends Exception {} class FileException extends Exception {} function processUpload($file) { try { // Validate file if (empty($file['name'])) { throw new ValidationException("No file uploaded"); } if ($file['size'] > 1000000) { throw new ValidationException("File too large"); } // Process file if (!move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name'])) { throw new FileException("Failed to move uploaded file"); } return true; } catch (ValidationException $e) { // Handle validation errors - show to user echo "<div class='error'>" . $e->getMessage() . "</div>"; return false; } catch (FileException $e) { // Handle file errors - log and show generic message error_log($e->getMessage()); echo "<div class='error'>Upload failed. Please try again.</div>"; return false; } } ?>

Re-throwing Exceptions

You can catch an exception, perform some action, and then re-throw it:

<?php function processPayment($amount) { try { if ($amount <= 0) { throw new Exception("Invalid amount"); } // Process payment logic here // If payment fails: throw new Exception("Payment gateway error"); } catch (Exception $e) { // Log the error error_log("Payment failed: " . $e->getMessage()); // Re-throw for higher level handling throw new Exception("Payment processing failed", 0, $e); } } try { processPayment(-50); } catch (Exception $e) { echo "Error: " . $e->getMessage(); // Get previous exception if ($e->getPrevious()) { echo "\nOriginal error: " . $e->getPrevious()->getMessage(); } } ?>
Tip: Re-throwing exceptions is useful when you want to log errors at multiple levels or add context to error messages.

Catching Multiple Exception Types (PHP 7.1+)

You can catch multiple exception types in one catch block:

<?php class NetworkException extends Exception {} class TimeoutException extends Exception {} class PermissionException extends Exception {} try { // Some operation that might throw different exceptions throw new NetworkException("Connection lost"); } catch (NetworkException | TimeoutException $e) { // Handle network and timeout errors the same way echo "Connection issue: " . $e->getMessage(); // Retry logic here } catch (PermissionException $e) { // Handle permission errors differently echo "Access denied: " . $e->getMessage(); } ?>

Exception Best Practices

<?php // 1. Use specific exception types class UserNotFoundException extends Exception {} class InvalidEmailException extends Exception {} // 2. Provide meaningful messages throw new InvalidEmailException( "Email '$email' is not valid. Expected format: user@domain.com" ); // 3. Include error codes for categorization throw new Exception("Database error", 1001); // 4. Don't catch exceptions you can't handle try { $result = riskyOperation(); } catch (SpecificException $e) { // Only catch if you can do something useful handleError($e); } // Let other exceptions bubble up // 5. Always log exceptions in production try { criticalOperation(); } catch (Exception $e) { error_log($e->getMessage() . "\n" . $e->getTraceAsString()); throw $e; // Re-throw after logging } ?>

Converting Errors to Exceptions

You can convert PHP errors into exceptions for consistent error handling:

<?php // Error handler that converts errors to exceptions function errorToException($severity, $message, $file, $line) { throw new ErrorException($message, 0, $severity, $file, $line); } set_error_handler('errorToException'); try { // This would normally trigger a warning $result = 10 / 0; } catch (ErrorException $e) { echo "Caught error as exception: " . $e->getMessage(); } restore_error_handler(); // Restore default error handler ?>

Global Exception Handler

Set a global exception handler for uncaught exceptions:

<?php function globalExceptionHandler($exception) { // Log the exception error_log("Uncaught exception: " . $exception->getMessage()); error_log($exception->getTraceAsString()); // Show user-friendly error page if (php_sapi_name() !== 'cli') { http_response_code(500); echo "<h1>Something went wrong</h1>"; echo "<p>We're sorry for the inconvenience. Please try again later.</p>"; } else { echo "Error: " . $exception->getMessage() . "\n"; } } set_exception_handler('globalExceptionHandler'); // Any uncaught exception will be handled by globalExceptionHandler throw new Exception("This will be caught by global handler"); ?>
Warning: Never display detailed exception information (stack traces, file paths) to users in production. Always log details and show generic error messages.

Practical Exception Handling Example

<?php class UserService { private $db; public function registerUser($email, $password) { try { // Validate input if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new ValidationException("Invalid email format"); } if (strlen($password) < 8) { throw new ValidationException("Password must be at least 8 characters"); } // Check if user exists $existing = $this->db->query("SELECT id FROM users WHERE email = ?", [$email]); if ($existing) { throw new ValidationException("Email already registered"); } // Create user $hashedPassword = password_hash($password, PASSWORD_DEFAULT); $this->db->query( "INSERT INTO users (email, password) VALUES (?, ?)", [$email, $hashedPassword] ); return true; } catch (ValidationException $e) { // Show validation errors to user throw $e; } catch (DatabaseException $e) { // Log database errors, show generic message error_log("Registration failed: " . $e->getMessage()); throw new Exception("Registration failed. Please try again."); } catch (Exception $e) { // Catch-all for unexpected errors error_log("Unexpected error in registerUser: " . $e->getMessage()); throw new Exception("An unexpected error occurred"); } } } // Usage try { $userService = new UserService(); $userService->registerUser('user@example.com', 'password123'); echo "Registration successful!"; } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ?>
Exercise:
  1. Create a file upload class that throws custom exceptions for different error conditions
  2. Write a function that reads JSON from a file and throws exceptions for file not found, invalid JSON, etc.
  3. Implement a calculator class with try-catch blocks that handle division by zero
  4. Create a global exception handler that logs errors and displays appropriate messages based on environment (development vs production)

Common Exception Patterns

<?php // Pattern 1: Fail fast with exceptions function processOrder($orderId) { if (empty($orderId)) { throw new InvalidArgumentException("Order ID is required"); } // Continue processing... } // Pattern 2: Exception chaining for context try { connectToAPI(); } catch (Exception $e) { throw new ApiException("Failed to connect to payment API", 0, $e); } // Pattern 3: Resource cleanup with finally function downloadFile($url) { $handle = null; try { $handle = fopen($url, 'r'); return stream_get_contents($handle); } finally { if ($handle) { fclose($handle); } } } ?>