← All writing

Laravel Package Doctor: Composer Tells You What Changed, Not What It Means

Before a Laravel upgrade you need to know which dependencies are safe, which are abandoned, which are blocked by your constraints, and which will break on the new version. composer outdated tells you none of that. Here is why I built Laravel Package Doctor and how to use it.


Every time I have prepared a Laravel project for a major version upgrade, the same problem appears. I run composer outdated and get a list of version numbers. I run composer audit and check for known CVEs. Then I spend the next hour or two opening Packagist pages and GitHub repositories for each package that looks suspicious, trying to answer questions the tools did not answer.

Is this package still actively maintained? When was the last release? Does it support the Laravel version I am moving to? Is the latest version blocked by my composer.json constraint? Is the GitHub repository archived? These are the questions that actually matter before an upgrade, and none of the standard Composer commands answer them together.

Laravel Package Doctor is a Composer package that does.

What Composer gives you vs what it means

When you run composer outdated on a project with a problematic package, you get something like this:

vendor/legacy-helper   1.2.0   2.0.0

That tells you a newer version exists. It does not tell you whether the upgrade is safe, whether your current constraint allows it, or whether the package has been abandoned by its author.

Laravel Package Doctor runs the same scan and produces:

vendor/legacy-helper
  Current:        1.2.0
  Latest:         2.0.0         (Major upgrade — may contain breaking changes)
  Latest allowed: 1.2.3         (Latest version your constraint permits)
  Score:          42 / 100
  Status:         Risky

  Issues:
  ↳ [constraint_blocked]      Latest version is blocked by your composer.json constraint.
  ↳ [major_upgrade_available] A major upgrade is available. Review changelog before updating.

  Recommendation: Review changelog and update constraint if compatible.

For a genuinely critical package, it goes further:

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.

The raw version diff becomes a scored decision.

What the package detects

Each dependency gets a health score from 0 to 100. The score starts at 100 and weighted deductions apply per issue:

  • Security advisory found: −30
  • Package abandoned on Packagist: −30
  • GitHub repository archived: −25
  • Laravel version incompatibility: −20
  • PHP version incompatibility: −20
  • Constraint blocking latest major: −15
  • No release in 18 months: −15
  • Risky license (GPL/AGPL variants): −15
  • Major upgrade available: −10
  • No release in 12 months: −8

Scores map to four statuses: Healthy (90–100), Watch (70–89), Risky (40–69), Critical (0–39). The project as a whole gets an aggregate score.

The full report is a table: package name, current version, latest version, upgrade type (patch/minor/major), score, status, and one recommendation per package.

Installing it

composer require --dev satheez/laravel-package-doctor

Laravel auto-discovers the service provider. There is no manual registration step. Running php artisan package:doctor is the entire quick start.

If you want to tune thresholds, CI gates, scan scope, or silence known false positives:

php artisan vendor:publish --tag=package-doctor-config

The config exposes a minimum_project_score for CI, which packages to ignore, whether to include dev or transitive dependencies, and a GitHub token for projects large enough to hit rate limits.

The workflows I actually use it for

Before a Laravel upgrade — direct dependencies only, because those are the ones you control:

php artisan package:doctor --direct

This shows every direct dependency that has not declared support for the target Laravel version, every one that is abandoned, and every one where a major upgrade is available but blocked by your constraint. That list is the pre-upgrade work list.

Gating a production deployment — exit codes make this composable with any CI system:

php artisan package:doctor --no-dev --ci

Exit code 0 = all production packages are healthy enough to deploy. Exit code 2 = at least one critical package was found; the pipeline stops. No parsing required.

Auditing an inherited project — full scan, all dependencies:

php artisan package:doctor

Five minutes after cloning an unfamiliar project, you have a scored picture of every dependency. Abandoned packages, archived repos, constraint-blocked upgrades, license concerns — all surfaced in one pass instead of an afternoon of Packagist tab-switching.

Weekly health check — store the result as a CI artifact:

php artisan package:doctor --json --ci

JSON output is stable enough to pipe into dashboards or diff week over week. The --ci flag ensures the exit code is meaningful even when output is redirected.

Why not Dependabot or composer audit

These tools solve different parts of the problem, and they compose well.

composer audit and Dependabot alerts are hard security gates. Run them. They are fast, authoritative for known CVEs, and integrate directly with GitHub. Package Doctor does not replace them.

Dependabot and Renovate automate routine version bumps. Let them. Automated minor and patch updates are noise you do not want to manage manually.

Package Doctor is the layer that answers the questions Dependabot and composer audit do not — abandonment, compatibility with your target Laravel version, constraint-blocked majors, release recency, license classification. It is the decision layer that sits above the raw data these tools provide.

The README has a full comparison table if you want to see exactly which tool covers which concern.

What it deliberately does not do

Package Doctor never modifies your project. It does not run composer update. It does not rewrite composer.json. It does not install or remove anything.

It reads your lock file, runs read-only Composer commands, and calls Packagist and GitHub APIs. The output is a report. What you do with the report is your decision.


The package is on GitHub at satheez/laravel-package-doctor and available via Packagist. If you inherit Laravel projects regularly or are approaching a major version upgrade, it is the first thing I reach for before touching composer.json.