friendship ended with social-app. php is my new best friend
1--- 2title: Error Handling 3description: Learn how to handle errors and exceptions with the Fetch HTTP package 4--- 5 6# Error Handling 7 8This guide explains how to handle errors when making HTTP requests with the Fetch HTTP package. 9 10## Response Status Checking 11 12The most common way to handle HTTP errors is by checking the response status: 13 14```php 15// Make a request 16$response = get('https://api.example.com/users/123'); 17 18if ($response->successful()) { 19 // Status code is 2xx - process the response 20 $user = $response->json(); 21 echo "Found user: {$user['name']}"; 22} else { 23 // Error occurred - handle based on status code 24 echo "Error: " . $response->status() . " " . $response->statusText(); 25} 26``` 27 28## Status Category Methods 29 30The `Response` class provides methods to check different status code categories: 31 32```php 33$response = get('https://api.example.com/users/123'); 34 35if ($response->successful()) { 36 // Status code is 2xx 37 $user = $response->json(); 38 echo "Found user: {$user['name']}"; 39} elseif ($response->isClientError()) { 40 // Status code is 4xx 41 echo "Client error: " . $response->status(); 42} elseif ($response->isServerError()) { 43 // Status code is 5xx 44 echo "Server error: " . $response->status(); 45} elseif ($response->isRedirection()) { 46 // Status code is 3xx 47 echo "Redirect to: " . $response->header('Location'); 48} 49``` 50 51## Specific Status Code Methods 52 53For handling specific status codes, the `Response` class provides dedicated methods: 54 55```php 56if ($response->isOk()) { 57 // 200 OK 58 $data = $response->json(); 59} elseif ($response->isNotFound()) { 60 // 404 Not Found 61 echo "Resource not found"; 62} elseif ($response->isUnauthorized()) { 63 // 401 Unauthorized 64 echo "Authentication required"; 65} elseif ($response->isForbidden()) { 66 // 403 Forbidden 67 echo "Access denied"; 68} elseif ($response->isUnprocessableEntity()) { 69 // 422 Unprocessable Entity 70 $errors = $response->json()['errors'] ?? []; 71 foreach ($errors as $field => $messages) { 72 echo "{$field}: " . implode(', ', $messages) . "\n"; 73 } 74} 75``` 76 77## Using Status Enums 78 79Fetch PHP provides type-safe enums for status codes, which you can use for more explicit comparisons: 80 81```php 82use Fetch\Enum\Status; 83 84// Get the status as an enum 85$statusEnum = $response->statusEnum(); 86 87// Compare with enum values 88if ($statusEnum === Status::OK) { 89 // Status is exactly 200 OK 90} elseif ($statusEnum === Status::NOT_FOUND) { 91 // Status is exactly 404 Not Found 92} elseif ($statusEnum === Status::TOO_MANY_REQUESTS) { 93 // Status is exactly 429 Too Many Requests 94} 95 96// Check using isStatus() with enum 97if ($response->isStatus(Status::CREATED)) { 98 // Status is 201 Created 99} 100``` 101 102## Exception Handling 103 104When network errors or other exceptions occur, they are thrown as PHP exceptions: 105 106```php 107use Fetch\Exceptions\NetworkException; 108use Fetch\Exceptions\RequestException; 109use Fetch\Exceptions\ClientException; 110use Fetch\Exceptions\TimeoutException; 111 112try { 113 $response = get('https://api.example.com/users'); 114 115 if ($response->failed()) { 116 throw new \Exception("Request failed with status: " . $response->status()); 117 } 118 119 $users = $response->json(); 120} catch (NetworkException $e) { 121 // Network-related issues (DNS failure, connection refused, etc.) 122 echo "Network error: " . $e->getMessage(); 123} catch (TimeoutException $e) { 124 // Request timed out 125 echo "Request timed out: " . $e->getMessage(); 126} catch (RequestException $e) { 127 // HTTP request errors 128 echo "Request error: " . $e->getMessage(); 129 130 // If the exception has a response, you can still access it 131 if ($e->hasResponse()) { 132 $errorResponse = $e->getResponse(); 133 $statusCode = $errorResponse->status(); 134 $errorDetails = $errorResponse->json()['error'] ?? 'Unknown error'; 135 echo "Status: {$statusCode}, Error: {$errorDetails}"; 136 } 137} catch (ClientException $e) { 138 // General client errors 139 echo "Client error: " . $e->getMessage(); 140} catch (\Exception $e) { 141 // Catch any other exceptions 142 echo "Error: " . $e->getMessage(); 143} 144``` 145 146## Handling JSON Decoding Errors 147 148When decoding JSON responses, you may encounter parsing errors: 149 150```php 151try { 152 $data = $response->json(); 153} catch (\RuntimeException $e) { 154 // JSON parsing failed 155 echo "Failed to decode JSON: " . $e->getMessage(); 156 157 // You can access the raw response body 158 $rawBody = $response->body(); 159 echo "Raw response: " . $rawBody; 160} 161``` 162 163To suppress JSON decoding errors: 164 165```php 166// Pass false to disable throwing exceptions 167$data = $response->json(true, false); 168 169// Or use the array method with error suppression 170$data = $response->array(false); 171 172// Or use the get method with a default 173$value = $response->get('key', 'default value'); 174``` 175 176## Handling Validation Errors 177 178Many APIs return validation errors with status 422 (Unprocessable Entity): 179 180```php 181$response = post('https://api.example.com/users', [ 182 'email' => 'invalid-email', 183 'password' => '123' // Too short 184]); 185 186if ($response->isUnprocessableEntity()) { 187 $errors = $response->json()['errors'] ?? []; 188 189 foreach ($errors as $field => $messages) { 190 echo "- {$field}: " . implode(', ', $messages) . "\n"; 191 } 192} 193``` 194 195## Common API Error Formats 196 197Different APIs structure their error responses differently. Here's how to handle some common formats: 198 199### Standard JSON API Errors 200 201```php 202if ($response->failed()) { 203 $errorData = $response->json(true, false); // Don't throw on parse errors 204 205 // Format: { "error": { "code": "invalid_token", "message": "The token is invalid" } } 206 if (isset($errorData['error']['message'])) { 207 echo "Error: " . $errorData['error']['message']; 208 echo "Code: " . $errorData['error']['code'] ?? 'unknown'; 209 } 210 // Format: { "errors": [{ "title": "Invalid token", "detail": "The token is expired" }] } 211 elseif (isset($errorData['errors']) && is_array($errorData['errors'])) { 212 foreach ($errorData['errors'] as $error) { 213 echo $error['title'] . ": " . ($error['detail'] ?? '') . "\n"; 214 } 215 } 216 // Format: { "message": "Validation failed", "errors": { "email": ["Invalid email"] } } 217 elseif (isset($errorData['message']) && isset($errorData['errors'])) { 218 echo $errorData['message'] . "\n"; 219 foreach ($errorData['errors'] as $field => $messages) { 220 echo "- {$field}: " . implode(', ', $messages) . "\n"; 221 } 222 } 223 // Simple format: { "message": "An error occurred" } 224 elseif (isset($errorData['message'])) { 225 echo "Error: " . $errorData['message']; 226 } 227 // Fallback 228 else { 229 echo "Unknown error occurred. Status code: " . $response->status(); 230 } 231} 232``` 233 234## Retry on Error 235 236You can automatically retry requests that fail due to transient errors: 237 238```php 239use Fetch\Http\ClientHandler; 240 241$response = ClientHandler::create() 242 // Retry up to 3 times with exponential backoff 243 ->retry(3, 100) 244 // Customize which status codes to retry 245 ->retryStatusCodes([429, 503, 504]) 246 // Customize which exceptions to retry 247 ->retryExceptions([\GuzzleHttp\Exception\ConnectException::class]) 248 ->get('https://api.example.com/unstable-endpoint'); 249``` 250 251## Handling Rate Limits 252 253Many APIs implement rate limiting. Here's how to handle 429 Too Many Requests responses: 254 255```php 256$response = get('https://api.example.com/users'); 257 258if ($response->isTooManyRequests()) { 259 // Check for Retry-After header (might be in seconds or a timestamp) 260 $retryAfter = $response->header('Retry-After'); 261 262 if ($retryAfter !== null) { 263 if (is_numeric($retryAfter)) { 264 $waitSeconds = (int) $retryAfter; 265 } else { 266 // Parse HTTP date 267 $waitSeconds = strtotime($retryAfter) - time(); 268 } 269 270 echo "Rate limited. Please try again after {$waitSeconds} seconds."; 271 272 // You could wait and retry automatically 273 if ($waitSeconds > 0 && $waitSeconds < 60) { // Only wait if reasonable 274 sleep($waitSeconds); 275 return get('https://api.example.com/users'); 276 } 277 } else { 278 echo "Rate limited. Please try again later."; 279 } 280} 281``` 282 283## Asynchronous Error Handling 284 285When working with asynchronous requests, you can use try/catch blocks with await or the catch method with promises: 286 287```php 288use function async; 289use function await; 290 291// Using try/catch with await 292await(async(function() { 293 try { 294 $response = await(async(function() { 295 return fetch('https://api.example.com/users/999'); 296 })); 297 298 if ($response->failed()) { 299 throw new \Exception("Request failed with status: " . $response->status()); 300 } 301 302 return $response->json(); 303 } catch (\Exception $e) { 304 echo "Error: " . $e->getMessage(); 305 return []; 306 } 307})); 308 309// Using catch() with promises 310$handler = fetch_client()->getHandler(); 311$handler->async() 312 ->get('https://api.example.com/users/999') 313 ->then(function($response) { 314 if ($response->failed()) { 315 throw new \Exception("API returned error: " . $response->status()); 316 } 317 return $response->json(); 318 }) 319 ->catch(function($error) { 320 echo "Error: " . $error->getMessage(); 321 return []; 322 }); 323``` 324 325## Custom Error Handling Class 326 327For more advanced applications, you might want to create a dedicated error handler: 328 329```php 330class ApiErrorHandler 331{ 332 /** 333 * Handle a response that might contain errors. 334 */ 335 public function handleResponse($response) 336 { 337 if ($response->successful()) { 338 return $response; 339 } 340 341 switch ($response->status()) { 342 case 401: 343 throw new AuthenticationException("Authentication required"); 344 345 case 403: 346 throw new AuthorizationException("You don't have permission to access this resource"); 347 348 case 404: 349 throw new ResourceNotFoundException("The requested resource was not found"); 350 351 case 422: 352 $errors = $response->json()['errors'] ?? []; 353 throw new ValidationException("Validation failed", $errors); 354 355 case 429: 356 $retryAfter = $response->header('Retry-After'); 357 throw new RateLimitException("Too many requests", $retryAfter); 358 359 case 500: 360 case 502: 361 case 503: 362 case 504: 363 throw new ServerException("Server error: " . $response->status()); 364 365 default: 366 throw new ApiException("API error: " . $response->status()); 367 } 368 } 369} 370 371// Usage 372$errorHandler = new ApiErrorHandler(); 373 374try { 375 $response = get('https://api.example.com/users'); 376 $errorHandler->handleResponse($response); 377 378 // Process successful response 379 $users = $response->json(); 380} catch (AuthenticationException $e) { 381 // Handle authentication error 382} catch (ValidationException $e) { 383 // Handle validation errors 384 $errors = $e->getErrors(); 385} catch (ApiException $e) { 386 // Handle other API errors 387} 388``` 389 390## Debugging Errors 391 392For debugging, you can get detailed information about a request: 393 394```php 395$handler = fetch_client()->getHandler(); 396$debugInfo = $handler->debug(); 397 398try { 399 // Attempt the request 400 $response = $handler->get('https://api.example.com/users'); 401 402 if ($response->failed()) { 403 echo "Request failed with status: " . $response->status() . "\n"; 404 echo "Debug information:\n"; 405 print_r($debugInfo); 406 } 407} catch (\Exception $e) { 408 echo "Exception: " . $e->getMessage() . "\n"; 409 echo "Debug information:\n"; 410 print_r($debugInfo); 411} 412``` 413 414## Error Logging 415 416You can use a PSR-3 compatible logger to log errors: 417 418```php 419use Monolog\Logger; 420use Monolog\Handler\StreamHandler; 421 422// Create a logger 423$logger = new Logger('api'); 424$logger->pushHandler(new StreamHandler('logs/api.log', Logger::ERROR)); 425 426// Set the logger on the client 427$client = fetch_client(); 428$client->setLogger($logger); 429 430// Now errors will be logged 431try { 432 $response = $client->get('https://api.example.com/users'); 433 434 if ($response->failed()) { 435 // This will be logged by the client 436 throw new \Exception("API request failed: " . $response->status()); 437 } 438} catch (\Exception $e) { 439 // Additional custom logging if needed 440 $logger->error("Custom error handler: " . $e->getMessage()); 441} 442``` 443 444## Error Handling with Retries and Logging 445 446Combining retries, logging, and error handling for robust API interactions: 447 448```php 449use Monolog\Logger; 450use Monolog\Handler\StreamHandler; 451 452// Create a logger 453$logger = new Logger('api'); 454$logger->pushHandler(new StreamHandler('logs/api.log', Logger::INFO)); 455 456// Configure client with retry logic and logging 457$client = fetch_client() 458 ->getHandler() 459 ->setLogger($logger) 460 ->retry(3, 500) // 3 retries with 500ms initial delay 461 ->retryStatusCodes([429, 500, 502, 503, 504]); 462 463try { 464 $response = $client->get('https://api.example.com/flaky-endpoint'); 465 466 if ($response->failed()) { 467 if ($response->isUnauthorized()) { 468 // Handle authentication issues 469 throw new \Exception("Authentication required"); 470 } elseif ($response->isForbidden()) { 471 // Handle permission issues 472 throw new \Exception("Permission denied"); 473 } else { 474 // Handle other errors 475 throw new \Exception("API error: " . $response->status()); 476 } 477 } 478 479 // Process successful response 480 $data = $response->json(); 481 482} catch (\Exception $e) { 483 // Handle the exception after retries are exhausted 484 $logger->error("Failed after retries: " . $e->getMessage()); 485 486 // Provide user-friendly message 487 echo "We're having trouble connecting to the service. Please try again later."; 488} 489``` 490 491## Next Steps 492 493- Learn about [Retry Handling](/guide/retry-handling) for automatic recovery from errors 494- Explore [Logging](/guide/logging) for more advanced error logging 495- See [Authentication](/guide/authentication) for handling authentication errors 496- Check out [Asynchronous Requests](/guide/async-requests) for handling errors in async operations