Fix false passes and stale results in Doctor diagnostics
Summary
Fixes four Doctor diagnostics that could report a broken application as healthy, report stale results, or fail without any details.
Purpose
Doctor is meant to catch broken infrastructure and configuration before they cause runtime failures. These fixes make the Redis, configuration, and Composer diagnostics match how the framework behaves at runtime and in long-lived processes. The beneficiaries are developers and CI pipelines that rely on php artisan doctor, and applications that run Doctor programmatically or on a schedule.
Problem
- Redis probes ignore failed PING replies. When Redis answers
PINGwith anERRreply, such asmax number of clients reached, phpredis returnsfalseinstead of throwing. The Redis, queue, and session reachability diagnostics only treated exceptions as failures. They passed exactly when the server was rejecting clients. - Blank default drivers pass the configuration diagnostic.
RequiredConfigurationValuesAreSetsubstituted hardcoded drivers (log,database,local,null) whenmail.default,queue.default,filesystems.default, orbroadcasting.defaultwas blank or null. The managers read these keys with typed getters, so at runtime the same value throws, for exampleMailer [] is not defined.A blank environment value such asMAIL_MAILER=therefore passed Doctor and failed in production. It also violated the rule that required settings must not have code-level fallbacks. composer.jsonis read once per worker.ComposerJsonmemoized the decoded manifest, but the container auto-singletons the class. In a long-lived process, the PHP version and extension diagnostics kept using the first read aftercomposer.jsonchanged. Scheduleddoctorcommands run inside the scheduler process, andDoctor::run()runs inside workers, so both were affected.- Composer failures could have empty details. When Composer exited unsuccessfully without writing to stdout or stderr, for example after being killed, the autoload check and fix reported an empty details string.
Architecture and Implementation
RedisConnectionsAreReachable,QueueConnectionIsReachable, andSessionDriverIsReachabletreatping() === falseas a failure. The Redis diagnostic records it as a failed connection. The queue and session probes throw aRuntimeException, which their existing error handling reports. This matchesRedisConnection::pingForHeartbeat().RequiredConfigurationValuesAreSetaddsmissingDefaultValues(). It reports blank, null, or non-stringbroadcasting.default,filesystems.default,mail.default, andqueue.defaultvalues as missing. The driver-specific checks for these settings no longer substitute a default and are skipped when their default is unusable.logging.defaultis handled differently.LogManagerreads it as nullable and falls back to its emergency logger, so a blank value is not reported. The diagnostic simply stops assuming thestackchannel.ComposerJson::contents()decodescomposer.jsonon every call and holds no state. The class stays auto-singletoned, but a shared instance no longer causes stale results.ComposerAutoloadIsValidbuilds failure details for bothcheck()andfix()with the existingDetails::processOutput()helper. When there is no output, it falls back toComposer exited with code N without output.
No public APIs, configuration keys, or command options change.
Design Decisions and Trade-offs
- Generic Redis failure message. The Redis failure message does not include Redis's error text. Reading
getLastError()reliably would require running the probe throughRedis::withConnection()so the same pooled connection is used. The diagnostic result is correct without it. - Blank defaults are reported, not resolved. The configuration diagnostic reports blank defaults instead of guessing the intended driver. Omitted or misspelled keys are unaffected: framework configuration merging still provides the shipped defaults.
composer.jsonis re-read. Reading the file on each call costs a few small file reads per Doctor run. Removing the memoized state was preferred to marking the classTransient.- Divergence from upstream. These diagnostics are ported from
laravel/doctor. The Redis, configuration, and Composer details changes diverge from upstream code that has the same behavior, which adds manual merge work when syncing upstream changes.
Changes
- Redis reachability
- Report connections whose
PINGreturnsfalsein the Redis, queue, and session diagnostics. - Add one regression test per diagnostic.
- Report connections whose
- Required configuration values
- Report blank or null default broadcasting, filesystem, mail, and queue settings as missing.
- Stop assuming a
stacklog channel whenlogging.defaultis blank. - Add regression tests for each default key and for the logging behavior.
- Composer manifest
- Read
composer.jsonon eachComposerJsoncall. - Add a regression test that rechecks root PHP requirements after the manifest changes.
- Read
- Composer autoload
- Use
Details::processOutput()with an exit-code fallback for check and fix failures. - Add a regression test for failures without output.
- Use
Validation
composer test:parallel -- --processes=4 tests/Doctor: 458 tests passed, 1 skipped. The skip is an existing environment-dependent requirement on the phpredis version or OPcache.vendor/bin/phpstan analyse --memory-limit=4G: no errors.composer analyseexhausted the default 128M PHP memory limit locally, so the main PHPStan configuration was run directly with a higher limit.php-cs-fixerwith the repository configuration on the changed files: no changes required.- The new regression tests were also run against the pre-change Doctor sources and failed.
- Manual reproductions:
- phpredis 6.3.0 returned
falsefromPINGagainst a local RESP server that replied-ERR max number of clients reached. - Blank
mail.default,queue.default,filesystems.default, andbroadcasting.defaultvalues each threw at runtime while the old diagnostic passed.
- phpredis 6.3.0 returned
Not run: the full components test suite, the phpstan.types.neon.dist and skeleton analysis steps, and an end-to-end check against a Redis server that has actually reached maxclients.
Risks and Review Notes
- The configuration diagnostic now fails for applications whose broadcasting, filesystem, mail, or queue default is blank or null. Those applications already fail at runtime when they use that service.
RequiredConfigurationValuesAreSetstill falls back to the connection name when a connection'sdriverkey is missing, for exampleConfigured::string("broadcasting.connections.{$connection}.driver", $connection). Whether this also hides runtime failures has not been verified, and it is not changed here.- No other material risks have been identified based on the available evidence.
Related Information
- Upstream
laravel/doctorhas the same behavior for the Redis probes, the default-driver fallbacks, and empty Composer failure details.
[!NOTE]
Fix false passes and stale results in Doctor diagnostics
RequiredConfigurationValuesAreSet now reports blank or missing default-driver values (broadcasting, filesystems, logging, mail, queue) instead of assuming a fallback driver and checking its configuration
- Redis PING probes in
QueueConnectionIsReachable, RedisConnectionsAreReachable, and SessionDriverIsReachable now treat a false PING result as a failure instead of continuing
ComposerAutoloadIsValid check and fix paths use a shared failureDetails helper with an exit-code fallback message when Composer produces no output
ComposerJson.contents no longer caches decoded composer.json, so repeated checks see current file contents (see ComposerJson.php)
- Behavioral Change: long-running doctor workers now re-read composer.json on each diagnostic run, and setups with unset default drivers that previously passed the required-configuration check will now fail it
Macroscope summarized 5156e47.
Summary by CodeRabbit
- Bug Fixes
- Redis connectivity checks now report a failure when a server rejects a ping, including the affected connection.
- Configuration diagnostics now flag missing default broadcasting, filesystem, mail, and queue settings instead of assuming a fallback driver.
- Composer autoload failures now provide a clear exit-code message when the process produces no output.
- PHP requirement checks now reflect changes made to the project’s Composer configuration.