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