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-onlyNotYetLoaded.ApplicationHealthReport: The aggregated result ofcheckProvidersHealth()— 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
| Property | Type | Description |
|---|---|---|
$status | HealthStatus | The reported state. |
$reason | ?string | Optional human-readable explanation (e.g. an error message). |
A provider's own checkHealth() implementation should only ever return Healthy, Degraded, or Unhealthy — NotYetLoaded 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
HealthCheckableInterfaceis excluded from$providersentirely — it never affects the overall status. - A deferred provider that has not yet been loaded is reported as
HealthStatus::NotYetLoadedwithout calling itscheckHealth()— 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 viaget(), or it's warmed vialoadDeferredProviders()), a latercheckProvidersHealth()call reports its real status. - The overall status is:
Unhealthyif any provider reportsUnhealthy,- else
Degradedif any reportsDegraded, - else
Healthy— including when$providersis empty (nothing registered implements the interface).
ApplicationHealthReport::isHealthy(): boolis 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), reservingunhealthy()for a resource that's actually unusable. - Selective Health Checks: Only implement
HealthCheckableInterfaceon 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()returnstrue.