zendframework/zenddiagnostics
ZendDiagnostics provides a library of ready-made health checks for PHP applications—verify database connections, filesystem permissions, disk space, PHP settings, and more. Use it standalone or with Zend Framework to monitor app health and catch issues early.
zendframework/zenddiagnostics is a library, not a health-check server. You compose Check objects into a Runner, run them, and decide what to do with the Result objects. That makes it a good fit for deploy smoke tests, CLI diagnostics, and a /health endpoint.
Install it (noting it is archived — see Gotchas):
composer require zendframework/zenddiagnostics
Minimal standalone script:
<?php
require 'vendor/autoload.php';
use ZendDiagnostics\Check;
use ZendDiagnostics\Runner\Runner;
$runner = new Runner();
$runner->addCheck(new Check\PhpVersion('7.4.0'));
$runner->addCheck(new Check\ExtensionLoaded('pdo'), 'pdo-loaded');
$runner->addCheck(new Check\ExtensionLoaded('mbstring'), 'mbstring-loaded');
$runner->addCheck(new Check\DirWritable(sys_get_temp_dir()), 'tmp-writable');
$runner->addCheck(new Check\DiskFree(200 * 1024 * 1024, '/'), 'disk-free');
$runner->addCheck(new Check\HttpService('https://example.com/health'), 'upstream');
$results = $runner->run();
foreach ($results as $label => $result) {
printf("[%s] %s: %s\n", get_class($result), $label, $result->getMessage());
}
exit($results->getFailureCount() > 0 ? 1 : 0);
Where to look first:
src/Check/ — the inventory of ready-made checks (PhpVersion, ExtensionLoaded, DiskFree, DirWritable, HttpService, Callback, ClassExists, StreamWrapper, DoctrineConnection, Redis, Memcached, etc.).ZendDiagnostics\Runner\Runner — addCheck(), addReporter(), run(), getResults().ZendDiagnostics\Result\* — Success, Warning, Failure, Skip, and their getMessage().ZendDiagnostics\Runner\Reporter\* — console reporters if you want ready-made CLI output.First use case: a post-deploy CLI smoke test that exits non-zero on failure.
Deploy/CI gate. Build a bin/diagnostics.php script, add checks for required extensions, writable cache/temp directories, database connectivity, and upstream HTTP services, then call it from your deploy pipeline. Use getFailureCount() for the exit code; treat warnings as alerts rather than deploy blockers.
HTTP health endpoint. The package gives you checks and results, not routing. Wrap the runner in a controller/handler and map severity to HTTP status:
$runner = new Runner();
$runner->addCheck(new Check\Callback(fn () => $pdo->query('SELECT 1') !== false), 'database');
$runner->addCheck(new Check\Callback(fn () => $redis->ping()), 'redis');
$runner->addCheck(new Check\DiskFree(500 * 1024 * 1024, '/'), 'disk');
$results = $runner->run();
$httpCode = 200;
$checks = [];
foreach ($results as $label => $result) {
$status = 'ok';
if ($result instanceof \ZendDiagnostics\Result\Warning) {
$status = 'warning';
} elseif ($result instanceof \ZendDiagnostics\Result\Failure) {
$status = 'failure';
$httpCode = 503;
} elseif ($result instanceof \ZendDiagnostics\Result\Skip) {
$status = 'skipped';
}
$checks[] = [
'check' => $label,
'status' => $status,
'message' => $result->getMessage(),
];
}
http_response_code($httpCode);
header('Content-Type: application/json');
echo json_encode([
'status' => $httpCode === 200 ? 'ok' : 'fail',
'checks' => $checks,
]);
Custom checks. Implement CheckInterface::check() and return a result object. This is the main extension point for application-specific probes (queue backlog, search cluster, feature flag service):
use ZendDiagnostics\Check\CheckInterface;
use ZendDiagnostics\Result\Failure;
use ZendDiagnostics\Result\Success;
final class QueueBacklogCheck implements CheckInterface
{
private $queue;
public function __construct($queue)
{
$this->queue = $queue;
}
public function check()
{
try {
$size = $this->queue->size();
return $size < 1000
? new Success("Queue backlog: {$size}")
: new Failure("Queue backlog too high: {$size}");
} catch (\Throwable $e) {
return new Failure('Queue check failed: ' . $e->getMessage());
}
}
}
Callback checks. For quick probes, Check\Callback avoids writing a class. Return true/false or a ResultInterface for custom messages. Keep the callable small and make sure it cannot throw uncaught exceptions.
Reporters. Instead of ad-hoc printing, implement the runner's reporter interface (start/progress/finish hooks) to stream results to Slack, Prometheus, or structured logs. For simple CLI scripts, the bundled console reporter works if zend-console is available; otherwise a few printf calls are usually enough.
Grouping. Tag checks with the third argument to addCheck($check, $label, ['group']) so you can separate “liveness” checks (fast, no dependencies) from “readiness” checks (DB, cache, upstreams). If your runner version does not filter groups directly, filter the check array before adding them.
Framework integration. In ZF2/ZF3/Mezzio, register the Runner as a service, inject checks through a factory or config, and expose a single controller/handler action. In Symfony, a console command or a custom controller around the runner is straightforward; historically this library was also used through monitor bundles that accept ZendDiagnostics-style checks.
Testing custom checks. Unit-test checks directly by invoking check() and asserting the result class and message. Stub the runner with fake checks when testing endpoint wiring; do not hit real databases or HTTP services in unit tests.
laminas/laminas-diagnostics (largely the same API with the Laminas\Diagnostics namespace). Migration is mostly use statement changes plus Composer replacement.composer.json PHP constraint before adopting it. On PHP 8, prefer the Laminas fork or restrict yourself to checks that do not depend on removed legacy extensions.Memcache, Memcached, Mongo, Redis, and DoctrineConnection checks require their respective extensions/classes. Do not register them unconditionally; guard with ClassExists or build them only when the service is configured.Runner per request/run. Reusing one across runs can leave previous results in the collection.Failure results or bubble up. The runner has configuration for exception handling and fail-fast behavior; set break_on_failure to false for health endpoints so you get a complete picture, and true for deploy gates where you want to stop early.DiskFree takes a byte count; DiskUsage semantics have varied by version (often a percentage). Read the check source before setting thresholds, and always pass integers.HttpService needs stream wrappers. It typically relies on allow_url_fopen/stream contexts rather than cURL. Set a timeout, avoid checking public endpoints from production, and do not let a slow upstream block your health endpoint.SecurityAdvisory and remote HTTP checks add latency and flakiness. Prefer CLI/cron for those, or cache their results./health by network policy or auth.DirWritable against application directories often fails in read-only container images. Use /tmp or a dedicated writable volume.ExtensionLoaded relies on extension_loaded() names; verify names like opcache/Zend OPcache, pdo_mysql, and apcu on the target runtime rather than guessing.getWarningCount() separately from getFailureCount() so a degraded dependency does not take down your load balancer health check.How can I help you explore Laravel packages today?