Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Dead Code Detector Laravel Package

shipmonk/dead-code-detector

PHPStan extension that finds unused PHP code: dead methods, constants, enum cases, properties, dead cycles, and transitively dead members. Detects dead tested code and can automatically remove unused code. Supports Symfony and other libraries with customizable usage providers.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

shipmonk/dead-code-detector is not a runtime library — it is a PHPStan rule extension that adds a whole-program reachability analysis over your AST. Architecturally, that means it slots into the layer you already own for static analysis rather than into the application itself. For a Laravel codebase this is a genuinely good fit because Laravel is the archetypal "dead code looks dead but isn't" framework: controllers, jobs, listeners, commands, policies, middleware, form requests, mailables, notifications, and Eloquent accessors/scopes are almost all invoked by the framework via reflection, container resolution, or string references rather than by direct call sites. A naive grep-based dead-code pass on Laravel is worthless. This package's differentiating value is that it ships framework-aware usage providers — the README explicitly enumerates Laravel (Route::get/post/resource/apiResource, Event::listen/subscribe, auto-discovered listeners, Schedule::job, Gate::define/policy, real-time facades, console commands, jobs, providers, middleware, notifications, form requests, mailables, broadcast events, JSON resources) and Eloquent (model constructor, boot/booted, casts, scopes, relationships, accessors, observers, factories, seeders, migrations). That list is the product. Everything else — transitive dead cycles, tested-code detection, auto-removal — is mechanism.

The second architectural fit is customizability. The MemberUsageProvider / MemberUsageExcluder interfaces let you teach the analyser about in-house magic (custom discovery attributes, dynamic container bindings, domain-specific serializers). That converts the tool from "buy or don't buy" into "adopt plus a bounded extension surface," which is the shape a TPM wants.

The counterweight: this is a static-analysis tool whose correctness is defined by how completely it enumerates framework magic. Its accuracy on your Laravel app is a function of which Laravel features and which third-party packages you use. The README's Laravel coverage list is impressive but not exhaustive — it does not claim Route::controller(), Route::inertia(), Route::livewire(), Filament/Nova resource discovery, Livewire components, Inertia props, Horizon/Spark internals, or package-registered macros. Any of those in your stack is a false-positive generator until you write a provider.

Integration feasibility

Low friction to try, moderate effort to trust.

  • Install is composer require --dev shipmonk/dead-code-detector plus a single includes: entry in phpstan.neon.dist. No runtime dependency, no service provider, no config beyond opt-in toggles.
  • It composes with existing PHPStan adoption. If you already run PHPStan in CI (most mature Laravel teams do, typically via Larastan), this is an additive ruleset, not a new pipeline.
  • The tests usage excluder is the single most important configuration decision and the README recommends it explicitly. Without it, a class defined in src/ that is only exercised by tests is reported as used, which silently defeats the purpose of the tool. With it, you get the high-value report: "this production method exists solely because a test touches it."
  • Auto-removal (--error-format removeDeadCode) is a real capability but is a process risk, not a technical one. It rewrites source files. That must never run unattended.

Technical risk

Risk Severity Notes
False positives → deletion of live code High The only irreversible failure mode. Magic-heavy Laravel features or third-party packages outside the provider list will be misreported. Auto-removal turns a warning into a data-loss event.
Provider drift on framework/package upgrades Medium–High New Laravel features and new packages introduce new invisible call paths. This is a recurring cost, not a one-time one.
Custom provider authoring complexity Medium AST-based providers require PhpParser node handling, Scope type resolution, and correct UsageOrigin construction. This is specialist work, not a junior task.
CI cost Medium Whole-program reachability adds a full graph pass. On a large Laravel monolith this can be minutes, and memory pressure on PHPStan is a known operational tax.
Supply-chain / sustainability Low–Medium 534 stars but 0 dependents in the data provided, single-vendor origin (ShipMonk R&D), MIT, no commercial support. Blast radius is CI, not production, and it is a pinned dev dependency — but there is no vendor SLA.
PHPStan version coupling Medium As a PHPStan extension it is tightly bound to PHPStan internals. A major PHPStan upgrade can block your PHPStan upgrade.

