pierre-arthur/sf-doctor
Symfony Doctor is a diagnostic CLI for Symfony applications. It scans configuration, dependencies, and environment settings to flag common issues, misconfigurations, and health risks, helping developers troubleshoot faster and keep projects running smoothly.
This is a Symfony audit tool, not a Laravel one — so the first step is deciding whether it applies to you at all. It audits a Symfony application's structure, configuration and conventions; a stock Laravel app has none of those artifacts. Reach for it when you own a Symfony-based package/service, when you're evaluating a Symfony dependency before adopting it, or when a Laravel app leans heavily on Symfony components (console, http-foundation, process, finder) and you want a second opinion on how those pieces are wired.
Minimal setup:
composer require --dev pierre-arthur/sf-doctor
ls vendor/bin/ # confirm the actual binary name
vendor/bin/<binary> --help
Read the --help output before the README — with a package this small (11 stars, 0 dependents) the help text is the most reliable description of what it actually checks. Then run it once against a throwaway Symfony skeleton (Symfony 6/7, symfony/skeleton + a bundle or two) to learn what a clean report looks like. Without that baseline you can't tell a real finding from a check that simply doesn't apply to your codebase.
First real use case: point it at the smallest Symfony-flavoured thing you own — a shared package, a console-heavy internal tool, an older service — not at your main Laravel monolith. The goal of that first run is to answer "does this produce signal or noise for me?" in ten minutes rather than a day.
For the Laravel-side equivalents you probably want in the same pipeline: Larastan/PHPStan for static analysis, Pint for style, Rector for upgrades, and Enlightn for the security/performance checks. This package does not replace any of them.
Triage run, not a gate. Add it as a Composer script and run it manually or weekly, not on every PR:
"scripts": {
"audit:symfony": "sf-doctor --no-interaction || true"
}
Treat output as a backlog of candidate issues, then convert each confirmed one into a PHPStan rule, a Rector rule, or a test — something that enforces the fix continuously. An audit tool you re-run is worth far less than a rule the CI enforces.
CI as a non-blocking job. Start with continue-on-error: true and archive the report as an artifact. Only flip it to blocking once you've confirmed every emitted finding is actionable for your codebase; otherwise you train the team to ignore red builds.
Scoped invocation. Run it per-package in a monorepo (cd packages/foo && ../../vendor/bin/sf-doctor) rather than at the root, so Symfony-specific checks aren't applied to directories that have nothing to do with Symfony.
Pair with a real static analyser. Run it alongside Larastan in the same job, different steps. The audit catches architectural/convention issues that analysis misses; Larastan catches type errors the audit will never see. Neither is a superset.
Dependency evaluation. If you're considering pulling in a Symfony bundle, run the audit against its source before adopting. It's a cheap, fast filter on a package you haven't committed to.
Baseline first, diff later. Capture the initial report in the repo (e.g. docs/audit-baseline.txt), then diff subsequent runs against it. On a legacy codebase the absolute count is meaningless; the trend is what you act on.
Expect hard failures on Laravel skeletons, not graceful skips. The tool almost certainly expects a Symfony Kernel, config/bundles.php, or a bundle-aware container. On a Laravel app it will likely error out or produce a report dominated by "not applicable" entries. That's not a bug — it's the wrong tool for that input. Have a clear answer for why you're running it before you add it to a pipeline.
Version resolution is the real risk. With zero dependents and a single maintainer, you're an early adopter: pin the exact version in composer.json and commit composer.lock. A Symfony/PHP constraint conflict with your locked illuminate/* versions is plausible — install into a separate tooling directory or a Docker image if Composer refuses to resolve alongside your app.
Convention false positives. Symfony and Laravel differ on container wiring, facades vs services, Doctrine vs Eloquent, and directory layout. Any finding rooted in those conventions is noise for a Laravel codebase. Read the rule, not just the message, before filing a ticket.
Don't let it drive automated edits. This class of tool is read-only by design; if it ever suggests an autofix, review the diff by hand. On a low-star package, an over-eager rewrite of config or service definitions is not something you want applied unreviewed.
Debugging: re-run with maximum verbosity (-vvv where supported) and check the exit code independently of the printed output — tools in this space often exit non-zero on "findings present" rather than on error, which will break naive CI steps. If it writes a cache directory, clear it between runs when comparing reports.
Extension points are your fallback. If the tool supports custom checks/rules, that's where you encode your own conventions (queue usage, container binding style) rather than fighting the defaults. If it doesn't, the audit's real value is as a checklist you read once — don't build a workflow around a package you'd have to fork to extend.
Watch the release cadence. Given the version history and adoption level, budget for the package going quiet. Keep the audit advisory, never load-bearing: nothing in your pipeline should fail only because this tool stopped being maintained.
How can I help you explore Laravel packages today?