All guides
No guide matches your search.
API reference
Every public class, interface and enum of Reqtide, with its signatures and a short note on what each method does. Read it next to the guides.
On this page
This page lists the public surface of enaxon/reqtide, enaxon/reqtide-redis and enaxon/reqtide-guzzle. The signatures are read from the source code, so they match the release. Types and methods that are marked internal are not listed and may change at any time.
Start with Getting started if you have not used the library yet.
Build a pipeline
Everything you need at start-up. PipelineBuilder assembles and validates the configuration, compileInMemory() wires process-local state, and the result is a CompiledPipeline you reuse. Every number is yours: the builder has no defaults.
PipelineBuilder
Fluent, immutable builder: every call returns a new builder. profile(), partition() and attemptTimeout() are required; a policy exists only if you call its method. A bad value raises InvalidConfigurationException naming the option.
final class PipelineBuilder {
public static function profile(string $id, string $version): self;
public function partition(
string $application,
string $environment,
string $subject,
?string $tenant = null,
): self;
public function attemptTimeout(
Duration $maximumDuration,
AttemptTimeoutRequirement $requirement,
): self;
public function retry(
int $maxAttempts,
BackoffMode $backoff,
Duration $baseDelay,
Duration $maximumDelay,
JitterMode $jitter,
?Duration $minimumRemainingAttemptAllowance = null,
): self;
public function retryBudget(
int $capacityTokens,
int $refillTokens,
Duration $refillInterval,
Duration $reservationLease,
): self;
public function rateLimit(
RateAlgorithmOptions $algorithm,
int $cost = 1,
): self;
public function circuitBreaker(
CircuitWindowOptions $window,
int $minimumSamples,
Ratio $failureThreshold,
Duration $openDuration,
int $probeCapacity,
int $probeSuccessThreshold,
Duration $permitLease,
): self;
public function bulkhead(int $capacity, Duration $lease): self;
public function cooldown(
Duration $cleanupGrace,
?Duration $maximumExtension = null,
): self;
public function fallbackScope(FallbackScope $scope): self;
public static function tokenBucket(
int $capacity,
int $refillTokens,
Duration $refillInterval,
): TokenBucketOptions;
public static function fixedWindow(
int $capacity,
Duration $window,
FixedWindowAnchor $anchor,
): FixedWindowOptions;
public static function slidingWindow(
int $capacity,
Duration $window,
): SlidingCounterOptions;
public static function lastCalls(int $completedSamples): CountCircuitWindow;
public static function timeWindow(
Duration $window,
Duration $bucketGranularity,
): TimeCircuitWindow;
public function definition(): PipelineDefinition;
public function compile(RuntimeEnvironment $environment): CompiledPipeline;
public function compileInMemory(
OutcomeClassifier $classifier,
?EventSink $events = null,
): CompiledPipeline;
}profile()Names and versions the configuration. Change the version when settings change; shared state is keyed by it.partition()Whose state this is: application, environment, subject and an optional tenant. One pipeline serves exactly one partition.attemptTimeout()The longest one attempt may take. UseCooperativewith the synchronous runtime.retry()Up tomaxAttemptsattempts, the first included. More than one needsretryBudget()and a replay-safety assertion on every call.retryBudget()A token pool that pays for retries so a failing dependency is not hammered.rateLimit()Chargescostunits per dispatch, retries included. Takes the result oftokenBucket(),fixedWindow()orslidingWindow().circuitBreaker()Judges a window of completed calls, opens at a failure share, probes after the open duration.bulkhead()At mostcapacityoperations in flight;leasebounds a slot whose owner vanished.cooldown()A shared pause after a server-stated wait. Needs a classifier that reports waits.fallbackScope()Whether a fallback may also replace admission refusals.tokenBucket()Rate algorithm: capacity, refill tokens per interval.fixedWindow()Rate algorithm: capacity per window, aligned to the epoch or to the first hit.slidingWindow()Rate algorithm: an approximate sliding counter.lastCalls()Circuit window over the last N completed calls.timeWindow()Circuit window over a duration, kept in buckets.definition()Validates the composition and returns the immutablePipelineDefinition.compile()Compiles against a hand-builtRuntimeEnvironment, for example with test helpers.compileInMemory()Compiles with the real clock, sleeping delays and process-local memory backends. There is no default classifier.
CompiledPipeline
The reusable pipeline. It holds the state that makes limits work, so build it once and share it. It has no per-operation state.
final class CompiledPipeline {
public function newContext(
Duration $timeout,
?OperationSafety $safety = null,
?FallbackOperation $fallback = null,
?Cancellation $cancellation = null,
?BoundedMetadata $attributes = null,
OperationId|string|null $operationId = null,
?PartitionIdentity $partition = null,
): ExecutionContext;
public function execute(
callable $operation,
ExecutionContext $context,
): mixed;
public function executeDetailed(
callable $operation,
ExecutionContext $context,
): ExecutionCompletion;
public function executeOperation(
AttemptOperation $operation,
ExecutionContext $context,
): mixed;
public function executeOperationDetailed(
AttemptOperation $operation,
ExecutionContext $context,
): ExecutionCompletion;
public function explain(): PipelineExplanation;
}newContext()One context per operation: the total deadline, the replay-safety statement, an optional fallback, cancellation and an operation id.execute()Runs a callable through the pipeline. Returns your value, throws your own exception unchanged, or throws a Reqtide refusal.executeDetailed()Same run, but returns anExecutionCompletioninstead of throwing for business or policy outcomes.executeOperation()Likeexecute()for anAttemptOperationobject instead of a callable.executeOperationDetailed()LikeexecuteDetailed()for anAttemptOperation.explain()Describes the configured policies and the runtime capabilities. No I/O and no state change.
InMemoryEnvironment
Wires the runtime for one process: system clock, a scheduler that really sleeps, secure randomness and a memory backend for each configured policy. This is what compileInMemory() uses.
final class InMemoryEnvironment {
public static function for(
PipelineDefinition $definition,
OutcomeClassifier $classifier,
?EventSink $events = null,
): RuntimeEnvironment;
}PipelineExplanation
What explain() returns: profile, enabled policies and capability notes such as physical_stop=not_guaranteed.
final readonly class PipelineExplanation {
public function __construct(
public ProfileId $profileId,
public ProfileVersion $profileVersion,
public array $enabledPolicies,
public array $capabilityNotes,
);
}Duration
A non-negative integer count of nanoseconds. Arithmetic is checked: overflow throws and never wraps.
final readonly class Duration {
public function __construct(public int $nanoseconds);
public static function zero(): self;
public static function nanoseconds(int $value): self;
public static function microseconds(int $value): self;
public static function milliseconds(int $value): self;
public static function seconds(int $value): self;
public function isZero(): bool;
public function isPositive(): bool;
public function add(self $other): self;
public function subtract(self $other): self;
public function saturatingSubtract(self $other): self;
public function multiply(int $factor): self;
public function min(self $other): self;
public function max(self $other): self;
public function compare(self $other): int;
public function equals(self $other): bool;
public function ceilMicroseconds(): int;
}Ratio
A reduced fraction in (0, 1], compared with integers. Used for the circuit failure threshold.
final readonly class Ratio {
public const MAX_COMPONENT = 9007199254740991;
public function __construct(
public PositiveInt $numerator,
public PositiveInt $denominator,
);
public static function of(int $numerator, int $denominator): self;
}Run an operation
Per-operation objects. A context carries the total deadline and your statement about repeating the call; your callable receives an AttemptContext.
ExecutionContext
Immutable facts of one logical operation. Build it with CompiledPipeline::newContext(). Do not share one between operations.
final readonly class ExecutionContext {
public function __construct(
public OperationId $operationId,
public PartitionIdentity $partition,
public Deadline $deadline,
public Cancellation $cancellation,
public OperationMetadata $metadata,
public ?FallbackOperation $fallback,
);
}AttemptContext
Passed to your callable on every attempt: operation id, attempt id, the attempt number, the deadline, the cancellation and the attempt timeout.
final readonly class AttemptContext {
public function __construct(
public OperationId $operationId,
public AttemptId $attemptId,
public int $dispatchedAttemptNumber,
public Deadline $deadline,
public Cancellation $cancellation,
public Duration $attemptTimeout,
);
}OperationSafety
Your statement about repeating a call. unsafe() is the default and forbids retries; the other two allow them.
final readonly class OperationSafety {
public static function unsafe(): self;
public static function replayableRead(ReplaySafetyContract $contract): self;
public static function providerIdempotentMutation(
ReplaySafetyContract $contract,
): self;
}unsafe()The default. A pipeline that allows more than one attempt refuses to run.replayableRead()You assert that running the callable again is safe.providerIdempotentMutation()You assert that the provider deduplicates repeated mutations.
ReplaySafetyContract
The written assertion behind OperationSafety. Reqtide never verifies it.
final readonly class ReplaySafetyContract {
public static function forReadOnlyCallable(
string $operationName,
Duration $validFor,
?Clock $clock = null,
?array $retryableOutcomeCodes = null,
): self;
public function __construct(
public string $contractReference,
public string $identityValue,
public string $identityScope,
public string $duplicateHandlingContract,
public DateTimeImmutable $establishedAt,
public DateTimeImmutable $validUntil,
public Duration $retention,
public ReplayPreparation $preparation,
public array $retryableOutcomeCodes,
);
}forReadOnlyCallable()Builds the contract for a repeatable read. Your call is the assertion; it is not checked.
OperationSafetyKind
The kind of safety an operation carries.
enum OperationSafetyKind: string {
case Unsafe = 'unsafe';
case ReplayableRead = 'replayable_read';
case ProviderIdempotentMutation = 'provider_idempotent_mutation';
}FallbackOperation
A fallback you attach to one operation through newContext(). It receives the original outcome.
interface FallbackOperation {
public function invoke(Outcome $original, ExecutionContext $context): mixed;
}FallbackScope
What a fallback may replace. The default replaces business outcomes only.
enum FallbackScope: string {
case BusinessOutcomesOnly = 'business_outcomes_only';
case IncludeAdmissionRefusals = 'include_admission_refusals';
}Decide what an outcome means
Policies act on the classification of each attempt, and you decide what a failure is. ExceptionClassifier is the ready-made classifier; implement OutcomeClassifier for anything it cannot express.
ExceptionClassifier
Classifier driven by lists of exception classes. Conservative on purpose: an exception nobody listed is not retried and not replaced by a fallback, but it counts as a circuit failure. Matching is instanceof. Every method returns a new instance.
final readonly class ExceptionClassifier implements OutcomeClassifier {
public static function create(): self;
public function retryOn(string ...$exceptionClasses): self;
public function countAsCircuitFailure(string ...$exceptionClasses): self;
public function ignoreForCircuit(string ...$exceptionClasses): self;
public function fallbackOn(string ...$exceptionClasses): self;
public function retryAfterFrom(callable $extractor): self;
public function unacceptableValuesWhen(callable $predicate): self;
public function classify(
Outcome $outcome,
OperationMetadata $metadata,
): OutcomeClassification;
}create()An empty classifier.retryOn()Exception classes that may be retried.countAsCircuitFailure()Narrows circuit failures to these classes (and retryable ones). Without it every exception that is not ignored counts.ignoreForCircuit()Exception classes that say nothing about the dependency, such as validation errors. Ignore wins over a failure list.fallbackOn()Exception classes a fallback may replace.retryAfterFrom()Extracts a server-stated wait (aDurationornull) from an exception.unacceptableValuesWhen()Marks a returned value as a failure: a circuit failure, never retried, never replaced.classify()TheOutcomeClassifiercontract.
OutcomeClassifier
The contract a classifier implements. Write your own when results must be retried or classified in ways the exception lists cannot say.
interface OutcomeClassifier {
public function classify(
Outcome $outcome,
OperationMetadata $metadata,
): OutcomeClassification;
}Outcome
What one attempt produced: a returned value or a thrown exception.
final readonly class Outcome {
public static function fromValue(
mixed $value,
BoundedMetadata $metadata,
): self;
public static function fromException(
Throwable $exception,
BoundedMetadata $metadata,
): self;
public function value(): mixed;
public function exception(): Throwable;
}OutcomeClassification
The classifier's verdict on one outcome: retry eligibility, circuit verdict, fallback eligibility, acceptability, a reason code and an optional wait lower bound.
final readonly class OutcomeClassification {
public function __construct(
public RetryEligibility $retry,
public CircuitVerdict $circuit,
public FallbackEligibility $fallback,
public Acceptability $acceptability,
public string $reasonCode,
public ?Duration $retryAfterLowerBound,
);
}OutcomeKind
Whether an outcome is a value or an exception.
enum OutcomeKind: string {
case Value = 'value';
case Exception = 'exception';
}RetryEligibility
Whether an outcome may be retried.
enum RetryEligibility: string {
case Eligible = 'eligible';
case Ineligible = 'ineligible';
}FallbackEligibility
Whether a fallback may replace an outcome.
enum FallbackEligibility: string {
case Eligible = 'eligible';
case Ineligible = 'ineligible';
}CircuitVerdict
How an outcome counts for the circuit breaker. Neutral outcomes neither open nor heal it.
enum CircuitVerdict: string {
case Success = 'success';
case Failure = 'failure';
case Neutral = 'neutral';
}Acceptability
Whether an outcome is acceptable to the caller.
enum Acceptability: string {
case Acceptable = 'acceptable';
case Unacceptable = 'unacceptable';
}Policy options
Enums and option objects the builder takes or returns. You normally reach them through PipelineBuilder and rarely construct them yourself.
BackoffMode
Retry delay growth: constant or exponential.
enum BackoffMode: string {
case Constant = 'constant';
case Exponential = 'exponential';
}JitterMode
Full draws each delay uniformly from zero to the computed delay.
enum JitterMode: string {
case None = 'none';
case Full = 'full';
}AttemptTimeoutRequirement
Cooperative is the honest choice for the synchronous runtime: your code must respect the time. TransportEnforced needs a runtime that declares it.
enum AttemptTimeoutRequirement: string {
case Cooperative = 'cooperative';
case TransportEnforced = 'transport_enforced';
}FixedWindowAnchor
Where fixed windows start: on multiples of the window length, or at the first hit.
enum FixedWindowAnchor: string {
case Aligned = 'aligned';
case FirstHit = 'first_hit';
}TokenBucketOptions
Token-bucket settings returned by PipelineBuilder::tokenBucket().
final readonly class TokenBucketOptions implements RateAlgorithmOptions {
public function __construct(
public PositiveInt $capacityUnits,
public PositiveInt $refillUnits,
public Duration $refillInterval,
);
}FixedWindowOptions
Fixed-window settings returned by PipelineBuilder::fixedWindow().
final readonly class FixedWindowOptions implements RateAlgorithmOptions {
public function __construct(
public PositiveInt $capacityUnits,
public Duration $window,
public FixedWindowAnchor $anchor,
);
}SlidingCounterOptions
Sliding-counter settings returned by PipelineBuilder::slidingWindow().
final readonly class SlidingCounterOptions implements RateAlgorithmOptions {
public function __construct(
public PositiveInt $capacityUnits,
public Duration $window,
);
}CountCircuitWindow
Circuit window over the last N completed calls.
final readonly class CountCircuitWindow implements CircuitWindowOptions {
public function __construct(public PositiveInt $completedSamples);
}TimeCircuitWindow
Circuit window over a duration, kept in buckets.
final readonly class TimeCircuitWindow implements CircuitWindowOptions {
public function __construct(
public Duration $window,
public Duration $bucketGranularity,
);
}Results of an operation
execute() returns your value or throws. executeDetailed() returns the same facts as data in an ExecutionCompletion.
ExecutionCompletion
Disposition, terminal outcome, per-attempt records, bounded diagnostics and a summary of counts.
final readonly class ExecutionCompletion {
public function __construct(
public CompletionDisposition $disposition,
public ?Outcome $terminalOutcome,
public ?Outcome $fallbackOutcome,
public ?ReqtideException $policyCause,
public ?ClassificationFailedException $classifierCause,
public array $attempts,
public array $diagnostics,
public ExecutionSummary $summary,
);
public function problem(): ?Problem;
}problem()The redactedProblemfor this completion, ornullwhen there is nothing for Reqtide to report.
CompletionDisposition
How an operation ended.
enum CompletionDisposition: string {
case Succeeded = 'succeeded';
case Failed = 'failed';
case Rejected = 'rejected';
case Deferred = 'deferred';
case Cancelled = 'cancelled';
case DeadlineExceeded = 'deadline_exceeded';
case FallbackSucceeded = 'fallback_succeeded';
case FallbackFailed = 'fallback_failed';
}ExecutionSummary
Counts for one operation: dispatches, retries, outcomes, fallbacks, elapsed time and whether accounting is complete.
final readonly class ExecutionSummary {
public function __construct(
public OperationId $operationId,
public ProfileVersion $profileVersion,
public CompletionDisposition $disposition,
public int $dispatched,
public int $admittedNotDispatched,
public int $retries,
public int $successfulAttempts,
public int $failedAttempts,
public int $neutralAttempts,
public int $actualValues,
public int $actualExceptions,
public int $fallbackExecutions,
public bool $accountingComplete,
public int $omittedDiagnostics,
public ?Duration $elapsed,
public int $admissionUncertain = 0,
);
}AttemptRecord
One physical attempt: its outcome, classification and circuit verdict.
final readonly class AttemptRecord {
public function __construct(
public AttemptId $attemptId,
public int $dispatchedAttemptNumber,
public ActualAttemptDisposition $disposition,
public Outcome $outcome,
public ?Duration $elapsed,
public SettlementOverride $logicalOverride,
public ?OutcomeClassification $classification,
public CircuitVerdict $circuitVerdict,
);
}Diagnostic
A bounded note about a stage and reason, such as retry / attempts_exhausted.
final readonly class Diagnostic {
public function __construct(
public string $stageCode,
public string $reasonCode,
);
public static function of(Stage $stage, ReasonCode $reason): self;
public function stage(): ?Stage;
public function reason(): ?ReasonCode;
}Errors and reasons
Your own exceptions come out unwrapped. A Reqtide decision is a typed exception that implements ProblemCarrier. Branch on reason() and problem(), never on message text.
Problem
Immutable, redaction-safe report shaped after RFC 9457 problem details. It is built from code metadata only: no message, URL, header or credential can enter it. Member names and order are API; title and detail wording is not.
final readonly class Problem {
public const TYPE_PREFIX = 'tag:enaloo.com,2026:reqtide:problem:';
public ?Stage $stage;
public ?string $correlationId;
public array $causes;
public function __construct(
public ReasonCode $code,
?Stage $stage = null,
public ?OperationId $operationId = null,
public ?ProfileVersion $profileVersion = null,
public ?AttemptId $attemptId = null,
?string $correlationId = null,
array $causes = [],
);
public function withContext(
OperationId $operationId,
ProfileVersion $profileVersion,
?AttemptId $attemptId = null,
?string $correlationId = null,
): self;
public function type(): string;
public function title(): string;
public function category(): Category;
public function severity(): Severity;
public function retryability(): Retryability;
public function detail(): string;
public function toArray(): array;
public function jsonSerialize(): array;
}ProblemCarrier
Implemented by every exception that describes a Reqtide decision.
interface ProblemCarrier extends ReqtideException {
public function reason(): ReasonCode;
public function problem(): Problem;
}ReqtideException
Marker for every exception raised by Reqtide itself. Your own exceptions are never wrapped in it.
interface ReqtideException {
}ReasonCode
The registry of stable reason codes. Keep a default branch when you match over it: new cases may be added.
enum ReasonCode: string {
case CircuitOpen = 'circuit_open';
case CircuitProbeCapacityExhausted = 'circuit_probe_capacity_exhausted';
case ConcurrencyCapacityExhausted = 'concurrency_capacity_exhausted';
case OwnershipLeaseInsufficient = 'ownership_lease_insufficient';
case CooldownActive = 'cooldown_active';
case CooldownExceedsDeadline = 'cooldown_exceeds_deadline';
case RateAccepted = 'rate.accepted';
case RateCostExceedsCapacity = 'rate.cost_exceeds_capacity';
case RateExhausted = 'rate.exhausted';
case RateWaitUnrepresentable = 'rate.wait_unrepresentable';
case AdmissionRejected = 'admission_rejected';
case AdmissionDeferred = 'admission_deferred';
case AdmissionImpossible = 'admission_impossible';
case CircuitThresholdExceeded = 'circuit.threshold_exceeded';
case CircuitProbeFailed = 'circuit.probe_failed';
case CircuitProbesSucceeded = 'circuit.probes_succeeded';
case CircuitOpenElapsed = 'circuit.open_elapsed';
case RetryBudgetExhausted = 'retry_budget_exhausted';
case RetryBudgetCommitRefused = 'retry_budget_commit_refused';
case AttemptsExhausted = 'attempts_exhausted';
case OutcomeNotRetryable = 'outcome_not_retryable';
case RetryDelayExceedsDeadline = 'retry_delay_exceeds_deadline';
case AttemptAllowanceUnavailable = 'attempt_allowance_unavailable';
case ReplayContractNotValid = 'replay_contract_not_valid';
case ReplayHorizonInadequate = 'replay_horizon_inadequate';
case ReplayHorizonSaturated = 'replay_horizon_saturated';
case RetryDeadlineReached = 'retry_deadline_reached';
case RetryCancelled = 'retry_cancelled';
case RetryAfterExceedsMaximumDelay = 'retry_after_exceeds_maximum_delay';
case RetryAfterExceedsDeadline = 'retry_after_exceeds_deadline';
case DelayFailed = 'delay_failed';
case OperationCancelled = 'operation_cancelled';
case DeadlineExceeded = 'deadline_exceeded';
case StateUnavailable = 'unavailable';
case StateCorrupt = 'corrupt';
case StateIndeterminate = 'indeterminate';
case StateCapacityExceeded = 'capacity_exceeded';
case ClockFailure = 'clock_failure';
case UnexpectedBackendFailure = 'unexpected';
case RuntimeAdapterFailure = 'runtime_adapter_failure';
case RateConsumeIndeterminate = 'rate_consume_indeterminate';
case CommitNotConfirmed = 'commit_not_confirmed';
case ClockUnavailable = 'clock_unavailable';
case RetryClockUnavailable = 'retry_clock_unavailable';
case FallbackClockUnavailable = 'fallback_clock_unavailable';
case TimingUnavailable = 'timing_unavailable';
case FallbackTimingUnavailable = 'fallback_timing_unavailable';
case ElapsedUnavailable = 'elapsed_unavailable';
case CancellationSampleFailed = 'cancellation_sample_failed';
case RandomOutOfRange = 'random_out_of_range';
case ConcurrencyReleaseFailed = 'concurrency_release_failed';
case ConcurrencyReleaseStale = 'concurrency_release_stale';
case CircuitAbandonFailed = 'circuit_abandon_failed';
case CircuitAbandonStale = 'circuit_abandon_stale';
case CircuitRecordFailed = 'circuit_record_failed';
case CircuitRecordStale = 'circuit_record_stale';
case RetryReservationReleaseFailed = 'retry_reservation_release_failed';
case CooldownExtendFailed = 'cooldown_extend_failed';
case CooldownExtensionCapped = 'cooldown_extension_capped';
case SettlementAccounting = 'settlement_accounting';
case CompletionSinkFailed = 'completion_sink_failed';
case EventSinkFailed = 'event_sink_failed';
case ClassifierFailed = 'classifier_failed';
case LateSignalExtractionFailed = 'late_signal_extraction_failed';
case FallbackFailed = 'fallback_failed';
case DiscardFailed = 'discard_failed';
case ValueAccepted = 'value_accepted';
case ValueUnacceptable = 'value_unacceptable';
case ExceptionRetryable = 'exception_retryable';
case ExceptionFailure = 'exception_failure';
case ExceptionIgnored = 'exception_ignored';
case PreparationFailed = 'preparation_failed';
case LifecycleFailure = 'lifecycle_failure';
case UnexpectedFailure = 'unexpected_failure';
case InvalidConfiguration = 'invalid_configuration';
case CapabilityMismatch = 'capability_mismatch';
public function category(): Category;
public function severity(): Severity;
public function retryability(): Retryability;
public function stage(): ?Stage;
public function title(): string;
}Retryability
What a caller should do with a decision: wait, retry, fix the setup or reconcile.
enum Retryability: string {
case Never = 'never';
case AfterWait = 'after_wait';
case Transient = 'transient';
case Unknown = 'unknown';
}Category
The kind of decision a code belongs to.
enum Category: string {
case Policy = 'policy';
case Infrastructure = 'infrastructure';
case Configuration = 'configuration';
case Classification = 'classification';
case Fallback = 'fallback';
case Admission = 'admission';
case Retry = 'retry';
case Accounting = 'accounting';
case Preparation = 'preparation';
}Severity
Advisory severity, using PSR-3 level names as plain strings.
enum Severity: string {
case Debug = 'debug';
case Info = 'info';
case Notice = 'notice';
case Warning = 'warning';
case Error = 'error';
case Critical = 'critical';
public function psr3Level(): string;
public function rank(): int;
public function isAtLeast(self $threshold): bool;
}Stage
The lifecycle stage a diagnostic or problem is reported at. Append-only.
enum Stage: string {
case Preflight = 'preflight';
case Preparation = 'preparation';
case RetryBudget = 'retry_budget';
case Admission = 'admission';
case Barrier = 'barrier';
case Dispatch = 'dispatch';
case Settlement = 'settlement';
case Classification = 'classification';
case Finalize = 'finalize';
case Cleanup = 'cleanup';
case Discard = 'discard';
case Retry = 'retry';
case Fallback = 'fallback';
case Observation = 'observation';
case Summary = 'summary';
}PolicyTerminationException
Base of the exceptions by which a policy ended the operation.
abstract class PolicyTerminationException implements ProblemCarrier, ReqtideException {
public function problem(): Problem;
}AdmissionRejectedException
A circuit, bulkhead or rate limit refused the call. Your callable did not run.
final class AdmissionRejectedException extends PolicyTerminationException {
public function __construct(
public readonly AdmissionDecision $decision,
?Throwable $previous = null,
);
public function reason(): ReasonCode;
}AdmissionDeferredException
A refusal that carries a positive wait, for example a cooldown or a Retry-After that cannot be honored.
final class AdmissionDeferredException extends PolicyTerminationException {
public function __construct(
public readonly AdmissionDecision $decision,
?Throwable $previous = null,
);
public function reason(): ReasonCode;
}ImpossibleAdmissionException
The request can never be admitted, for example a rate cost above the capacity.
final class ImpossibleAdmissionException extends PolicyTerminationException {
public function __construct(
public readonly AdmissionDecision $decision,
?Throwable $previous = null,
);
public function reason(): ReasonCode;
}DeadlineExceededException
The total deadline passed. A late value is replaced by this.
final class DeadlineExceededException extends PolicyTerminationException {
public function reason(): ReasonCode;
}OperationCancelledException
The operation was cancelled through its cancellation.
final class OperationCancelledException extends PolicyTerminationException {
public function reason(): ReasonCode;
}FallbackFailedException
The fallback itself threw. getPrevious() is the fallback exception.
final class FallbackFailedException implements ProblemCarrier, ReqtideException {
public function __construct(
public readonly Outcome $originalOutcome,
public readonly Throwable $fallbackCause,
);
public function reason(): ReasonCode;
public function problem(): Problem;
}InvalidConfigurationException
A value, option or combination is invalid. Raised before anything is dispatched.
final class InvalidConfigurationException implements ProblemCarrier, ReqtideException {
public function reason(): ReasonCode;
public function problem(): Problem;
}CapabilityMismatchException
A configured behavior needs a capability the selected runtime, scheduler or backend does not declare.
final class CapabilityMismatchException implements ProblemCarrier, ReqtideException {
public function reason(): ReasonCode;
public function problem(): Problem;
}InfrastructureException
Base of clock and state-backend failures. They are never hidden as success.
abstract class InfrastructureException implements ProblemCarrier, ReqtideException {
public function problem(): Problem;
}StateUnavailableException
A state backend could not be reached. The protected call does not run.
final class StateUnavailableException extends InfrastructureException {
public function reason(): ReasonCode;
}StateCapacityExceededException
A memory backend reached its ceiling and rejected a new partition.
final class StateCapacityExceededException extends InfrastructureException {
public function reason(): ReasonCode;
}CorruptStateException
A state backend found corrupt state.
final class CorruptStateException extends InfrastructureException {
public function reason(): ReasonCode;
}IndeterminateStateException
The result of a state-backend operation is indeterminate.
final class IndeterminateStateException extends InfrastructureException {
public function reason(): ReasonCode;
}ClockFailureException
A clock could not be read.
final class ClockFailureException extends InfrastructureException {
public function reason(): ReasonCode;
}ClassificationFailedException
The classifier itself threw.
final class ClassificationFailedException implements ProblemCarrier, ReqtideException {
public function __construct(
public readonly Outcome $actualOutcome,
public readonly Throwable $classifierCause,
);
public function reason(): ReasonCode;
public function problem(): Problem;
}PreparationFailedException
Attempt preparation or replay verification failed. Nothing was dispatched.
final class PreparationFailedException implements ProblemCarrier, ReqtideException {
public function __construct(public readonly Throwable $preparationCause);
public function reason(): ReasonCode;
public function problem(): Problem;
}Events
Core emits its own typed events and has no telemetry-library dependency. Pass an EventSink to compileInMemory(). A failing sink never replaces your result.
EventSink
Receives every event. Implement it to log or count.
interface EventSink {
public function emit(Event $event): void;
}Event
Base contract of all events: each names its operation and profile version. EventSeverity::of() gives an event a severity.
interface Event {
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
}RetryScheduled
A retry was scheduled after a delay.
final readonly class RetryScheduled implements Event {
public function __construct(
private OperationId $operation,
private ProfileVersion $profile,
public int $nextDispatchedAttemptNumber,
public Duration $delay,
public string $reasonCode,
);
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
}AdmissionRejected
A policy refused an attempt. policyKind and reasonCode say which.
final readonly class AdmissionRejected implements Event {
public function __construct(
private OperationId $operation,
private ProfileVersion $profile,
public string $policyKind,
public string $reasonCode,
);
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
public function reason(): ?ReasonCode;
public function severity(): Severity;
}AttemptCompleted
An attempt settled, with its circuit verdict.
final readonly class AttemptCompleted implements Event {
public function __construct(
private OperationId $operation,
private ProfileVersion $profile,
public AttemptId $attemptId,
public CircuitVerdict $verdict,
public ?Duration $elapsed,
);
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
}OperationCompleted
The operation ended, with its disposition.
final readonly class OperationCompleted implements Event {
public function __construct(
private OperationId $operation,
private ProfileVersion $profile,
public CompletionDisposition $disposition,
public ?Duration $elapsed,
public bool $accountingComplete,
);
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
}CircuitTransitioned
The circuit changed state.
final readonly class CircuitTransitioned implements Event {
public function __construct(
private OperationId $operation,
private ProfileVersion $profile,
public string $fromState,
public string $toState,
public string $reasonCode,
);
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
public function reason(): ?ReasonCode;
public function severity(): Severity;
}BackendErrorObserved
A state backend reported an error.
final readonly class BackendErrorObserved implements Event {
public function __construct(
private OperationId $operation,
private ProfileVersion $profile,
public string $backendKind,
public string $failureCode,
);
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
public function reason(): ?ReasonCode;
public function severity(): Severity;
}AccountingDegraded
A bookkeeping step after the call failed or was stale. reasonCode says which.
final readonly class AccountingDegraded implements Event {
public function __construct(
private OperationId $operation,
private ProfileVersion $profile,
public string $reasonCode,
);
public function operationId(): OperationId;
public function profileVersion(): ProfileVersion;
public function reason(): ?ReasonCode;
public function severity(): Severity;
}NullEventSink
The default sink: discards every event.
final class NullEventSink implements EventSink {
public function emit(Event $event): void;
}EventSeverity
Severity of an event, with PSR-3 level names.
final class EventSeverity {
public static function of(Event $event): Severity;
public static function ofDisposition(
CompletionDisposition $disposition,
): Severity;
}Test helpers
In the Enaxon\Reqtide\Testing namespace, shipped in the same package. For tests only: never call them from production code. See Testing with the virtual clock.
VirtualClock
A clock that moves only when you move it. Starts at a fixed date so a test gives the same answer every day.
final class VirtualClock implements Clock {
public const DEFAULT_WALL_START = '2026-01-01T00:00:00+00:00';
public function __construct(
int $initialMonotonicNanoseconds = 1000000000,
?DateTimeImmutable $initialWall = null,
);
public function startingAt(DateTimeImmutable $wall): self;
public function monotonicNow(): int;
public function wallNow(): DateTimeImmutable;
public function advance(Duration $duration): void;
public function advanceMonotonicOnly(Duration $duration): void;
public function setMonotonic(int $nanoseconds): void;
public function setWall(DateTimeImmutable $wall): void;
public function failNextMonotonicReads(int $count = 1): void;
public function failNextWallReads(int $count = 1): void;
public function monotonicReadCount(): int;
public function wallReadCount(): int;
public function peekMonotonic(): int;
}VirtualDelayScheduler
Advances a VirtualClock instead of sleeping, and records every requested delay.
final class VirtualDelayScheduler implements DelayScheduler {
public function __construct(private readonly VirtualClock $clock);
public function capability(): SchedulingCapability;
public function cancelDuringNextDelay(
CancellationToken $token,
Duration $after,
): void;
public function delay(
Duration $duration,
Deadline $deadline,
Cancellation $cancellation,
): DelayDisposition;
public function records(): array;
public function requestedDelays(): array;
public function callCount(): int;
public function totalElapsed(): Duration;
}ControlledRuntimeAdapter
A synchronous runtime driven by scripts: return a value, throw, advance the clock or request cancellation per dispatch.
final class ControlledRuntimeAdapter implements RuntimeAdapter {
public function __construct(
private readonly VirtualClock $clock,
private readonly ?DispatchScript $default = null,
private readonly ?RuntimeCapabilities $capabilities = null,
);
public static function defaultCapabilities(): RuntimeCapabilities;
public function enqueue(DispatchScript ...$scripts): void;
public function capabilities(): RuntimeCapabilities;
public function invoke(
PreparedAttempt $operation,
AttemptContext $context,
AttemptControls $controls,
): RuntimeExecution;
public function dispatchCount(): int;
public function dispatches(): array;
public function attemptContexts(): array;
public function pendingScripts(): int;
}DispatchScript
One scripted dispatch.
final readonly class DispatchScript {
public static function returning(mixed $value): self;
public static function throwing(Throwable $exception): self;
public static function callingOperation(): self;
public function advancing(Duration $duration): self;
public function requestingCancellation(CancellationToken $token): self;
}RecordingEventSink
Stores every event for assertions.
final class RecordingEventSink implements EventSink {
public function emit(Event $event): void;
public function throwOnEmit(Throwable $failure, ?int $times = null): void;
public function stopThrowing(): void;
public function events(): array;
public function ofType(string $class): array;
public function count(): int;
public function clear(): void;
}SeededRandomSource
Seeded randomness: same seed, same sequence.
final class SeededRandomSource implements RandomSource {
public function __construct(public readonly int $seed);
public function integer(int $minimumInclusive, int $maximumInclusive): int;
public function draws(): int;
}ScriptedRandomSource
Returns a queued list of integers and fails loudly when it runs out.
final class ScriptedRandomSource implements RandomSource {
public function __construct(array $values);
public function enqueue(int ...$values): void;
public function integer(int $minimumInclusive, int $maximumInclusive): int;
public function remaining(): int;
public function requests(): array;
}Redis package: set up shared state
These classes belong to the separate package enaxon/reqtide-redis, namespace Enaxon\Reqtide\Redis. It is installed as a separate package. See Shared state with Redis. The host creates the registry and the ACL users. The application only builds the environment.
RedisEnvironment
The shared-state counterpart of InMemoryEnvironment. It builds a RuntimeEnvironment whose policies live in one Redis registry, for exactly the policies the definition configures. Creating it opens no connection.
final class RedisEnvironment {
public static function for(
PipelineDefinition $definition,
OutcomeClassifier $classifier,
RedisConnectionOptions $connection,
RedisRegistry $registry,
?EventSink $events = null,
): RuntimeEnvironment;
public static function explain(
CompiledPipeline $pipeline,
RedisConnectionOptions $connection,
RedisRegistry $registry,
): PipelineExplanation;
}for()Returns the environment to pass toPipelineBuilder::compile(). The clock of every decision is the Redis server clock.explain()Adds the facts of the shared state to the usual explanation: state scope, I/O timeout, TLS, registry identity and ceilings. No I/O, and no host, password, prefix or secret in the output.
RedisRegistry
The immutable identity of one registry, the isolation unit of shared state: a key prefix, an identity, two ceilings and a secret of 32 bytes or more. The object creates nothing in Redis.
final readonly class RedisRegistry {
public const SCHEMA_VERSION = 1;
public const DEFAULT_MAX_PARTITIONS = 10000;
public const DEFAULT_MAX_RECORDS = 20000;
public function __construct(
public string $keyPrefix,
public string $identity,
private string $secret,
public int $maxPartitions = self::DEFAULT_MAX_PARTITIONS,
public int $maxRecords = self::DEFAULT_MAX_RECORDS,
);
}RedisRegistryAdmin
The only way to create, check or retire a registry. The host calls it with its administration connection. The application never does.
final class RedisRegistryAdmin {
public function __construct(
RedisConnectionOptions $administrativeConnection,
private readonly RedisRegistry $registry,
);
public function initialize(): RegistryStatus;
public function verify(): RegistryStatus;
public function retire(): RegistryStatus;
public function purgeRetired(): int;
public function inspectServer(): ServerPolicyReport;
}initialize()Creates the registry, or confirms an identical one. A different identity or different ceilings are refused. Refused on a server older than Redis 7.0 and on a prefix that still holds leftovers of a retired registry.verify()Checks presence, identity, schema and counters. Writes nothing.retire()Deletes the registry metadata. Partition and record keys stay untilpurgeRetired()removes them.purgeRetired()Removes the keys a retired registry left behind. Refuses to run while the registry exists. Returns the number of keys removed.inspectServer()Readsmaxmemory-policyandmaxmemory. NeedsCONFIG GETandINFO. Refuses a server older than Redis 7.0.
RedisConnectionOptions
Immutable options for one direct Redis connection. Both timeouts are required. There is no retry option and no way to turn off certificate or host name checks. The password never appears in var_dump or in an exception message.
final readonly class RedisConnectionOptions {
public function __construct(
public string $host,
public float $connectTimeoutSeconds,
public float $readWriteTimeoutSeconds,
public int $port = 6379,
public ?string $username = null,
private ?string $password = null,
public bool $tls = false,
public ?string $caFile = null,
public ?string $tlsServerName = null,
);
}RedisAcl
The Redis ACL rules the library needs, and no more. Print them and pass them to ACL SETUSER.
final class RedisAcl {
public static function execution(RedisRegistry $registry): array;
public static function administration(RedisRegistry $registry): array;
}execution()Rules for the application user: the shipped scripts and their commands, only on keys under the registry prefix.administration()The same rules plusSCAN,UNLINK,CONFIG GETandINFO, for the host.
RegistryStatus
What a registry administration call found or did: whether it created the registry, the authority watermark, and the counts against the ceilings.
final readonly class RegistryStatus {
public function __construct(
public bool $created,
public int $watermarkSeconds,
public int $watermarkMicroseconds,
public int $partitions,
public int $records,
public int $maxPartitions,
public int $maxRecords,
);
}ServerPolicyReport
What the server does when it runs out of memory. protectsState() is true for noeviction or no memory limit, false for a policy that can delete keys, and null when the policy could not be read.
final readonly class ServerPolicyReport {
public function __construct(
public ?string $evictionPolicy,
public ?int $maxMemoryBytes,
);
public function protectsState(): ?bool;
}Redis package: operate
Run these from a worker, a scheduler or an operator command, with a connection that has the administration rules. The library never calls them for you.
RedisRegistryMaintenance
Bounded cleanup of a registry. One step looks at no more than 128 candidates and no more than 128 finished records. Nothing runs it for you.
final class RedisRegistryMaintenance {
public function __construct(
RedisConnectionOptions $connection,
private readonly RedisRegistry $registry,
?RedisBridge $bridge = null,
);
public function step(): array;
}step()Runs one step and returns what it removed and whethermoremay be found. Call again untilmoreis false.
RedisCircuitAdmin
Operator actions on circuit breaker partitions. A reset is never called by the library.
final class RedisCircuitAdmin {
public function __construct(
RedisConnectionOptions $connection,
private readonly RedisRegistry $registry,
?RedisBridge $bridge = null,
);
public function reset(PolicyIdentity $identity): ?Generation;
}reset()Closes the circuit, discards its history and makes every active permit stale. Returns the new generation, or null when the partition does not exist.
Redis package: backends
One backend for each policy. RedisEnvironment::for() builds them for you. Create one yourself only to pass it to ReqtideClient::withBackends(). The third constructor argument is a seam for tests: leave it out. Each backend sends one server-side script call for each step, and a command is never sent twice.
RedisRateLimitBackend
Shared token bucket, fixed window and sliding counter.
final class RedisRateLimitBackend implements RateLimitBackend, StateBackend {
public function __construct(
RedisConnectionOptions $connection,
private readonly RedisRegistry $registry,
?RedisBridge $bridge = null,
);
public function capabilities(): StateBackendCapabilities;
public function consume(
RateLimitPolicy $policy,
PositiveInt $cost,
CommandId $command,
): AdmissionDecision;
public function inspect(
RateLimitPolicy $policy,
PositiveInt $requestedCost,
): RateLimitInspection;
}RedisCircuitBackend
Shared circuit breaker. Open and half-open state, active permits and count history are never reclaimed or evicted.
final class RedisCircuitBackend implements CircuitBackend, StateBackend {
public function __construct(
RedisConnectionOptions $connection,
private readonly RedisRegistry $registry,
?RedisBridge $bridge = null,
);
public function capabilities(): StateBackendCapabilities;
public function acquire(
CircuitPolicy $policy,
AttemptId $attempt,
PermitOwnerId $owner,
CommandId $command,
): CircuitAdmission;
public function record(
CircuitPolicy $policy,
CircuitPermit $permit,
CircuitVerdict $verdict,
CommandId $command,
): FinalizationResult;
public function abandon(
CircuitPolicy $policy,
CircuitPermit $permit,
CommandId $command,
): FinalizationResult;
}RedisConcurrencyBackend
Shared bulkhead with leases. Active owners are never evicted.
final class RedisConcurrencyBackend implements ConcurrencyBackend, StateBackend {
public function __construct(
RedisConnectionOptions $connection,
private readonly RedisRegistry $registry,
?RedisBridge $bridge = null,
);
public function capabilities(): StateBackendCapabilities;
public function acquire(
ConcurrencyPolicy $policy,
PermitOwnerId $owner,
CommandId $command,
): ConcurrencyAdmission;
public function release(
ConcurrencyPolicy $policy,
ConcurrencyPermit $permit,
CommandId $command,
): FinalizationResult;
}RedisRetryBudgetBackend
Shared retry budget. A balance is never reclaimed or evicted.
final class RedisRetryBudgetBackend implements RetryBudgetBackend, StateBackend {
public function __construct(
RedisConnectionOptions $connection,
private readonly RedisRegistry $registry,
?RedisBridge $bridge = null,
);
public function capabilities(): StateBackendCapabilities;
public function inspect(RetryBudgetPolicy $policy): RetryBudgetInspection;
public function reserve(
RetryBudgetPolicy $policy,
AttemptId $attempt,
PermitOwnerId $owner,
CommandId $command,
): RetryReservationDecision;
public function commit(
RetryBudgetPolicy $policy,
RetryBudgetReservation $reservation,
CommandId $command,
): RetryBudgetFinalizationResult;
public function release(
RetryBudgetPolicy $policy,
RetryBudgetReservation $reservation,
CommandId $command,
): RetryBudgetFinalizationResult;
}RedisCooldownBackend
Shared cooldown. An extension never shortens an existing pause.
final class RedisCooldownBackend implements CooldownBackend, StateBackend {
public function __construct(
RedisConnectionOptions $connection,
private readonly RedisRegistry $registry,
?RedisBridge $bridge = null,
);
public function capabilities(): StateBackendCapabilities;
public function inspect(CooldownPolicy $policy): CooldownDecision;
public function extend(
CooldownPolicy $policy,
Duration $extension,
CommandId $command,
): CooldownDecision;
}Guzzle package
These classes belong to the separate package enaxon/reqtide-guzzle, namespace Enaxon\Reqtide\Guzzle. It is installed as a separate package. See Guzzle requests with Reqtide. A 4xx or 5xx status is a response, never an exception.
ReqtideClient
The entry point. It owns its cURL handlers and runs each operation, with all its attempts, through one compiled pipeline.
final class ReqtideClient {
public static function inMemory(
PipelineBuilder $pipeline,
?GuzzleConfiguration $configuration = null,
?EventSink $events = null,
): self;
public static function withBackends(
PipelineBuilder $pipeline,
?RateLimitBackend $rateLimit = null,
?CircuitBackend $circuit = null,
?ConcurrencyBackend $concurrency = null,
?RetryBudgetBackend $retryBudget = null,
?CooldownBackend $cooldown = null,
?GuzzleConfiguration $configuration = null,
?EventSink $events = null,
): self;
public function uri(UriInterface|string $reference): UriInterface;
public function send(
HttpOperation $operation,
Duration $timeout,
?Cancellation $cancellation = null,
): ResponseInterface;
public function sendDetailed(
HttpOperation $operation,
Duration $timeout,
?Cancellation $cancellation = null,
): ExecutionCompletion;
public function sendAsync(
HttpOperation $operation,
Duration $timeout,
?Cancellation $cancellation = null,
): PromiseInterface;
public function explain(bool $async = false): PipelineExplanation;
}inMemory()A client with process-local state.withBackends()A client whose state lives in the backends you pass, for example the Redis backends. Pass a backend for every policy the pipeline configures.uri()Resolves a reference against the base URI (RFC 3986). The base URI is not a security boundary: an absolute reference replaces the host.send()Runs the operation and returns the response, or throws the failure of the operation. The timeout covers every attempt and every wait.sendDetailed()Returns anExecutionCompletionwith the summary of attempts and decisions instead of throwing.sendAsync()Returns a lazy promise. The operation runs when you callwait(), one operation at a time.explain()Describes the compiled pipeline of the synchronous runtime, or of the promise runtime with true. No transfer, no state change.
GuzzleConfiguration
The options of the client: the classifier, a short list of Guzzle client defaults and an optional base URI. Any other client default is refused by name.
final readonly class GuzzleConfiguration {
public const ALLOWED_CLIENT_DEFAULTS = array (
0 => 'headers',
1 => 'auth',
2 => 'proxy',
3 => 'verify',
4 => 'cert',
5 => 'ssl_key',
6 => 'crypto_method',
7 => 'force_ip_resolve',
8 => 'version',
9 => 'decode_content',
);
public HttpOutcomeClassifier $classifier;
public ?UriInterface $baseUri;
public function __construct(
?HttpOutcomeClassifier $classifier = null,
public array $clientDefaults = [],
UriInterface|string|null $baseUri = null,
public array $credentialHeaders = [],
);
}HttpOperation
One logical HTTP operation: an absolute request and your explicit statement of whether and how it may be sent again. Nothing is guessed from the method or a header.
final readonly class HttpOperation {
public const DEFAULT_MAX_VERIFIED_BODY_BYTES = 16777216;
public ?Closure $bodyFactory;
public function forClient(GuzzleConfiguration $configuration): self;
public static function read(
RequestInterface $request,
string $operationGroup,
Duration $validFor,
array $retryableOutcomeCodes = [],
?Closure $bodyFactory = null,
?Clock $clock = null,
?string $credentialKey = null,
BodyIntegrity $bodyIntegrity = BodyIntegrity::Verified,
int $maxVerifiedBodyBytes = self::DEFAULT_MAX_VERIFIED_BODY_BYTES,
): self;
public static function idempotentMutation(
RequestInterface $request,
string $operationGroup,
string $identityValue,
string $identityScope,
string $duplicateHandling,
Duration $validFor,
?Duration $retention = null,
?string $identityHeader = 'Idempotency-Key',
array $retryableOutcomeCodes = [],
?Closure $bodyFactory = null,
?Clock $clock = null,
?string $credentialKey = null,
BodyIntegrity $bodyIntegrity = BodyIntegrity::Verified,
int $maxVerifiedBodyBytes = self::DEFAULT_MAX_VERIFIED_BODY_BYTES,
): self;
public static function single(
RequestInterface $request,
string $operationGroup,
?string $credentialKey = null,
): self;
public function metadata(): OperationMetadata;
}read()For GET and HEAD. You state that repeating the request has no side effect, for how long, and which outcomes may be retried.idempotentMutation()For POST, PUT, PATCH or DELETE that the server deduplicates by an identity. The same body and the same identity are sent on every attempt, and the identity is checked before each one.single()Sent once, never repeated. Core refuses it in a pipeline that retries more than once.metadata()The bounded facts passed to the pipeline: the safety statement and the descriptor attributes.
HttpOutcomeClassifier
Decides which statuses and cURL errors are retried, count against a circuit, may fall back or set a cooldown. Every method returns a new instance.
final readonly class HttpOutcomeClassifier implements OutcomeClassifier {
public static function starter(?Clock $clock = null): self;
public static function empty(?Clock $clock = null): self;
public function withRetryableStatuses(int ...$statuses): self;
public function withCircuitFailureStatuses(int ...$statuses): self;
public function withCircuitNeutralStatuses(int ...$statuses): self;
public function withFallbackStatuses(int ...$statuses): self;
public function withRetryAfterStatuses(int ...$statuses): self;
public function withRetryableTransportErrnos(int ...$errnos): self;
public function retryableOutcomeCodes(): array;
public function classify(
Outcome $outcome,
OperationMetadata $metadata,
): OutcomeClassification;
}starter()A documented starting set. It is not a recommendation for every API.empty()Retries and falls back on nothing and reads noRetry-After.retryableOutcomeCodes()The reason codes that can be retried. Pass them toread()andidempotentMutation(). An empty list never retries.
RetryAfter
A strict reader of Retry-After: seconds or an HTTP date. A value that cannot be read gives no wait.
final class RetryAfter {
public const MAX_SECONDS = 9007199254;
public static function read(
array $lines,
DateTimeImmutable $now,
): RetryAfterReading;
}read()Reads the header lines against the given wall time. The greatest valid delay wins.
RetryAfterReading
What the Retry-After lines of one response say. A null delay means no lower bound, never zero.
final readonly class RetryAfterReading {
public function __construct(
public ?Duration $delay,
public int $validLines,
public int $rejectedLines,
public bool $outOfRange,
);
}Was this page helpful?