RetryMiddleware
in package
implements
MiddlewareInterface
Retry/backoff middleware - retries a request on transient network exceptions and/or specific response status codes, with exponential backoff and full jitter between attempts. Honors a Retry-After response header over the computed delay when present. Skips retrying entirely when the request has a non-seekable body, since a partially-consumed stream can't be safely resent.
Tags
Table of Contents
Interfaces
- MiddlewareInterface
- Client middleware interface - a PSR-15-style interceptor for Client's outbound dispatch. Not a literal PSR-15 implementation: PSR-15's real interfaces are typed to ServerRequestInterface (server-side only) and cannot apply to a Client's outbound RequestInterface flow, so this mirrors PSR-15's shape with the request type swapped.
Properties
- $baseDelay : float
- Base delay, in seconds, for the exponential backoff calculation
- $maxDelay : float
- Maximum delay, in seconds, before jitter is applied
- $maxRetries : int
- Maximum number of retries (attempts beyond the first try)
- $onRetry : callable|null
- Optional observability callback, fired before each retry's sleep callable(int $attempt, RequestInterface $request, ?ResponseInterface $response, ?\Throwable $exception, float $delaySeconds): void
- $retryableMethods : array<string|int, mixed>
- HTTP methods eligible for retry (uppercase)
- $retryableStatusCodes : array<string|int, mixed>
- Response status codes eligible for retry
- $shouldRetryException : callable
- Predicate deciding whether a caught exception is retryable callable(\Throwable $exception): bool
- $sleeper : callable
- Sleep function, callable(float $seconds): void - defaults to a usleep() wrapper, overridable so tests don't have to actually sleep
Methods
- __construct() : mixed
- Constructor
- getBaseDelay() : float
- Get the base delay, in seconds
- getMaxDelay() : float
- Get the maximum delay, in seconds
- getMaxRetries() : int
- Get the maximum number of retries
- getRetryableMethods() : array<string|int, mixed>
- Get the HTTP methods eligible for retry
- getRetryableStatusCodes() : array<string|int, mixed>
- Get the response status codes eligible for retry
- process() : ResponseInterface
- Process the request, retrying on transient failures per the configured policy
- setBaseDelay() : RetryMiddleware
- Set the base delay, in seconds, for the exponential backoff calculation
- setMaxDelay() : RetryMiddleware
- Set the maximum delay, in seconds, before jitter is applied
- setMaxRetries() : RetryMiddleware
- Set the maximum number of retries
- setOnRetry() : RetryMiddleware
- Set the observability callback, fired before each retry's sleep
- setRetryableMethods() : RetryMiddleware
- Set the HTTP methods eligible for retry
- setRetryableStatusCodes() : RetryMiddleware
- Set the response status codes eligible for retry
- setShouldRetryException() : RetryMiddleware
- Set the predicate deciding whether a caught exception is retryable
- setSleeper() : RetryMiddleware
- Set the sleep function - test-only override point, defaults to a usleep() wrapper
- canRetryBody() : bool
- Determine if the request's body can be safely resent - true when there's no underlying stream to worry about, or the body is seekable.
- computeDelay() : float
- Compute the delay before the next attempt - honors a Retry-After response header over the computed exponential-with-jitter delay when present, clamped to the configured maximum delay
- isMethodRetryable() : bool
- Determine if the request's method is eligible for retry
- parseRetryAfter() : float|null
- Parse a Retry-After header value - either an integer number of seconds, or an HTTP-date to wait until (RFC 9110)
- rewindBody() : void
- Rewind the request's body, if it has an underlying stream. Uses the same "no stream attached" signal as canRetryBody() (see its docblock) so the two never drift out of sync.
- wait() : void
- Compute the delay, fire the onRetry callback, sleep, and rewind the request body
Properties
$baseDelay
Base delay, in seconds, for the exponential backoff calculation
protected
float
$baseDelay
= 0.1
$maxDelay
Maximum delay, in seconds, before jitter is applied
protected
float
$maxDelay
= 10.0
$maxRetries
Maximum number of retries (attempts beyond the first try)
protected
int
$maxRetries
= 3
$onRetry
Optional observability callback, fired before each retry's sleep callable(int $attempt, RequestInterface $request, ?ResponseInterface $response, ?\Throwable $exception, float $delaySeconds): void
protected
callable|null
$onRetry
= null
$retryableMethods
HTTP methods eligible for retry (uppercase)
protected
array<string|int, mixed>
$retryableMethods
= ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS']
$retryableStatusCodes
Response status codes eligible for retry
protected
array<string|int, mixed>
$retryableStatusCodes
= [429, 502, 503, 504]
$shouldRetryException
Predicate deciding whether a caught exception is retryable callable(\Throwable $exception): bool
protected
callable
$shouldRetryException
$sleeper
Sleep function, callable(float $seconds): void - defaults to a usleep() wrapper, overridable so tests don't have to actually sleep
protected
callable
$sleeper
Methods
__construct()
Constructor
public
__construct([int $maxRetries = 3 ]) : mixed
Parameters
- $maxRetries : int = 3
getBaseDelay()
Get the base delay, in seconds
public
getBaseDelay() : float
Return values
floatgetMaxDelay()
Get the maximum delay, in seconds
public
getMaxDelay() : float
Return values
floatgetMaxRetries()
Get the maximum number of retries
public
getMaxRetries() : int
Return values
intgetRetryableMethods()
Get the HTTP methods eligible for retry
public
getRetryableMethods() : array<string|int, mixed>
Return values
array<string|int, mixed>getRetryableStatusCodes()
Get the response status codes eligible for retry
public
getRetryableStatusCodes() : array<string|int, mixed>
Return values
array<string|int, mixed>process()
Process the request, retrying on transient failures per the configured policy
public
process(RequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
Parameters
- $request : RequestInterface
- $handler : RequestHandlerInterface
Return values
ResponseInterfacesetBaseDelay()
Set the base delay, in seconds, for the exponential backoff calculation
public
setBaseDelay(float $seconds) : RetryMiddleware
Parameters
- $seconds : float
Return values
RetryMiddlewaresetMaxDelay()
Set the maximum delay, in seconds, before jitter is applied
public
setMaxDelay(float $seconds) : RetryMiddleware
Parameters
- $seconds : float
Return values
RetryMiddlewaresetMaxRetries()
Set the maximum number of retries
public
setMaxRetries(int $maxRetries) : RetryMiddleware
Parameters
- $maxRetries : int
Return values
RetryMiddlewaresetOnRetry()
Set the observability callback, fired before each retry's sleep
public
setOnRetry(callable $callback) : RetryMiddleware
Parameters
- $callback : callable
-
callable(int $attempt, RequestInterface $request, ?ResponseInterface $response, ?\Throwable $exception, float $delaySeconds): void
Return values
RetryMiddlewaresetRetryableMethods()
Set the HTTP methods eligible for retry
public
setRetryableMethods(array<string|int, mixed> $methods) : RetryMiddleware
Parameters
- $methods : array<string|int, mixed>
Return values
RetryMiddlewaresetRetryableStatusCodes()
Set the response status codes eligible for retry
public
setRetryableStatusCodes(array<string|int, mixed> $statusCodes) : RetryMiddleware
Parameters
- $statusCodes : array<string|int, mixed>
Return values
RetryMiddlewaresetShouldRetryException()
Set the predicate deciding whether a caught exception is retryable
public
setShouldRetryException(callable $predicate) : RetryMiddleware
Parameters
- $predicate : callable
-
callable(\Throwable $exception): bool
Return values
RetryMiddlewaresetSleeper()
Set the sleep function - test-only override point, defaults to a usleep() wrapper
public
setSleeper(callable $sleeper) : RetryMiddleware
Parameters
- $sleeper : callable
-
callable(float $seconds): void
Return values
RetryMiddlewarecanRetryBody()
Determine if the request's body can be safely resent - true when there's no underlying stream to worry about, or the body is seekable.
protected
canRetryBody(RequestInterface $request) : bool
Deliberately does not use getSize() === 0 as the "no body" signal: a real, statable stream (e.g. a pipe, socket, or php://stdin) can legitimately report a size of 0 while still being non-rewindable, and that must fall through to the isSeekable() check rather than being waved through. A Body with no stream attached at all has no metadata (getMetadata() returns []), which is what actually means "no body".
Parameters
- $request : RequestInterface
Return values
boolcomputeDelay()
Compute the delay before the next attempt - honors a Retry-After response header over the computed exponential-with-jitter delay when present, clamped to the configured maximum delay
protected
computeDelay(ResponseInterface|null $response, int $attempt) : float
Parameters
- $response : ResponseInterface|null
- $attempt : int
Return values
floatisMethodRetryable()
Determine if the request's method is eligible for retry
protected
isMethodRetryable(RequestInterface $request) : bool
Parameters
- $request : RequestInterface
Return values
boolparseRetryAfter()
Parse a Retry-After header value - either an integer number of seconds, or an HTTP-date to wait until (RFC 9110)
protected
parseRetryAfter(string $value) : float|null
Parameters
- $value : string
Return values
float|nullrewindBody()
Rewind the request's body, if it has an underlying stream. Uses the same "no stream attached" signal as canRetryBody() (see its docblock) so the two never drift out of sync.
protected
rewindBody(RequestInterface $request) : void
Parameters
- $request : RequestInterface
wait()
Compute the delay, fire the onRetry callback, sleep, and rewind the request body
protected
wait(RequestInterface $request, int $attempt, ResponseInterface|null $response, Throwable|null $exception) : void
Parameters
- $request : RequestInterface
- $attempt : int
- $response : ResponseInterface|null
- $exception : Throwable|null