Key questions

  1. What is our actual false-positive rate? This is the only number that matters. It cannot be inferred from the README — it must be measured on our codebase before any commitment.
  2. How much framework magic do we have outside the documented provider list? Enumerate third-party packages with discovery/reflection semantics (admin panels, Livewire, Inertia, search, feature flags) and custom Route/container patterns.
  3. What PHPStan level and version are we on, and is Larastan in play? Extension compatibility and rule interaction must be verified, not assumed.
  4. Who owns the custom providers? If the answer is "nobody yet," the tool will decay into a noisy, baselined, ignored CI step within two quarters.
  5. Are we willing to fund the triage phase? Adoption is 20% setup, 80% triage. Without a named owner and a time box, this becomes shelfware.
  6. What is our policy on auto-removal? Recommended: developer-invoked, always in a PR, never in CI, never on a branch that auto-merges.

Integration Approach

Stack fit

Strong fit for a PHP 8.1+ Laravel application already running PHPStan. The package auto-enables its Laravel, Eloquent, and (where relevant) PHPUnit, PhpBench, Behat, Twig, Doctrine, Symfony, Nette, PHPStan, and PHPat providers based on composer.json contents — so a standard Laravel app gets Laravel + Eloquent + PHPUnit coverage with zero provider configuration. That is the fastest path to a first signal.

Where it fits less well:

  • Blade-heavy UI code. The package understands Twig view objects and Symfony UX components; it does not document Blade equivalents. View models, Blade components, and @livewire-style references are likely uncovered.
  • Laravel packages with their own discovery mechanisms (admin panels, modular monoliths with attribute-driven registration). These need custom providers.
  • Codebases without PHPStan. Adopting this means adopting PHPStan first. That is a much larger programme and should be scoped separately.

Migration path

There is no migration — it is net-new static analysis. The correct framing to stakeholders is "we are adding a linting capability," not "we are changing architecture." The only real migration is cultural: teams must learn to treat dead-code reports as actionable rather than baseline-able.

Compatibility

  • Verify PHPStan major/minor constraint compatibility against your pinned PHPStan and Larastan versions before committing — this is the most likely hard blocker.
  • Confirm the analyser scans both src and tests paths, as the README insists. A partial scan produces mass false positives.
  • Confirm phpstan/extension-installer is present, or wire rules.neon manually.
  • Test the tests excluder's devPaths — it autodetects from autoload-dev, but monorepos and non-standard layouts need explicit paths.
  • Sanity-check Laravel-specific coverage against your actual patterns: Route::controller(), grouped controllers, Route::resource with only/except, invokable controllers, Event::listen with string class names, queued listener discovery, and any Gate::before/Gate::after superadmin bypass.

Sequencing

  1. Spike (1 sprint, one bounded module). Install on a single well-understood domain or a mid-sized service directory. Run report-only. Measure: total findings, confirmed-true positives, false positives, wall-clock CI delta. Kill or proceed on the false-positive rate.
  2. Repo-wide report-only baseline. Enable detect for methods, constants, enum cases, and properties. Enable the tests usage excluder. Generate a baseline so CI stays green while you triage.
  3. Triage and provider build-out. Bucket findings into (a) genuinely dead → removal PRs, (b) framework magic → custom MemberUsageProvider, (c) intentional/edge → MemberUsageExcluder with a documented reason. Each custom provider needs a test, per the project's own contributor guidance.
  4. Ratchet. Once false positives are driven near zero, remove the baseline in slices and make the rule an enforcing (non-baselined) CI gate on new code at minimum.
  5. Optional: auto-removal as a developer tool. Document it as a local, manual, PR-reviewed command. Never in CI. Never on generated code paths.

Do not reorder steps 1–4. The temptation is to go straight to repo-wide enforcement; that is how static-analysis rollouts get reverted.

Operational Impact

Maintenance

