graham-campbell/analyzer
Analyzer is a PHP package by Graham Campbell that checks whether referenced classes actually exist. It helps catch missing or mistyped class references, supports PHP 8.1-8.5, and integrates with PHPUnit 10-13 for test-time analysis.
Architecture fit. Analyzer is not a Laravel package in the conventional sense — no service provider, no config file, no facade, no Artisan command, no container bindings, no publishable assets. It is a dev-only static check that plugs into PHPUnit via an AnalysisTrait exposing four static hooks (getPaths(), getIgnored(), shouldAnalyzeFile(), provideFilesToCheck(), all static since v4.0). It parses PHP source with nikic/php-parser, resolves use statements, type hints, instantiations, and — since v2.0 — PHPDoc references through phpdocumentor/reflection-docblock/type-resolver, then asserts each referenced class actually loads. The minimalism is the value proposition: zero production runtime footprint, nothing added to the boot path, nothing to reason about during an incident. The correct mental model is a pre-test quality gate that runs inside the existing test suite, not an application dependency.
Integration feasibility. High on the happy path, low on the follow-through. Installation is one composer require --dev "graham-campbell/analyzer:^5.1" plus a test case that uses the trait and declares paths and ignores. The real cost is triage: the first run against a mature Laravel codebase will surface a large wave of findings, each requiring a judgment call — genuine defect versus legitimate dynamic reference. Schedule around triage and ignore-list governance, not around installation.
Technical risk.
config/app.php providers and aliases arrays, $app->make('...'), Route::middleware('...'), morph maps, event/listener discovery, factory $model properties, queue payloads, Blade/Inertia component strings. This is a material gap because stale providers and facade aliases in config arrays are among the most common Laravel breakage classes. The tool under-delivers exactly where Laravel breaks most often.class_alias(), conditionally-defined classes, generated or eval-produced classes, package-discovery artifacts absent from the dev autoloader, and docblocks referencing optional or upstream dependencies will fail the gate while runtime is fine. getIgnored() is the escape hatch, but every entry is a permanent suppression that needs an owner.Key questions to answer before committing.
getIgnored() list, and what is the review bar for adding an entry?Stack fit. Complementary, not competitive. PHPStan/Psalm reason about types, signatures, and control flow; Analyzer reasons only about existence. Position it as layer zero of the static quality stack — cheapest, fastest, narrowest — running before the heavier tools so trivial breakage fails in seconds rather than minutes. Natural Laravel targets: service providers, facades, policies, API resources, form requests, jobs, mailables, console commands, and the PHPDoc blocks that feed IDE completion and generated API docs. Keep it out of the production dependency tree and out of the application namespace.
Migration path. Net-new capability, so the migration is organizational: from manual review for class correctness to an automated gate. Run report-only for two to four weeks, collect findings into a spreadsheet, classify each as defect / false positive / accepted suppression, then flip to blocking. Do not make it blocking on day one — a red pipeline on the first PR destroys credibility before the team understands the signal.
Compatibility. Pin to ^5.1 and treat the PHPUnit major as the binding constraint: PHP 8.1–8.5 and PHPUnit 10–13 are the supported envelope. Anything on PHPUnit ≤9 or PHP <8.1 needs an older Analyzer major and should be treated as a separate support track, not a backport. Verify that nikic/php-parser and the phpdocumentor reflection stack in the dev tree resolve without conflicting with Larastan/PHPStan, which pull their own parser versions — Composer will usually converge, but confirm in a clean composer update --dry-run before rollout. The package is --dev only; make that explicit in the lockfile review so it never leaks into a production image.
Sequencing.
app/, tests/, and first-party packages — exclude vendor/ and generated code (IDE helpers, compiled container, cached config).getIgnored() seed list.Maintenance. Low ongoing cost in the steady state, with two recurring obligations: the ignore list (which accretes silently and must be pruned) and the PHPUnit-major co-upgrade. Mitigate the first by requiring an inline comment or a linked ticket for every suppression and by reviewing the list quarterly — an ignore list nobody reads becomes a permanent blind spot. Mitigate the second by keeping a single owner for the test-tooling stack.
Support. Graham Campbell is a well-known, long-tenured maintainer of the Laravel-ecosystem GrahamCampbell package family, with a documented changelog, releases, security policy, and an enterprise support path through Tidelift. That is better support posture than most single-maintainer packages. The counterweight is the bus factor: one maintainer, no dependents, no visible downstream validation. Budget the effort to fork (small, MIT-licensed surface) as the contingency, and decide in advance whether Tidelift is worth the subscription for the specific value of commercial escalation on a dev-only test dependency — usually it is not.
Scaling. Scales well mechanically: analysis is per-file, stateless, and parallelizes across CI shards as a separate PHPUnit test case. Runtime grows linearly with the number of files in the configured paths, which is why path scoping matters more than anything else — pointing it at a large app/ plus first-party packages is fine; pointing it at everything including generated code is not. The scaling problem is social, not technical: the bigger and older the codebase, the larger the initial finding wave and the more ignore entries accumulate. Each ignore entry reduces coverage in a way that is invisible on a dashboard.
Failure modes.
make() strings, middleware aliases, morph maps. Explicitly out of scope; do not let the badge imply otherwise. Pair with a config-cache smoke test or a boot-time integration test to cover this class of breakage.Ramp-up. Roughly half a day for a competent Laravel engineer to wire up, run against a pilot repo, and produce a triaged finding list; one sprint of two-to-four-week report-only observation before enabling blocking; then an hour or two per repo thereafter. The genuine ramp-up cost is the first triage on a legacy codebase, which can be multi-day and should be staffed accordingly and framed as a defect-discovery exercise rather than tooling setup — because that is what it actually buys you.
How can I help you explore Laravel packages today?