title: Error Handling description: Learn how to handle errors and exceptions with the Fetch HTTP package#
Error Handling#
This guide explains how to handle errors when making HTTP requests with the Fetch HTTP package.
Response Status Checking#
The most common way to handle HTTP errors is by checking the response status:
// Make a request
$response = get('https://api.example.com/users/123');
if ($response->successful()) {
// Status code is 2xx - process the response
$user = $response->json();
echo "Found user: {$user['name']}";
} else {
// Error occurred - handle based on status code
echo "Error: " . $response->status() . " " . $response->statusText();
}
Status Category Methods#
The Response class provides methods to check different status code categories:
$response = get('https://api.example.com/users/123');
if ($response->successful()) {
// Status code is 2xx
$user = $response->json();
echo "Found user: {$user['name']}";
} elseif ($response->isClientError()) {
// Status code is 4xx
echo "Client error: " . $response->status();
} elseif ($response->isServerError()) {
// Status code is 5xx
echo "Server error: " . $response->status();
} elseif ($response->isRedirection()) {
// Status code is 3xx
echo "Redirect to: " . $response->header('Location');
}
Specific Status Code Methods#
For handling specific status codes, the Response class provides dedicated methods:
if ($response->isOk()) {
// 200 OK
$data = $response->json();
} elseif ($response->isNotFound()) {
// 404 Not Found
echo "Resource not found";
} elseif ($response->isUnauthorized()) {
// 401 Unauthorized
echo "Authentication required";
} elseif ($response->isForbidden()) {
// 403 Forbidden
echo "Access denied";
} elseif ($response->isUnprocessableEntity()) {
// 422 Unprocessable Entity
$errors = $response->json()['errors'] ?? [];
foreach ($errors as $field => $messages) {
echo "{$field}: " . implode(', ', $messages) . "\n";
}
}
Using Status Enums#
Fetch PHP provides type-safe enums for status codes, which you can use for more explicit comparisons:
use Fetch\Enum\Status;
// Get the status as an enum
$statusEnum = $response->statusEnum();
// Compare with enum values
if ($statusEnum === Status::OK) {
// Status is exactly 200 OK
} elseif ($statusEnum === Status::NOT_FOUND) {
// Status is exactly 404 Not Found
} elseif ($statusEnum === Status::TOO_MANY_REQUESTS) {
// Status is exactly 429 Too Many Requests
}
// Check using isStatus() with enum
if ($response->isStatus(Status::CREATED)) {
// Status is 201 Created
}
Exception Handling#
When network errors or other exceptions occur, they are thrown as PHP exceptions:
use Fetch\Exceptions\NetworkException;
use Fetch\Exceptions\RequestException;
use Fetch\Exceptions\ClientException;
use Fetch\Exceptions\TimeoutException;
try {
$response = get('https://api.example.com/users');
if ($response->failed()) {
throw new \Exception("Request failed with status: " . $response->status());
}
$users = $response->json();
} catch (NetworkException $e) {
// Network-related issues (DNS failure, connection refused, etc.)
echo "Network error: " . $e->getMessage();
} catch (TimeoutException $e) {
// Request timed out
echo "Request timed out: " . $e->getMessage();
} catch (RequestException $e) {
// HTTP request errors
echo "Request error: " . $e->getMessage();
// If the exception has a response, you can still access it
if ($e->hasResponse()) {
$errorResponse = $e->getResponse();
$statusCode = $errorResponse->status();
$errorDetails = $errorResponse->json()['error'] ?? 'Unknown error';
echo "Status: {$statusCode}, Error: {$errorDetails}";
}
} catch (ClientException $e) {
// General client errors
echo "Client error: " . $e->getMessage();
} catch (\Exception $e) {
// Catch any other exceptions
echo "Error: " . $e->getMessage();
}
Handling JSON Decoding Errors#
When decoding JSON responses, you may encounter parsing errors:
try {
$data = $response->json();
} catch (\RuntimeException $e) {
// JSON parsing failed
echo "Failed to decode JSON: " . $e->getMessage();
// You can access the raw response body
$rawBody = $response->body();
echo "Raw response: " . $rawBody;
}
To suppress JSON decoding errors:
// Pass false to disable throwing exceptions
$data = $response->json(true, false);
// Or use the array method with error suppression
$data = $response->array(false);
// Or use the get method with a default
$value = $response->get('key', 'default value');
Handling Validation Errors#
Many APIs return validation errors with status 422 (Unprocessable Entity):
$response = post('https://api.example.com/users', [
'email' => 'invalid-email',
'password' => '123' // Too short
]);
if ($response->isUnprocessableEntity()) {
$errors = $response->json()['errors'] ?? [];
foreach ($errors as $field => $messages) {
echo "- {$field}: " . implode(', ', $messages) . "\n";
}
}
Common API Error Formats#
Different APIs structure their error responses differently. Here's how to handle some common formats:
Standard JSON API Errors#
if ($response->failed()) {
$errorData = $response->json(true, false); // Don't throw on parse errors
// Format: { "error": { "code": "invalid_token", "message": "The token is invalid" } }
if (isset($errorData['error']['message'])) {
echo "Error: " . $errorData['error']['message'];
echo "Code: " . $errorData['error']['code'] ?? 'unknown';
}
// Format: { "errors": [{ "title": "Invalid token", "detail": "The token is expired" }] }
elseif (isset($errorData['errors']) && is_array($errorData['errors'])) {
foreach ($errorData['errors'] as $error) {
echo $error['title'] . ": " . ($error['detail'] ?? '') . "\n";
}
}
// Format: { "message": "Validation failed", "errors": { "email": ["Invalid email"] } }
elseif (isset($errorData['message']) && isset($errorData['errors'])) {
echo $errorData['message'] . "\n";
foreach ($errorData['errors'] as $field => $messages) {
echo "- {$field}: " . implode(', ', $messages) . "\n";
}
}
// Simple format: { "message": "An error occurred" }
elseif (isset($errorData['message'])) {
echo "Error: " . $errorData['message'];
}
// Fallback
else {
echo "Unknown error occurred. Status code: " . $response->status();
}
}
Retry on Error#
You can automatically retry requests that fail due to transient errors:
use Fetch\Http\ClientHandler;
$response = ClientHandler::create()
// Retry up to 3 times with exponential backoff
->retry(3, 100)
// Customize which status codes to retry
->retryStatusCodes([429, 503, 504])
// Customize which exceptions to retry
->retryExceptions([\GuzzleHttp\Exception\ConnectException::class])
->get('https://api.example.com/unstable-endpoint');
Handling Rate Limits#
Many APIs implement rate limiting. Here's how to handle 429 Too Many Requests responses:
$response = get('https://api.example.com/users');
if ($response->isTooManyRequests()) {
// Check for Retry-After header (might be in seconds or a timestamp)
$retryAfter = $response->header('Retry-After');
if ($retryAfter !== null) {
if (is_numeric($retryAfter)) {
$waitSeconds = (int) $retryAfter;
} else {
// Parse HTTP date
$waitSeconds = strtotime($retryAfter) - time();
}
echo "Rate limited. Please try again after {$waitSeconds} seconds.";
// You could wait and retry automatically
if ($waitSeconds > 0 && $waitSeconds < 60) { // Only wait if reasonable
sleep($waitSeconds);
return get('https://api.example.com/users');
}
} else {
echo "Rate limited. Please try again later.";
}
}
Asynchronous Error Handling#
When working with asynchronous requests, you can use try/catch blocks with await or the catch method with promises:
use function async;
use function await;
// Using try/catch with await
await(async(function() {
try {
$response = await(async(function() {
return fetch('https://api.example.com/users/999');
}));
if ($response->failed()) {
throw new \Exception("Request failed with status: " . $response->status());
}
return $response->json();
} catch (\Exception $e) {
echo "Error: " . $e->getMessage();
return [];
}
}));
// Using catch() with promises
$handler = fetch_client()->getHandler();
$handler->async()
->get('https://api.example.com/users/999')
->then(function($response) {
if ($response->failed()) {
throw new \Exception("API returned error: " . $response->status());
}
return $response->json();
})
->catch(function($error) {
echo "Error: " . $error->getMessage();
return [];
});
Custom Error Handling Class#
For more advanced applications, you might want to create a dedicated error handler:
class ApiErrorHandler
{
/**
* Handle a response that might contain errors.
*/
public function handleResponse($response)
{
if ($response->successful()) {
return $response;
}
switch ($response->status()) {
case 401:
throw new AuthenticationException("Authentication required");
case 403:
throw new AuthorizationException("You don't have permission to access this resource");
case 404:
throw new ResourceNotFoundException("The requested resource was not found");
case 422:
$errors = $response->json()['errors'] ?? [];
throw new ValidationException("Validation failed", $errors);
case 429:
$retryAfter = $response->header('Retry-After');
throw new RateLimitException("Too many requests", $retryAfter);
case 500:
case 502:
case 503:
case 504:
throw new ServerException("Server error: " . $response->status());
default:
throw new ApiException("API error: " . $response->status());
}
}
}
// Usage
$errorHandler = new ApiErrorHandler();
try {
$response = get('https://api.example.com/users');
$errorHandler->handleResponse($response);
// Process successful response
$users = $response->json();
} catch (AuthenticationException $e) {
// Handle authentication error
} catch (ValidationException $e) {
// Handle validation errors
$errors = $e->getErrors();
} catch (ApiException $e) {
// Handle other API errors
}
Debugging Errors#
For debugging, you can get detailed information about a request:
$handler = fetch_client()->getHandler();
$debugInfo = $handler->debug();
try {
// Attempt the request
$response = $handler->get('https://api.example.com/users');
if ($response->failed()) {
echo "Request failed with status: " . $response->status() . "\n";
echo "Debug information:\n";
print_r($debugInfo);
}
} catch (\Exception $e) {
echo "Exception: " . $e->getMessage() . "\n";
echo "Debug information:\n";
print_r($debugInfo);
}
Error Logging#
You can use a PSR-3 compatible logger to log errors:
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Create a logger
$logger = new Logger('api');
$logger->pushHandler(new StreamHandler('logs/api.log', Logger::ERROR));
// Set the logger on the client
$client = fetch_client();
$client->setLogger($logger);
// Now errors will be logged
try {
$response = $client->get('https://api.example.com/users');
if ($response->failed()) {
// This will be logged by the client
throw new \Exception("API request failed: " . $response->status());
}
} catch (\Exception $e) {
// Additional custom logging if needed
$logger->error("Custom error handler: " . $e->getMessage());
}
Error Handling with Retries and Logging#
Combining retries, logging, and error handling for robust API interactions:
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Create a logger
$logger = new Logger('api');
$logger->pushHandler(new StreamHandler('logs/api.log', Logger::INFO));
// Configure client with retry logic and logging
$client = fetch_client()
->getHandler()
->setLogger($logger)
->retry(3, 500) // 3 retries with 500ms initial delay
->retryStatusCodes([429, 500, 502, 503, 504]);
try {
$response = $client->get('https://api.example.com/flaky-endpoint');
if ($response->failed()) {
if ($response->isUnauthorized()) {
// Handle authentication issues
throw new \Exception("Authentication required");
} elseif ($response->isForbidden()) {
// Handle permission issues
throw new \Exception("Permission denied");
} else {
// Handle other errors
throw new \Exception("API error: " . $response->status());
}
}
// Process successful response
$data = $response->json();
} catch (\Exception $e) {
// Handle the exception after retries are exhausted
$logger->error("Failed after retries: " . $e->getMessage());
// Provide user-friendly message
echo "We're having trouble connecting to the service. Please try again later.";
}
Next Steps#
- Learn about Retry Handling for automatic recovery from errors
- Explore Logging for more advanced error logging
- See Authentication for handling authentication errors
- Check out Asynchronous Requests for handling errors in async operations