The steady-state maintenance surface is threefold:

  • The dependency itself — pinned dev dependency, version bumps gated on PHPStan compatibility. Low effort, but not zero: a PHPStan upgrade is now a two-party coordination.
  • Custom providers — the real cost. Every provider is production code that encodes an assumption about how your framework or a third-party package resolves callables. When that package changes, the provider silently becomes wrong, and the failure mode is false positives — the tool starts flagging live code. Budget ongoing ownership: one named engineer, reviewable in the same way as application code.
  • Baseline hygiene — baselines rot into permanent suppressions if nobody owns burn-down. Assign a quarterly target (e.g., "baseline reduced 30%").

Support

MIT, community-maintained, no vendor SLA, single-organisation origin. The README is unusually good (worked examples, provider interfaces, slides/recording from a 2025 conference talk), which materially reduces ramp cost. But there is no escalation path: a false positive caused by a new Laravel release is fixed by you or awaited upstream. For a dev-only tool with CI-only blast radius, that is an acceptable posture — provided the decision is made explicitly rather than discovered during an incident.

Scaling

  • Analysis time grows superlinearly with codebase size for whole-program reachability. On a large Laravel monolith, expect a meaningful addition to CI minutes. Mitigations: run the full dead-code pass nightly or on a schedule, and a fast subset on PRs; shard by directory; cache PHPStan results.
  • Monorepo/multi-app layouts complicate the "scan everything" requirement. Each app may need its own phpstan.neon and its own exclusion set; findings can cross app boundaries and produce noise.
  • Team scaling is the more subtle constraint: the tool's output is only actionable if engineers understand why something is flagged. Without a short internal runbook ("here's what the tests excluder does, here's how to add an excluder"), you get baseline-everything behaviour at scale.

Failure modes

  • False positive → live code deleted. Highest-severity, irreversible without review. Mitigated only by human-reviewed PRs and a hard prohibition on CI auto-removal.
  • False negative → false confidence. Transitive analysis can be defeated by unmodelled dynamic dispatch; teams may over-trust the green check and stop doing manual dead-code review.
  • Noisy baseline → adoption collapse. If the first full run produces 400 findings with 30% false positives, the tool gets baselined wholesale and never looked at again. This is the single most common failure and is entirely predictable from the spike.
  • PHPStan upgrade deadlock. Extension lags a PHPStan release; your PHPStan upgrade is blocked; the tool gets removed in frustration. Pin and monitor.
  • Memory exhaustion on large graphs — PHPStan OOM in CI, intermittent, hard to attribute.
  • Provider drift after a framework upgrade — silent, and only surfaces when someone notices a live method was flagged.

Ramp-up

  • Day 1: install, include rules.neon, run report-only. Any PHP-fluent engineer can do this.
  • Week 1: understand the toggles (detect.*, usageProviders.*, usageExcluders.tests), interpret the transitive-dead tip output, learn to distinguish "dead" from "invisibly called."
  • Weeks 2–4: triage. This is where the product decision is actually made — the false-positive rate discovered here is the go/no-go.
  • Weeks 4–8: first custom provider. Requires comfort with PhpParser nodes, PHPStan Scope, and the package's ClassMethodRef/UsageOrigin graph model. This is a senior-engineer task, ~1–2 weeks to first working provider, and it is the capability that determines long-term value.
  • Ongoing: ~10–20% of one engineer's time for dependency bumps, provider upkeep, and baseline burn-down.

TPM recommendation: treat this as a high-opportunity, medium-confidence bet — consistent with the 65.84 opportunity score against a 39.99 overall score and thin adoption signal (0 dependents). Fund a time-boxed, one-sprint spike on a bounded module with a hard gate on measured false-positive rate. If false positives land under roughly 10% after the tests excluder is enabled, proceed to repo-wide report-only plus a named owner for custom providers. If they land higher, the gap is almost certainly unmodelled framework magic, and the honest question is whether you have the senior capacity to close it — because without that capacity, the tool will be baselined and ignored, which is worse than not adopting it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc