Error Handling and Exceptions in PHP
Error handling is crucial for building robust, production-ready applications. PHP provides comprehensive error handling mechanisms including traditional error reporting and modern exception-based handling.
Understanding when and how to use each approach, along with proper error logging and user-friendly error messages, is essential for creating maintainable and reliable applications.
Exceptions provide a structured way to handle errors and exceptional conditions in your code.
Basic Exception Handling:
1<?php
2declare(strict_types=1);
3
4function divide(float $a, float $b): float {
5 if ($b === 0.0) {
6 throw new Exception('Division by zero is not allowed');
7 }
8 return $a / $b;
9}
10
11try {
12 $result = divide(10, 2);
13 echo "Result: $result\n"; // Output: Result: 5
14
15 $result = divide(10, 0); // This will throw an exception
16 echo "This line won't execute\n";
17
18} catch (Exception $e) {
19 echo "Error: " . $e->getMessage() . "\n";
20 echo "File: " . $e->getFile() . "\n";
21 echo "Line: " . $e->getLine() . "\n";
22}
23?>Multiple Catch Blocks:
1<?php
2class InvalidEmailException extends Exception {}
3class InvalidPasswordException extends Exception {}
4
5function createUser(string $email, string $password): void {
6 if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
7 throw new InvalidEmailException("Invalid email format: $email");
8 }
9
10 if (strlen($password) < 8) {
11 throw new InvalidPasswordException("Password must be at least 8 characters");
12 }
13
14 echo "User created successfully\n";
15}
16
17try {
18 createUser('invalid-email', 'short');
19
20} catch (InvalidEmailException $e) {
21 echo "Email Error: " . $e->getMessage() . "\n";
22 // Handle email-specific error
23
24} catch (InvalidPasswordException $e) {
25 echo "Password Error: " . $e->getMessage() . "\n";
26 // Handle password-specific error
27
28} catch (Exception $e) {
29 echo "General Error: " . $e->getMessage() . "\n";
30 // Handle any other exception
31}
32?>Finally Block:
1<?php
2function processFile(string $filename): void {
3 $file = fopen($filename, 'r');
4
5 try {
6 // Process file
7 $content = fread($file, filesize($filename));
8 echo "File processed: " . strlen($content) . " bytes\n";
9
10 // Simulate an error
11 if (rand(0, 1)) {
12 throw new Exception("Processing error");
13 }
14
15 } catch (Exception $e) {
16 echo "Error: " . $e->getMessage() . "\n";
17
18 } finally {
19 // Always executes, even if exception is thrown
20 if (is_resource($file)) {
21 fclose($file);
22 echo "File closed\n";
23 }
24 }
25}
26?>Rethrowing Exceptions:
1<?php
2function validateData(array $data): void {
3 try {
4 if (empty($data['email'])) {
5 throw new InvalidEmailException("Email is required");
6 }
7
8 } catch (InvalidEmailException $e) {
9 // Log the error
10 error_log($e->getMessage());
11
12 // Rethrow for higher level to handle
13 throw $e;
14 }
15}
16
17try {
18 validateData(['name' => 'John']);
19} catch (InvalidEmailException $e) {
20 echo "Caught rethrown exception: " . $e->getMessage() . "\n";
21}
22?>Multi-Catch (PHP 7.1+):
1<?php
2class NetworkException extends Exception {}
3class TimeoutException extends Exception {}
4
5try {
6 // Some operation
7 throw new NetworkException("Connection failed");
8
9} catch (NetworkException | TimeoutException $e) {
10 // Handle both exceptions the same way
11 echo "Connection issue: " . $e->getMessage() . "\n";
12 // Retry logic
13}
14?>Creating custom exception classes allows you to categorize errors and handle them appropriately.
Basic Custom Exception:
1<?php
2declare(strict_types=1);
3
4class ValidationException extends Exception {
5 private array $errors;
6
7 public function __construct(
8 array $errors,
9 string $message = "Validation failed",
10 int $code = 0,
11 ?Throwable $previous = null
12 ) {
13 parent::__construct($message, $code, $previous);
14 $this->errors = $errors;
15 }
16
17 public function getErrors(): array {
18 return $this->errors;
19 }
20
21 public function getFirstError(): ?string {
22 return $this->errors[0] ?? null;
23 }
24}
25
26function validateUser(array $data): void {
27 $errors = [];
28
29 if (empty($data['email'])) {
30 $errors[] = 'Email is required';
31 }
32
33 if (empty($data['password'])) {
34 $errors[] = 'Password is required';
35 }
36
37 if (!empty($errors)) {
38 throw new ValidationException($errors);
39 }
40}
41
42try {
43 validateUser(['name' => 'John']);
44} catch (ValidationException $e) {
45 echo "Validation Errors:\n";
46 foreach ($e->getErrors() as $error) {
47 echo " - $error\n";
48 }
49}
50?>Exception with Context:
1<?php
2class DatabaseException extends Exception {
3 public function __construct(
4 string $message,
5 private string $query,
6 private array $params = [],
7 int $code = 0
8 ) {
9 parent::__construct($message, $code);
10 }
11
12 public function getQuery(): string {
13 return $this->query;
14 }
15
16 public function getParams(): array {
17 return $this->params;
18 }
19
20 public function getContext(): array {
21 return [
22 'query' => $this->query,
23 'params' => $this->params,
24 'message' => $this->getMessage()
25 ];
26 }
27}
28
29function executeQuery(string $sql, array $params): void {
30 try {
31 // Simulate database error
32 throw new PDOException("Syntax error in query");
33
34 } catch (PDOException $e) {
35 throw new DatabaseException(
36 "Failed to execute query: " . $e->getMessage(),
37 $sql,
38 $params
39 );
40 }
41}
42
43try {
44 executeQuery("SELECT * FROM users WHERE id = ?", [123]);
45} catch (DatabaseException $e) {
46 echo "Database Error:\n";
47 print_r($e->getContext());
48}
49?>HTTP Exception Hierarchy:
1<?php
2abstract class HttpException extends Exception {
3 public function __construct(
4 string $message = "",
5 int $code = 0,
6 ?Throwable $previous = null
7 ) {
8 parent::__construct($message, $code, $previous);
9 }
10
11 abstract public function getStatusCode(): int;
12 abstract public function getStatusMessage(): string;
13}
14
15class NotFoundException extends HttpException {
16 public function getStatusCode(): int {
17 return 404;
18 }
19
20 public function getStatusMessage(): string {
21 return 'Not Found';
22 }
23}
24
25class UnauthorizedException extends HttpException {
26 public function getStatusCode(): int {
27 return 401;
28 }
29
30 public function getStatusMessage(): string {
31 return 'Unauthorized';
32 }
33}
34
35class ForbiddenException extends HttpException {
36 public function getStatusCode(): int {
37 return 403;
38 }
39
40 public function getStatusMessage(): string {
41 return 'Forbidden';
42 }
43}
44
45function findUser(int $id): array {
46 if ($id < 1) {
47 throw new NotFoundException("User not found with ID: $id");
48 }
49 return ['id' => $id, 'name' => 'John'];
50}
51
52try {
53 $user = findUser(-1);
54} catch (HttpException $e) {
55 http_response_code($e->getStatusCode());
56 echo "HTTP {$e->getStatusCode()} {$e->getStatusMessage()}\n";
57 echo "Error: " . $e->getMessage() . "\n";
58}
59?>PHP has traditional error handling mechanisms alongside exceptions. Understanding both is important.
Error Levels:
1<?php
2declare(strict_types=1);
3
4// Common error levels:
5// E_ERROR - Fatal runtime errors
6// E_WARNING - Runtime warnings (non-fatal)
7// E_NOTICE - Runtime notices
8// E_DEPRECATED - Deprecated features
9// E_STRICT - Code suggestions for compatibility
10// E_ALL - All errors and warnings
11
12// Configure error reporting
13error_reporting(E_ALL);
14ini_set('display_errors', '1');
15
16// Examples of different error levels
17$undefined; // E_NOTICE: Undefined variable
18
19echo 10 / 0; // E_WARNING: Division by zero
20
21// Deprecated function (example)
22// split(); // E_DEPRECATED in PHP 5.3+
23?>Custom Error Handler:
1<?php
2class ErrorHandler {
3 private array $errors = [];
4
5 public function register(): void {
6 set_error_handler([$this, 'handleError']);
7 }
8
9 public function handleError(
10 int $errno,
11 string $errstr,
12 string $errfile,
13 int $errline
14 ): bool {
15 $error = [
16 'type' => $this->getErrorType($errno),
17 'message' => $errstr,
18 'file' => $errfile,
19 'line' => $errline,
20 'time' => date('Y-m-d H:i:s')
21 ];
22
23 $this->errors[] = $error;
24
25 // Log error
26 error_log("[{$error['type']}] {$errstr} in {$errfile}:{$errline}");
27
28 // Don't execute PHP internal error handler
29 return true;
30 }
31
32 private function getErrorType(int $errno): string {
33 return match($errno) {
34 E_ERROR => 'ERROR',
35 E_WARNING => 'WARNING',
36 E_NOTICE => 'NOTICE',
37 E_DEPRECATED => 'DEPRECATED',
38 E_STRICT => 'STRICT',
39 default => 'UNKNOWN'
40 };
41 }
42
43 public function getErrors(): array {
44 return $this->errors;
45 }
46}
47
48$handler = new ErrorHandler();
49$handler->register();
50
51// Trigger some errors
52$undefined_var; // Notice
53echo $non_existent; // Notice
54
55print_r($handler->getErrors());
56?>Converting Errors to Exceptions:
1<?php
2class ErrorException extends Exception {}
3
4function errorToException(
5 int $errno,
6 string $errstr,
7 string $errfile,
8 int $errline
9): bool {
10 throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
11}
12
13set_error_handler('errorToException');
14
15try {
16 // This will trigger a notice, which becomes an exception
17 echo $undefined_variable;
18
19} catch (ErrorException $e) {
20 echo "Caught error as exception:\n";
21 echo "Message: " . $e->getMessage() . "\n";
22 echo "File: " . $e->getFile() . "\n";
23 echo "Line: " . $e->getLine() . "\n";
24}
25?>Global exception handlers provide a safety net for uncaught exceptions.
Global Exception Handler:
1<?php
2declare(strict_types=1);
3
4class ExceptionHandler {
5 public function register(): void {
6 set_exception_handler([$this, 'handle']);
7 }
8
9 public function handle(Throwable $exception): void {
10 // Log the exception
11 $this->logException($exception);
12
13 // Display user-friendly message
14 $this->displayError($exception);
15
16 // Clean up if needed
17 $this->cleanup();
18 }
19
20 private function logException(Throwable $exception): void {
21 $log = sprintf(
22 "[%s] %s: %s in %s:%d\n",
23 date('Y-m-d H:i:s'),
24 get_class($exception),
25 $exception->getMessage(),
26 $exception->getFile(),
27 $exception->getLine()
28 );
29
30 error_log($log);
31 }
32
33 private function displayError(Throwable $exception): void {
34 if (php_sapi_name() === 'cli') {
35 echo "\nFatal Error:\n";
36 echo "Message: " . $exception->getMessage() . "\n";
37 echo "File: " . $exception->getFile() . "\n";
38 echo "Line: " . $exception->getLine() . "\n";
39 } else {
40 // Web environment
41 http_response_code(500);
42 echo "<h1>Application Error</h1>";
43 echo "<p>An error occurred. Please try again later.</p>";
44
45 if (getenv('APP_DEBUG') === 'true') {
46 echo "<pre>" . $exception . "</pre>";
47 }
48 }
49 }
50
51 private function cleanup(): void {
52 // Close database connections
53 // Send error notifications
54 // etc.
55 }
56}
57
58$handler = new ExceptionHandler();
59$handler->register();
60
61// This exception will be caught by the global handler
62throw new Exception("Uncaught exception example");
63?>Shutdown Function:
1<?php
2class ShutdownHandler {
3 public function register(): void {
4 register_shutdown_function([$this, 'handle']);
5 }
6
7 public function handle(): void {
8 $error = error_get_last();
9
10 if ($error !== null && $this->isFatal($error['type'])) {
11 $this->handleFatalError($error);
12 }
13 }
14
15 private function isFatal(int $type): bool {
16 return in_array($type, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR]);
17 }
18
19 private function handleFatalError(array $error): void {
20 error_log("Fatal error: {$error['message']} in {$error['file']}:{$error['line']}");
21
22 echo "\nFatal Error Detected:\n";
23 echo "Message: {$error['message']}\n";
24 echo "File: {$error['file']}\n";
25 echo "Line: {$error['line']}\n";
26 }
27}
28
29$shutdownHandler = new ShutdownHandler();
30$shutdownHandler->register();
31?>Complete Error Management System:
1<?php
2class ErrorManager {
3 private bool $debug = false;
4 private array $errorLog = [];
5
6 public function __construct(bool $debug = false) {
7 $this->debug = $debug;
8 }
9
10 public function register(): void {
11 set_error_handler([$this, 'handleError']);
12 set_exception_handler([$this, 'handleException']);
13 register_shutdown_function([$this, 'handleShutdown']);
14 }
15
16 public function handleError(int $errno, string $errstr, string $errfile, int $errline): bool {
17 if (!(error_reporting() & $errno)) {
18 return false;
19 }
20
21 throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
22 }
23
24 public function handleException(Throwable $exception): void {
25 $this->log($exception);
26
27 if ($this->debug) {
28 $this->displayDetailedError($exception);
29 } else {
30 $this->displayGenericError();
31 }
32 }
33
34 public function handleShutdown(): void {
35 $error = error_get_last();
36 if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR])) {
37 $this->handleException(
38 new ErrorException(
39 $error['message'],
40 0,
41 $error['type'],
42 $error['file'],
43 $error['line']
44 )
45 );
46 }
47 }
48
49 private function log(Throwable $exception): void {
50 $this->errorLog[] = [
51 'time' => date('Y-m-d H:i:s'),
52 'type' => get_class($exception),
53 'message' => $exception->getMessage(),
54 'file' => $exception->getFile(),
55 'line' => $exception->getLine()
56 ];
57 }
58
59 private function displayDetailedError(Throwable $exception): void {
60 echo "Error: " . $exception->getMessage() . "\n";
61 echo "File: " . $exception->getFile() . "\n";
62 echo "Line: " . $exception->getLine() . "\n";
63 }
64
65 private function displayGenericError(): void {
66 echo "An error occurred. Please contact support.\n";
67 }
68
69 public function getErrorLog(): array {
70 return $this->errorLog;
71 }
72}
73?>Apply industry best practices for robust error handling.
Fail Fast Principle:
1<?php
2declare(strict_types=1);
3
4function processOrder(array $order): void {
5 // Validate early, fail fast
6 if (empty($order['id'])) {
7 throw new InvalidArgumentException('Order ID is required');
8 }
9
10 if (empty($order['items'])) {
11 throw new InvalidArgumentException('Order must have items');
12 }
13
14 if ($order['total'] <= 0) {
15 throw new InvalidArgumentException('Order total must be positive');
16 }
17
18 // Process order only if all validations pass
19 echo "Processing order {$order['id']}...\n";
20}
21?>Domain-Specific Exceptions:
1<?php
2// Payment domain exceptions
3class PaymentException extends Exception {}
4class InsufficientFundsException extends PaymentException {}
5class PaymentDeclinedException extends PaymentException {}
6class PaymentTimeoutException extends PaymentException {}
7
8class PaymentProcessor {
9 public function processPayment(float $amount, array $card): void {
10 if ($amount > $this->getBalance()) {
11 throw new InsufficientFundsException(
12 "Insufficient funds. Required: $amount"
13 );
14 }
15
16 try {
17 $this->chargeCard($card, $amount);
18 } catch (Exception $e) {
19 if ($this->isDeclined($e)) {
20 throw new PaymentDeclinedException("Payment was declined");
21 }
22 if ($this->isTimeout($e)) {
23 throw new PaymentTimeoutException("Payment gateway timeout");
24 }
25 throw $e;
26 }
27 }
28
29 private function getBalance(): float {
30 return 1000.00;
31 }
32
33 private function chargeCard(array $card, float $amount): void {
34 // Payment processing logic
35 }
36
37 private function isDeclined(Exception $e): bool {
38 return str_contains($e->getMessage(), 'declined');
39 }
40
41 private function isTimeout(Exception $e): bool {
42 return str_contains($e->getMessage(), 'timeout');
43 }
44}
45
46try {
47 $processor = new PaymentProcessor();
48 $processor->processPayment(2000, ['number' => '1234']);
49
50} catch (InsufficientFundsException $e) {
51 echo "Please add funds to your account\n";
52
53} catch (PaymentDeclinedException $e) {
54 echo "Payment declined. Try another card\n";
55
56} catch (PaymentTimeoutException $e) {
57 echo "Payment timeout. Please try again\n";
58
59} catch (PaymentException $e) {
60 echo "Payment error: " . $e->getMessage() . "\n";
61}
62?>Retry Logic with Exceptions:
1<?php
2class RetryableException extends Exception {}
3
4function retryOperation(callable $operation, int $maxAttempts = 3): mixed {
5 $attempt = 1;
6
7 while ($attempt <= $maxAttempts) {
8 try {
9 return $operation();
10
11 } catch (RetryableException $e) {
12 if ($attempt === $maxAttempts) {
13 throw new Exception(
14 "Operation failed after $maxAttempts attempts: " .
15 $e->getMessage()
16 );
17 }
18
19 echo "Attempt $attempt failed, retrying...\n";
20 $attempt++;
21 sleep(1); // Wait before retry
22 }
23 }
24}
25
26// Usage
27try {
28 $result = retryOperation(function() {
29 static $count = 0;
30 $count++;
31
32 if ($count < 3) {
33 throw new RetryableException("Temporary failure");
34 }
35
36 return "Success on attempt $count";
37 });
38
39 echo $result . "\n";
40
41} catch (Exception $e) {
42 echo "Final error: " . $e->getMessage() . "\n";
43}
44?>Logging with Context:
1<?php
2interface LoggerInterface {
3 public function error(string $message, array $context = []): void;
4}
5
6class FileLogger implements LoggerInterface {
7 public function __construct(private string $logFile) {}
8
9 public function error(string $message, array $context = []): void {
10 $log = [
11 'timestamp' => date('Y-m-d H:i:s'),
12 'level' => 'ERROR',
13 'message' => $message,
14 'context' => $context
15 ];
16
17 file_put_contents(
18 $this->logFile,
19 json_encode($log) . "\n",
20 FILE_APPEND
21 );
22 }
23}
24
25class UserService {
26 public function __construct(private LoggerInterface $logger) {}
27
28 public function createUser(array $data): void {
29 try {
30 // Simulate error
31 throw new Exception("Database connection failed");
32
33 } catch (Exception $e) {
34 $this->logger->error('Failed to create user', [
35 'exception' => get_class($e),
36 'message' => $e->getMessage(),
37 'user_data' => $data,
38 'stack_trace' => $e->getTraceAsString()
39 ]);
40
41 throw $e;
42 }
43 }
44}
45?>Building a Robust API with Complete Error Handling
1<?php
2declare(strict_types=1);
3
4/**
5 * Complete API with Professional Error Handling
6 *
7 * Demonstrates:
8 * - Custom exception hierarchy
9 * - Global error/exception handlers
10 * - Validation with exceptions
11 * - HTTP status codes
12 * - Error logging
13 * - User-friendly error responses
14 */
15
16// ==================== CUSTOM EXCEPTIONS ====================
17
18abstract class ApiException extends Exception {
19 abstract public function getStatusCode(): int;
20
21 public function toArray(): array {
22 return [
23 'error' => true,
24 'message' => $this->getMessage(),
25 'code' => $this->getCode(),
26 'status' => $this->getStatusCode()
27 ];
28 }
29}
30
31class ValidationException extends ApiException {
32 public function __construct(
33 private array $errors,
34 string $message = "Validation failed"
35 ) {
36 parent::__construct($message);
37 }
38
39 public function getStatusCode(): int {
40 return 400;
41 }
42
43 public function toArray(): array {
44 return array_merge(parent::toArray(), [
45 'errors' => $this->errors
46 ]);
47 }
48}
49
50class NotFoundException extends ApiException {
51 public function getStatusCode(): int {
52 return 404;
53 }
54}
55
56class UnauthorizedException extends ApiException {
57 public function getStatusCode(): int {
58 return 401;
59 }
60}
61
62class ServerException extends ApiException {
63 public function getStatusCode(): int {
64 return 500;
65 }
66}
67
68// ==================== LOGGER ====================
69
70class Logger {
71 private static array $logs = [];
72
73 public static function error(string $message, array $context = []): void {
74 $log = [
75 'timestamp' => date('Y-m-d H:i:s'),
76 'level' => 'ERROR',
77 'message' => $message,
78 'context' => $context
79 ];
80
81 self::$logs[] = $log;
82 error_log(json_encode($log));
83 }
84
85 public static function info(string $message): void {
86 $log = [
87 'timestamp' => date('Y-m-d H:i:s'),
88 'level' => 'INFO',
89 'message' => $message
90 ];
91
92 self::$logs[] = $log;
93 }
94
95 public static function getLogs(): array {
96 return self::$logs;
97 }
98}
99
100// ==================== VALIDATOR ====================
101
102class Validator {
103 private array $errors = [];
104
105 public function validateRequired(array $data, array $required): void {
106 foreach ($required as $field) {
107 if (!isset($data[$field]) || empty($data[$field])) {
108 $this->errors[$field][] = "$field is required";
109 }
110 }
111 }
112
113 public function validateEmail(array $data, string $field): void {
114 if (isset($data[$field]) && !filter_var($data[$field], FILTER_VALIDATE_EMAIL)) {
115 $this->errors[$field][] = "Invalid email format";
116 }
117 }
118
119 public function validateMin(array $data, string $field, int $min): void {
120 if (isset($data[$field]) && strlen($data[$field]) < $min) {
121 $this->errors[$field][] = "$field must be at least $min characters";
122 }
123 }
124
125 public function validate(): void {
126 if (!empty($this->errors)) {
127 throw new ValidationException($this->errors);
128 }
129 }
130
131 public function getErrors(): array {
132 return $this->errors;
133 }
134}
135
136// ==================== USER REPOSITORY ====================
137
138class UserRepository {
139 private array $users = [];
140 private int $nextId = 1;
141
142 public function __construct() {
143 // Pre-populate with test users
144 $this->users[1] = [
145 'id' => 1,
146 'name' => 'John Doe',
147 'email' => '[email protected]',
148 'created_at' => date('Y-m-d H:i:s')
149 ];
150 $this->nextId = 2;
151 }
152
153 public function create(array $data): array {
154 $user = [
155 'id' => $this->nextId++,
156 'name' => $data['name'],
157 'email' => $data['email'],
158 'created_at' => date('Y-m-d H:i:s')
159 ];
160
161 $this->users[$user['id']] = $user;
162
163 Logger::info("User created: {$user['id']}");
164
165 return $user;
166 }
167
168 public function find(int $id): array {
169 if (!isset($this->users[$id])) {
170 throw new NotFoundException("User not found with ID: $id");
171 }
172
173 return $this->users[$id];
174 }
175
176 public function findAll(): array {
177 return array_values($this->users);
178 }
179
180 public function update(int $id, array $data): array {
181 $user = $this->find($id);
182
183 if (isset($data['name'])) {
184 $user['name'] = $data['name'];
185 }
186
187 if (isset($data['email'])) {
188 $user['email'] = $data['email'];
189 }
190
191 $user['updated_at'] = date('Y-m-d H:i:s');
192 $this->users[$id] = $user;
193
194 Logger::info("User updated: $id");
195
196 return $user;
197 }
198
199 public function delete(int $id): void {
200 $this->find($id); // Throws if not found
201 unset($this->users[$id]);
202
203 Logger::info("User deleted: $id");
204 }
205}
206
207// ==================== USER SERVICE ====================
208
209class UserService {
210 public function __construct(
211 private UserRepository $repository,
212 private Validator $validator
213 ) {}
214
215 public function createUser(array $data): array {
216 try {
217 // Validate input
218 $this->validator->validateRequired($data, ['name', 'email']);
219 $this->validator->validateEmail($data, 'email');
220 $this->validator->validateMin($data, 'name', 3);
221 $this->validator->validate();
222
223 // Create user
224 return $this->repository->create($data);
225
226 } catch (ValidationException $e) {
227 Logger::error('Validation failed', ['errors' => $e->toArray()]);
228 throw $e;
229
230 } catch (Exception $e) {
231 Logger::error('Failed to create user', [
232 'error' => $e->getMessage(),
233 'data' => $data
234 ]);
235 throw new ServerException('Failed to create user');
236 }
237 }
238
239 public function getUser(int $id): array {
240 try {
241 return $this->repository->find($id);
242
243 } catch (NotFoundException $e) {
244 Logger::error("User not found: $id");
245 throw $e;
246
247 } catch (Exception $e) {
248 Logger::error('Failed to get user', ['id' => $id, 'error' => $e->getMessage()]);
249 throw new ServerException('Failed to retrieve user');
250 }
251 }
252
253 public function getAllUsers(): array {
254 try {
255 return $this->repository->findAll();
256
257 } catch (Exception $e) {
258 Logger::error('Failed to get users', ['error' => $e->getMessage()]);
259 throw new ServerException('Failed to retrieve users');
260 }
261 }
262
263 public function updateUser(int $id, array $data): array {
264 try {
265 if (isset($data['email'])) {
266 $this->validator->validateEmail($data, 'email');
267 }
268 if (isset($data['name'])) {
269 $this->validator->validateMin($data, 'name', 3);
270 }
271 $this->validator->validate();
272
273 return $this->repository->update($id, $data);
274
275 } catch (ValidationException | NotFoundException $e) {
276 throw $e;
277
278 } catch (Exception $e) {
279 Logger::error('Failed to update user', ['id' => $id, 'error' => $e->getMessage()]);
280 throw new ServerException('Failed to update user');
281 }
282 }
283
284 public function deleteUser(int $id): void {
285 try {
286 $this->repository->delete($id);
287
288 } catch (NotFoundException $e) {
289 throw $e;
290
291 } catch (Exception $e) {
292 Logger::error('Failed to delete user', ['id' => $id, 'error' => $e->getMessage()]);
293 throw new ServerException('Failed to delete user');
294 }
295 }
296}
297
298// ==================== API RESPONSE ====================
299
300class ApiResponse {
301 public static function success(mixed $data, int $statusCode = 200): void {
302 http_response_code($statusCode);
303 echo json_encode([
304 'success' => true,
305 'data' => $data
306 ], JSON_PRETTY_PRINT) . "\n";
307 }
308
309 public static function error(ApiException $exception): void {
310 http_response_code($exception->getStatusCode());
311 echo json_encode($exception->toArray(), JSON_PRETTY_PRINT) . "\n";
312 }
313}
314
315// ==================== DEMO ====================
316
317echo str_repeat("=", 60) . "\n";
318echo " š API ERROR HANDLING DEMO\n";
319echo str_repeat("=", 60) . "\n\n";
320
321$repository = new UserRepository();
322$validator = new Validator();
323$service = new UserService($repository, $validator);
324
325// Test 1: Create valid user
326echo "š Test 1: Create valid user\n";
327echo str_repeat("-", 60) . "\n";
328try {
329 $user = $service->createUser([
330 'name' => 'Alice Smith',
331 'email' => '[email protected]'
332 ]);
333 ApiResponse::success($user, 201);
334} catch (ApiException $e) {
335 ApiResponse::error($e);
336}
337
338// Test 2: Create user with validation errors
339echo "\nš Test 2: Validation errors\n";
340echo str_repeat("-", 60) . "\n";
341try {
342 $user = $service->createUser([
343 'name' => 'AB', // Too short
344 'email' => 'invalid-email' // Invalid format
345 ]);
346 ApiResponse::success($user, 201);
347} catch (ApiException $e) {
348 ApiResponse::error($e);
349}
350
351// Test 3: Get existing user
352echo "\nš Test 3: Get existing user\n";
353echo str_repeat("-", 60) . "\n";
354try {
355 $user = $service->getUser(1);
356 ApiResponse::success($user);
357} catch (ApiException $e) {
358 ApiResponse::error($e);
359}
360
361// Test 4: Get non-existent user
362echo "\nš Test 4: User not found\n";
363echo str_repeat("-", 60) . "\n";
364try {
365 $user = $service->getUser(999);
366 ApiResponse::success($user);
367} catch (ApiException $e) {
368 ApiResponse::error($e);
369}
370
371// Test 5: Get all users
372echo "\nš Test 5: Get all users\n";
373echo str_repeat("-", 60) . "\n";
374try {
375 $users = $service->getAllUsers();
376 ApiResponse::success($users);
377} catch (ApiException $e) {
378 ApiResponse::error($e);
379}
380
381// Test 6: Update user
382echo "\nš Test 6: Update user\n";
383echo str_repeat("-", 60) . "\n";
384try {
385 $user = $service->updateUser(1, ['name' => 'John Updated']);
386 ApiResponse::success($user);
387} catch (ApiException $e) {
388 ApiResponse::error($e);
389}
390
391// Display logs
392echo "\nš Error Logs:\n";
393echo str_repeat("=", 60) . "\n";
394foreach (Logger::getLogs() as $log) {
395 echo "[{$log['timestamp']}] {$log['level']}: {$log['message']}\n";
396}
397
398echo "\n" . str_repeat("=", 60) . "\n";
399echo "ā
API DEMO COMPLETE!\n";
400echo str_repeat("=", 60) . "\n";
401echo "\nšÆ Key Features:\n";
402echo " ⢠Custom exception hierarchy\n";
403echo " ⢠Validation with detailed error messages\n";
404echo " ⢠HTTP status codes\n";
405echo " ⢠Comprehensive error logging\n";
406echo " ⢠User-friendly API responses\n";
407echo " ⢠Try-catch at service layer\n";
408?>This API demonstrates professional error handling:
1. Exception Hierarchy:
ApiException base class with status codestoArray() method for consistent API responses2. Validation Layer:
Validator class for reusable validationValidationException with all errors3. Service Layer:
4. Logging:
5. API Responses:
This pattern scales well for production APIs!
Your progress is saved in this browser, on this device only.