Laravel Package Doctor
Composer audit tool that scores Laravel dependencies for security, abandonment, compatibility, and upgrade risk — and turns the result into a single actionable report instead of a wall of version numbers.
Problem
Before upgrading a Laravel application — or when inheriting one — you need answers that Composer does not give you. composer outdated lists version numbers. composer audit flags known CVEs. Neither tells you whether a package is still actively maintained, whether it has declared support for the Laravel version you are targeting, or whether your composer.json constraint is blocking the latest release.
The questions that actually matter before an upgrade are:
- Is this dependency still maintained, or abandoned on Packagist?
- Is the GitHub repository archived?
- Does it explicitly support the Laravel version I am moving to?
- Is the latest version blocked by my current version constraint?
- Is a major breaking upgrade sitting there that I need to review before running
composer update? - Which packages have not had a release in 18 months?
- Are any licenses in the tree incompatible with how the project is distributed?
Answering those questions manually means running four or five separate commands, cross-referencing Packagist pages, checking GitHub activity, and building a picture in your head. On a project with 40+ dependencies that is slow and error-prone.
Constraints
- Single Artisan command.
php artisan package:doctormust be the entire interaction for the common case. No config required to get started. - Auto-discovered service provider. Laravel registers it. The developer does not touch a config file to install.
- Works offline. Restricted CI environments or air-gapped machines should still get partial results.
--offlineskips Packagist and GitHub but still runscomposer outdated,composer audit, and reads the lock file. - Deterministic CI exit codes.
0= all clear,1= risky packages found,2= critical packages found,3= runtime error. Scripts and pipelines can gate on these without parsing the text output. - Never modifies the project. The tool reads; it does not write. It does not run
composer update, does not touchcomposer.json, and does not install or remove anything.
Key decisions
Combine multiple data sources into one decision layer. The tool runs composer outdated (version data), composer audit (security advisories), and composer licenses (license metadata), then enriches each result with Packagist API data (abandonment status, download counts) and GitHub API data (archived status, latest release date). This composition is the core value. Each source alone answers one question; combined they answer all of them.
0–100 weighted health score per package. Each package starts at 100. Weighted deductions apply per detected issue — security advisory costs 30 points, abandonment costs 30, archive costs 25, Laravel incompatibility costs 20, and so on down to minor concerns like missing documentation. Score is clamped to [0, 100]. The score reduces upgrade decisions to a single number that can be gated in CI.
Four-status classification. Healthy (90–100), Watch (70–89), Risky (40–69), Critical (0–39). Status is derived directly from score, not from separate heuristics. This keeps the model predictable and the scoring configurable.
Cache metadata to limit API calls. Packagist and GitHub responses are cached. Repeated runs on the same project do not re-fetch unchanged metadata. This keeps the tool practical for frequent use in CI and during active development.
Graceful degradation under GitHub rate limits. Large projects with many packages can exhaust GitHub’s unauthenticated API limit. When a rate limit is detected, Package Doctor skips remaining GitHub calls for the run, continues using cached metadata where available, and emits a single warning. A PACKAGE_DOCTOR_GITHUB_TOKEN env var raises the limit without requiring changes to the tool invocation.
Flags for different workflows. --direct scans only direct dependencies — the right scope before a Laravel upgrade. --ci enables exit codes without interactive output. --json emits machine-readable output. --offline skips external calls. --score-below=70 filters the report to packages that actually need attention.
Configurable scoring. vendor:publish --tag=package-doctor-config exposes thresholds, CI gates, scan scope, GitHub token, and per-package ignores. The defaults cover the common case without configuration.
Example usage
# Install
composer require --dev satheez/laravel-package-doctor
# Full dependency health scan
php artisan package:doctor
# Direct dependencies only — useful before a Laravel upgrade
php artisan package:doctor --direct
# Gate a CI pipeline on production packages
php artisan package:doctor --no-dev --ci
# Machine-readable output for dashboards or artifact storage
php artisan package:doctor --json --ci
# Focus on packages that need attention
php artisan package:doctor --score-below=70
Example report row for a problematic package:
vendor/abandoned-auth
Current: 0.9.1
Latest: 0.9.1
Score: 14 / 100
Status: Critical
Issues:
↳ [abandoned] Package is marked as abandoned on Packagist.
↳ [no_release_18_months] No release in over 18 months.
↳ [laravel_incompatible] Does not declare support for current Laravel version.
Recommendation: Replace this package before upgrading Laravel.
Outcome
A single command gives a complete health picture of a Laravel project’s dependencies — scored, classified, and prioritised. Before a major Laravel upgrade this surfaces every package that needs manual review before composer update runs. In CI it gates production deploys on dependency health without requiring additional scripting. For an inherited project it produces an immediate triage list instead of hours of manual Packagist and GitHub tab-switching.
What I’d do differently
The scoring model’s weights (e.g., deducting 30 points for abandonment, 25 for archived repositories) are currently hardcoded, which works well for a standard baseline but does not fit every team’s risk tolerance. In the next major version, I would move these scoring weights into the published configuration file to let teams customize their risk thresholds. I would also design a plugin-based analyzer architecture, allowing teams to register custom checks—such as checking for deprecated functions or verifying internal compliance rules—without modifying the core package.