Error handling and retries
Overview
This guide shows how to:
- Catch and handle SDK exceptions (
ApiException,HttpException). - Map errors to your own API responses.
- Use
ProblemDetailsfor logging and diagnostics. - Think about retries and timeouts in production systems.
It complements the reference information in the Error handling section.
Catching ApiException and HttpException
All SDK-specific errors extend EflLeasingException and the two most important subclasses are:
ApiException– API-level errors (HTTP 4xx/5xx) returned by the EFL Leasing Online API.HttpException– transport-level errors (network failures, timeouts, malformed responses).
use Imoli\EflLeasingSdk\EflClient;
use Imoli\EflLeasingSdk\Exception\ApiException;
use Imoli\EflLeasingSdk\Exception\HttpException;
try {
$offer = $client->calculateBasicOffer($basket, $token);
} catch (ApiException $e) {
// The EFL API returned a structured error response
$statusCode = $e->getStatusCode();
$problemDetails = $e->getProblemDetails(); // may be null if not provided
// Log details and translate to your domain-specific error
} catch (HttpException $e) {
// Transport-level problem (timeout, connection issue, misconfigured HTTP client)
// Decide whether to retry or fail fast
}
Avoid catching \Exception in generic handlers.
Treat ApiException and HttpException as the main boundary for SDK-related failures.
Mapping errors to your API responses
When building your own HTTP API, map SDK exceptions to appropriate responses:
ApiException:- Use the HTTP status code from the exception as a starting point.
- Map validation problems or business rule violations to 4xx responses in your API.
HttpException:- Typically map to
503 Service Unavailableor502 Bad Gateway. - Indicate that the problem is temporary or infrastructure-related.
- Typically map to
Example controller-level handler:
try {
$offer = $client->calculateBasicOffer($basket, $token);
return new JsonResponse($offer, 200);
} catch (ApiException $e) {
$problem = $e->getProblemDetails();
// Do not expose full problem details to end users
$responseBody = [
'message' => 'Leasing service rejected the request.',
'code' => $problem?->getType(),
];
return new JsonResponse($responseBody, $e->getStatusCode());
} catch (HttpException $e) {
// Generic infrastructure error
return new JsonResponse(
['message' => 'Leasing service is temporarily unavailable.'],
503
);
}
Logging and monitoring with ProblemDetails
ApiException exposes an optional ProblemDetails instance that follows RFC 7807.
It contains structured fields like type, title, status, detail and instance.
Use these fields for logging and monitoring:
- Log
type,title,statusanddetailto your centralised logging system. - Use
instanceor additional extension fields for correlation when available. - Build dashboards or alerts based on recurring
type/statuscombinations.
Be careful not to log full customer payloads or other personal data.
Treat ProblemDetails as diagnostic information, not as user-facing messages.
Retries and timeouts
Retries are appropriate only for some classes of errors:
- Good candidates:
HttpExceptioncaused by transient network issues or timeouts.- Idempotent GET requests such as status polling or reading process changes.
- Poor candidates:
ApiExceptionrepresenting validation or business rule violations.- Non-idempotent POSTs that could create duplicate side effects.
Typical approach:
- Configure reasonable timeouts at the HTTP client level (see the Configuration section).
- Optionally implement a small retry mechanism around idempotent calls:
- Use exponential backoff (e.g. 200ms, 400ms, 800ms).
- Limit the number of attempts.
- Log all failures even when a retry eventually succeeds.
use Imoli\EflLeasingSdk\Exception\HttpException;
function callWithRetry(callable $operation, int $maxAttempts = 3)
{
$attempt = 0;
$delayMs = 200;
while (true) {
++$attempt;
try {
return $operation();
} catch (HttpException $e) {
if ($attempt >= $maxAttempts) {
throw $e;
}
usleep($delayMs * 1000);
$delayMs *= 2;
}
}
}
$state = callWithRetry(
fn () => $client->getProcessChanges($transactionId, null, $token)
);