Skip to main content

Provider Health & Readiness

Overview

A service provider that owns an external resource — a database connection, a queue client, a cache backend — may optionally report its own health/readiness state. Application::checkProvidersHealth() aggregates every registered provider that opts in into a single ApplicationHealthReport, ready to wire up to an HTTP readiness or liveness endpoint.

This feature is entirely opt-in: a provider that doesn't implement the contract below is simply excluded from the report — never treated as unhealthy.


Key Concepts

  • HealthCheckableInterface: The opt-in contract a provider implements to report its own health.
  • HealthCheckResult: An immutable value object describing one provider's status and an optional human-readable reason.
  • HealthStatus: The enum of possible states — Healthy, Degraded, Unhealthy, and the report-only NotYetLoaded.
  • ApplicationHealthReport: The aggregated result of checkProvidersHealth() — an overall status plus a per-provider breakdown.

HealthCheckableInterface

Signature

interface HealthCheckableInterface
{
public function checkHealth(): HealthCheckResult;
}

Purpose Implement this on a service provider to report its current health/readiness. checkHealth() is only ever called for a provider that has already been registered — eagerly, or deferred and already loaded. It is never called for a deferred provider that hasn't been loaded yet (see NotYetLoaded below).

Usage Example

class DatabaseServiceProvider extends AbstractServiceProvider implements HealthCheckableInterface
{
private ?PDO $connection = null;

public function register(Application $app): void
{
$app->bind('db.connection', function () {
return $this->connection ??= new PDO(/* ... */);
});
}

public function checkHealth(): HealthCheckResult
{
try {
$this->connection?->query('SELECT 1');

return HealthCheckResult::healthy();
} catch (\Throwable $e) {
return HealthCheckResult::unhealthy($e->getMessage());
}
}
}

HealthCheckResult

An immutable status + optional reason, created via one of three named constructors:

HealthCheckResult::healthy(?string $reason = null): HealthCheckResult
HealthCheckResult::degraded(?string $reason = null): HealthCheckResult
HealthCheckResult::unhealthy(?string $reason = null): HealthCheckResult
PropertyTypeDescription
$statusHealthStatusThe reported state.
$reason?stringOptional human-readable explanation (e.g. an error message).

A provider's own checkHealth() implementation should only ever return Healthy, Degraded, or UnhealthyNotYetLoaded is reserved for checkProvidersHealth()'s own aggregation.


Application::checkProvidersHealth()

Signature

public function checkProvidersHealth(): ApplicationHealthReport

Purpose Aggregates every registered provider implementing HealthCheckableInterface into a single ApplicationHealthReport { HealthStatus $overallStatus, array<string, HealthCheckResult> $providers }.

Rules

  • A provider that does not implement HealthCheckableInterface is excluded from $providers entirely — it never affects the overall status.
  • A deferred provider that has not yet been loaded is reported as HealthStatus::NotYetLoaded without calling its checkHealth() — loading it just to check its health would defeat the point of deferring it. This status is deliberately excluded from the overall-status aggregation, so a provider that may simply never be needed doesn't make the report read as degraded or unhealthy. Once such a provider is loaded (its first provided identifier is requested via get(), or it's warmed via loadDeferredProviders()), a later checkProvidersHealth() call reports its real status.
  • The overall status is:
    • Unhealthy if any provider reports Unhealthy,
    • else Degraded if any reports Degraded,
    • else Healthy — including when $providers is empty (nothing registered implements the interface).
  • ApplicationHealthReport::isHealthy(): bool is a shorthand for $overallStatus === HealthStatus::Healthy.

NotYetLoaded deferred providers

$app->registerProvider(new QueueServiceProvider()); // deferred, not yet loaded

$report = $app->checkProvidersHealth();
$report->providers[QueueServiceProvider::class]->status; // HealthStatus::NotYetLoaded
$report->isHealthy(); // true — NotYetLoaded never degrades the overall status

Wiring to an HTTP readiness endpoint

$app->get('/healthz', function () use ($app) {
$report = $app->checkProvidersHealth();

return new JsonResponse(
['status' => $report->overallStatus->value, 'providers' => $report->providers],
$report->isHealthy() ? 200 : 503
);
});

Extensibility

  • Degraded vs. Unhealthy: Use degraded() for a provider that still works but isn't fully healthy (e.g. a replica lagging behind), reserving unhealthy() for a resource that's actually unusable.
  • Selective Health Checks: Only implement HealthCheckableInterface on providers that genuinely own an external resource — providers with nothing meaningful to report are correctly excluded by simply not implementing it.
  • Combining with Middleware: Pair a readiness endpoint with Middleware to gate traffic until checkProvidersHealth()->isHealthy() returns true.