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

Phpdcd Laravel Package

sebastian/phpdcd

PHP Dead Code Detector (phpdcd) is a command-line tool that scans PHP codebases for unused functions and methods, helping teams find dead code to remove. Part of Sebastian Bergmann's tooling, it is now archived and largely superseded by modern static analysis tools.

View on GitHub
Deep Wiki
Context7

Getting Started

sebastian/phpdcd is a CLI tool, not a library. You will never use it in application code and it exposes no API — you run a binary against a directory and read a list of functions/methods it cannot find a caller for.

  1. Install outside your runtime dependencies:

    composer require --dev sebastian/phpdcd
    

    ⚠️ The last tagged release predates PHP 8 and pins an old nikic/php-parser. On a modern Laravel stack (PHP 8.2+) Composer will likely refuse to resolve. Plan for one of: --ignore-platform-reqs, the distributed .phar, or running it in a throwaway PHP 7.x container. Check this before you write any tooling around it.

  2. Confirm the flag set for your installed version — it is small and version-dependent:

    vendor/bin/phpdcd --help
    

    Expect roughly --recursive, --exclude <dirs>, --names <list>, --names-exclude <list>.

  3. First run: do not point it at app/. Start with a slice of plain PHP that has no framework magic — app/Support, app/Services, app/Actions, app/Helpers:

    vendor/bin/phpdcd --recursive --exclude vendor,tests,storage app/Support
    
  4. Read the output as leads, not verdicts. Every line is "no static call site found", which in a Laravel app is very often a false positive.

Where to look first: private/protected helpers and *Service/*Action methods left behind after a refactor. Those are the highest-signal, lowest-risk findings. Treat anything public on a controller, command, job, listener, policy, or middleware as guilty until proven innocent — the framework calls those reflectively and phpdcd cannot see it.

Implementation Patterns

Triage sprint, not CI gate. phpdcd's output is too noisy to block merges on. The realistic pattern is a periodic "dead code audit" branch: run it, hand-verify findings, delete, open one PR.

Scope by directory, run multiple times. There is no config file, so encode your scope in a script or Make target:

deadcode:
	@for d in app/Support app/Services app/Actions; do \
		vendor/bin/phpdcd --recursive --exclude vendor,tests "$$d"; \
	done

Whitelist framework entry points via --names. Framework-invoked methods will flood the report. Feed them in as "considered used" (verify the exact semantics against --help on your version, since the flag's meaning shifted between releases):

__construct,__invoke,handle,register,boot,up,down,run,render,toArray,rules,authorize,via,scope*

Cross-check with a runtime signal. Combine with an Xdebug/PCOV coverage run of your test suite. A method that is both never executed by tests and flagged by phpdcd is a high-confidence deletion. A method flagged by phpdcd but hot in coverage is a dynamic-dispatch false positive — keep it.

Verify with grep before deleting. phpdcd cannot see string dispatch. Confirm with:

git grep -n "methodName"        # string-based calls, container keys, route arrays
git log -S "methodName" --oneline  # who added it and when

Then delete mechanically with Rector (RemoveUnusedPrivateMethodRector in --dry-run --diff mode) rather than by hand, so the diff is reviewable.

Report-only CI. If you wire it up at all, publish the output as a job-summary/artifact:

- run: vendor/bin/phpdcd --recursive app/Support > phpdcd.txt || true
- uses: actions/upload-artifact@v4
  with: { name: phpdcd, path: phpdcd.txt }

Long-term: for a maintained Laravel project, use PHPStan (unused private method/property detection), phan --dead-code-detection (methods, functions, classes), or Rector's dead-code rules. Keep phpdcd only as a one-off archaeology tool on legacy code.

Gotchas and Tips

Massive false-positive rate in Laravel. phpdcd's resolver is name-based, not type-based: a single $x->handle() call marks every handle() in the codebase as used, and conversely it cannot see anything invoked reflectively. Methods invoked by the framework and therefore invisible to it include:

Area Methods it will wrongly flag
Controllers everything routed to, __invoke
Console commands handle
Jobs / listeners handle, __invoke, failed
Policies viewAny, view, create, update, delete
Middleware handle, terminate
Form requests authorize, rules, messages, attributes
Models scope*, accessors/mutators, casts, booted
Mailables / notifications build, via, toMail
Blade components render, shouldRender
Providers register, boot

Name collisions hide real dead code. Because resolution is name-only, common names (get, run, make, handle, toArray) are effectively never reported even when genuinely dead. Do not read a clean report as "no dead code."

It detects only functions and methods. Not unused classes, properties, constants, parameters, local variables, or imports. Use PHPStan/phpmd for those.

Invisible call styles. call_user_func([$o,'foo']), $o->$name(), Closure::fromCallable, app(Foo::class), container string bindings, event class strings, and route 'Controller@method' string syntax are all undetectable.

Modern syntax may not parse. Enums, readonly properties, attributes, and especially first-class callable syntax ($this->foo(...)) postdate the last release's parser. A parse failure can silently abort analysis of a whole file — sanity-check by introducing a deliberate unused method and confirming it is reported.

Interfaces, abstracts, and traits. Interface-mandated implementations are frequently reported; trait methods can be attributed to the wrong class. Exclude app/Contracts and trait directories.

Debugging. No verbosity or config; if results look wrong, shrink the input to one file and re-run. There are no extension points — no plugin API, no config file. Your only levers are --exclude and --names. If you need more, fork it or move on.

Archived and unmaintained. No fixes for new PHP versions, no security response, and the parser dependency will drift further out of date. Do not build a durable pipeline on it.

License. Packagist metadata reports NOASSERTION, which will trip automated license scanners in a commercial repo even though the upstream source is BSD-3-Clause. Read the LICENSE file in the repo before adding it to a shipped monorepo; a --dev-only, locally-run tool is usually fine, but confirm with whoever owns compliance.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons