laravel/doctor
Laravel Doctor diagnoses common configuration, environment, and infrastructure problems in your Laravel application. Run php artisan doctor to see each failing check, then apply safe, deterministic fixes with --fix or follow the remediation steps for issues that can't be repaired automatically.
Architecture fit. laravel/doctor is a first-party, dev-oriented diagnostic runner that follows Laravel's own packaging conventions: a service provider registers an Artisan command (doctor), the work is decomposed into single-responsibility Diagnostic classes, and results are modelled as first-class objects (DiagnosticResult, FixResult, Message, Link) rather than console strings. That is the important architectural property for a TPM: the unit of extension is a class with a declared name/group, a check() returning a typed result, and an optional fix() behind the Fixable contract. It slots into the app the same way Artisan commands and config publishing already do — no queues, no daemons, no schema, no external service. Results carry stable machine-readable codes (application-key-is-set.missing), which means output is consumable by CI and by agent tooling without screen-scraping.
Integration feasibility. Very high. Installation is composer require laravel/doctor --dev, the default suite runs with zero configuration, and everything else is opt-in: selector flags (--only/--except by class, group, package, or wildcard), a published config file for persistent allowlists, and an environments map that resolves custom environment names onto local or production mode. Exit-code semantics are explicit and documented (pass/notice/skip don't fail; fail/error do; warn only with --fail-on=warn), which is exactly the contract a pipeline needs. --bail and the programmatic Doctor::bail()->run() give us both shell and PHP embedding options.
Technical risk. The dominant risk is maturity, not design: 111 stars, 0 dependents, and a latest release dated 2026-07-28 means almost no field time or third-party validation. The Laravel organisation is a strong signal on stewardship, but there is no observed ecosystem usage to cite as evidence of stability. Second risk is the dev-dependency posture colliding with the product's best feature — production-mode diagnostics are most valuable in staging and production, where composer install --no-dev would omit the package. Third, --fix mutates the application (writes .env, generates APP_KEY, creates symlinks, repairs storage permissions); the package mitigates this well (fixes only on explicitly ->fixable() failures, EnvironmentMode::Local gating, no prompting under --fix, --fix rejected for JSON/GitHub formats), but the blast radius is real and must be governed by policy, not left to developer discretion. Fourth, the deliberate "unlisted environment = production" default will generate strict-expectation noise in any org with bespoke APP_ENV values until the config map is written. Finally, an uncaught exception inside a diagnostic yields error, which fails the run — good for correctness, but a single brittle custom diagnostic can red-line a whole pipeline.
Key questions.
Diagnostic, Fixable, message registry, result codes) treated as stable, and what's the Laravel/PHP support matrix?composer audit inside the default suite require network egress, and how does that behave in air-gapped CI?--dev?Stack fit. Any Laravel application on a supported PHP version that already runs Artisan in CI can adopt this without touching application code, container images, or infrastructure. It is a read-only inspection tool by default and an explicitly-invoked repair tool secondarily, so it fits the "developer machine + CI + staging shell" triad rather than the request path. It does not replace PHPUnit/Pest, Pint, or Larastan — it occupies the previously empty slot of environment/configuration/infrastructure correctness.
Migration path. Incremental and reversible at every step:
--dev, run php artisan doctor locally and in CI in report-only mode (--fail-on=never). Capture output as a baseline; classify findings into genuine defects versus expected-state noise.APP_ENV value onto local or production. This is the single highest-value configuration step and the one most likely to be skipped.only/except allowlist to encode the org's opinionated baseline: exclude diagnostics that don't apply to our topology, keep security and storage groups mandatory.--fail-on=warn only for groups where we've confirmed zero baseline noise. Keep --fix out of CI entirely; permit it only on local developer machines.make:diagnostic for org-specific invariants (required env vars, mandated queue driver, presence of a health endpoint, expected cache prefix), with fixes gated to EnvironmentMode::Local and messages written into the messages() registry with remediation text and docs links.Compatibility. Verify the package's Laravel and PHP constraints against the oldest app in the estate before committing — a fleet usually spans two or three Laravel majors, and a single-version package becomes a template that only half the fleet can use. Check for overlapping diagnostics with existing tooling so we don't double-report the same class of issue from two systems with different severities. The --only/--except package-wildcard selectors (laravel/*) mean third-party diagnostics from other packages can be adopted or muted through the same mechanism — worth designing the config with that in mind from the start.
Sequencing rationale. Report-only before enforcing, environment mapping before scoping, scoping before custom diagnostics. Each step is independently valuable and independently revertible; nothing requires a code change to the host application.
Maintenance. Low and bounded. The host application gains one dev dependency and one published config file; there is no runtime component, no migration, no scheduled job. Cost is concentrated in two places we own: the environment-mode map (changes whenever we add an environment name) and our custom diagnostics (which are application code and need owners, tests, and review). Version pinning plus changelog review per upgrade is the appropriate discipline — this is a young package whose diagnostic inventory and result semantics can reasonably be expected to evolve.
Support. MIT, community-supported, delivered through Laravel's normal channels. There is no vendor SLA, no security advisory process we can contractually rely on, and — with zero dependents — no crowd-sourced signal that breakage will be caught by anyone but us. Mitigation: an internal named owner, a pinned version, and a smoke run of the diagnostic suite as part of the upgrade checklist so a behaviour change surfaces before it hits a release pipeline.
Scaling. Effectively free per application: it's a short-lived CLI process bounded by the number of diagnostics and the latency of the drivers it pings (database, cache, Redis, queue). Cost scales linearly with repo count and appears as CI minutes, not infrastructure. At fleet scale, the interesting move is aggregating the JSON output across applications into a single posture view — configuration and security drift across dozens of Laravel services is otherwise invisible until an incident. That aggregation should wait until the per-repo baseline is stable.
Failure modes to plan for.
production, so staging-like environments get production strictness. Symptom: warnings about debug mode, sync queues, or uncached bootstrap on boxes that legitimately shouldn't care. Fix: the environments map, reviewed whenever a new environment is provisioned.error status is a hard failure; one brittle custom diagnostic can block every pipeline that includes it. Custom diagnostics need try/catch discipline and, ideally, --except escape hatches documented in the runbook.--fix applied in the wrong context. Mitigated by mode-gated fixability and by --fix being rejected for machine-readable formats, but the operator-level risk of running --fix on a production host remains a policy problem, not a technical one. Ban it outside local machines in writing.require in pre-production environments.--fail-on=warn across the full default suite before the baseline is clean is the most likely way to get the tool switched off. Enforce group by group.Ramp-up. Near-zero for developers using the command — the output is human-readable, the prompts are confirmations, and remediation text is embedded. Custom diagnostic authoring is the only real learning curve: roughly half a day for an engineer comfortable with Artisan commands to internalise the messages() registry, status-to-exit-code mapping, fix gating, and result codes. Budget a short internal convention note (when to warn versus fail, when a fix is appropriate, where docs links go, naming for result codes) — the package supplies the mechanism but deliberately leaves house style to us, and that style is what determines whether findings get acted on or muted.
How can I help you explore Laravel packages today?