composer-unused wants the phar, not a composer require
Show unused composer dependencies by scanning your code
At a glance
- What is it?
- composer-unused scans your PHP source for the symbols your dependencies declare and reports the packages nothing references. Its maintainers recommend the phar archive or PHIVE over a local install.
- Who is it for?
- Run composer-unused on a large PHP repository before a dependency cleanup, where the payoff is obvious and the false positives are manageable through --excludePackage and a composer-unused.php file. Do not add it to a project as a local development dependency on the strength of the composer require line, because the project explicitly warns against that path and its own toolchain depends on pinned dependency versions.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- composer-unused/composer-unused1,695 ★ · +3
| Repository | 2026-10-16 | 2026-11-07 |
|---|---|---|
| composer-unused/composer-unused | 1.7k | 1.7k |
3 Stars in the last 22 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The gap composer why cannot fill
Composer's own dependency explanation tool answers a different question. As the README puts it, `composer why` only tells you why a package is installed as a dependency of another package. That explains provenance, not usage. In a repository with many packages, the question that hurts is whether anything in your own code actually calls the symbols a package provides.
composer-unused answers that by parsing your source and matching what it finds against the symbols each dependency exposes. The output is a list of packages nothing in your code touches, which is exactly the input you need before removing a require line.
The mechanism has a consequence that shapes everything else about the tool: it is a static scanner over PHP source, so anything loaded dynamically, generated at runtime, or referenced through configuration rather than code is invisible to it. That is why the configuration surface is as large as the scanning surface, and it is where you should look first if you see a package reported that you are certain you use.
The maintainers ask you not to install it locally
The installation section opens with a warning rather than a command. The tool heavily depends on certain versions of its dependencies, so a local installation is not recommended as it might not work as intended or cannot be installed correctly. The recommended route is the phar archive, installed through PHIVE or downloaded directly:
phive install composer-unused --trust-gpg-keys DB82D6DEA49B570163338FA33135AA4CB4F1AB0Bor fetched from the latest release:
curl -OL https://github.com/composer-unused/composer-unused/releases/latest/download/composer-unused.pharThe PHIVE line includes a GPG key fingerprint, which is the right way to distribute a phar and worth copying into any internal tooling of your own.
A local install is still documented, as a development dependency, and the run command then differs:
composer require --dev icanhazstring/composer-unusedafter which you invoke it as vendor/bin/composer-unused instead of `php composer-unused.phar`. Note also the prerequisite stated under usage: packages must be installed with composer install or composer update before running the tool, since it reads the installed set.
Excluding directories and packages, and scripting the output
Two options handle the noise that a static scanner inevitably produces. --excludeDir skips a directory and --excludePackage skips a dependency, and both accept multiple values either on one line or repeated:
php composer-unused.phar --excludeDir=config --excludePackage=symfony/consoleThe package name must match exactly what is written in composer.json, which is a frequent source of a rule that silently does nothing.
For CI, the output format is switchable and the progress bars can be turned off:
php composer-unused.phar --output-format=json --no-progressThose two flags together are what make the tool usable as a build gate rather than a read-only report, which matters because a JSON result can be diffed in a pull request and a fail-on-change rule can be added on top.
There is also an XDebug consideration. The tool automatically disables XDebug while running to avoid memory overhead and compatibility problems, and you can opt back in with an environment variable when you need to debug the tool itself:
COMPOSER_UNUSED_ALLOW_XDEBUG=1 php composer-unused.pharcomposer-unused.php is where the judgement lives
Configuration sits in a composer-unused.php file placed beside the project's composer.json. Three mechanisms appear there, and they map to the three ways a scanner goes wrong.
Ignoring a dependency by name uses a named filter:
$config->addNamedFilter(NamedFilter::fromString('dependency/name'));Ignoring by pattern covers a whole organisation, and the README gives symfony as the example form:
$config->addPatternFilter(PatternFilter::fromString('/dependency\/name/'));The third mechanism handles the case that hurts most: dependencies whose symbols are used but which the scan cannot see, because their composer.json is not set up correctly or because files are loaded another way. Per dependency you can declare extra paths:
$config->setAdditionalFilesFor('dependency/name', [<list-of-file-paths>]);By default the tool decides where to look from the autoload directive in composer.json, so anything outside those paths is invisible unless you name it.
Symfony has a preset, everything else needs a custom class
For the most common framework layout there is a shortcut. A configuration set applies scan paths for you, and the bundled one is SymfonyConfigurationSet, which the README says automatically scans bin/, config/, public/, assets/ and migrations/. The example builds it from the root package name read out of composer.json:
use ComposerUnused\ComposerUnused\Configuration\ConfigurationSet\SymfonyConfigurationSet;
$composerJson = json_decode(file_get_contents(__DIR__ . '/composer.json'), true);
$rootPackageName = $composerJson['name'] ?? 'root';
$config->applyConfigurationSet(new SymfonyConfigurationSet($rootPackageName));Anything else requires implementing ConfigurationSetInterface yourself, with an apply method that receives and returns a Configuration and a name and description:
use ComposerUnused\ComposerUnused\Configuration\Configuration;
use ComposerUnused\ComposerUnused\Configuration\ConfigurationSetInterface;
final class MyFrameworkConfigurationSet implements ConfigurationSetInterface
{
public function apply(Configuration $configuration): Configuration
{
// Add your custom configuration logic here
return $configuration;
}
public function getName(): string
{
return 'my-framework';
}
public function getDescription(): string
{
return 'Custom configuration for My Framework';
}
}That is the boundary of the convenience, and it is a documentation gap as much as a feature gap: nothing in the README explains which paths a framework's bootstrapping hides.
Testing spans PHP 8.1 through 8.5 in containers
The contributor side shows how carefully the project is built. The compose file defines five services, one per PHP minor version from php8.1 through php8.5, each built from a directory under .docker and mounting the repository read-write into the container.
The Makefile drives them, with PHP_VERSION=8.1 as the default selection and the container name built from that variable, so each target runs the matching service. The phpunit target runs vendor/bin/phpunit in the selected container, analyse runs vendor/bin/phpstan, cs runs vendor/bin/phpcs and box compiles the phar with `vendor/bin/box compile --no-parallel`. A `check` target runs cs, analyse and phpunit in succession, and clean removes vendor/ and composer.lock. Contributors get a reproducible matrix without installing five PHP versions locally, and the maintainers can claim the tool works across that whole range.
The release build itself is produced with box, configured by box.json and scoper.inc.php, which is how the phar keeps its dependency versions pinned. Static analysis runs under PHPStan with a baseline file, and coding standards through phpcs with phpcbf as the fixer. The package is MIT licensed, with the copyright held by Andreas Frömer.
Editorial conclusion
Run composer-unused on a large PHP repository before a dependency cleanup, where the payoff is obvious and the false positives are manageable through --excludePackage and a composer-unused.php file. Do not add it to a project as a local development dependency on the strength of the composer require line, because the project explicitly warns against that path and its own toolchain depends on pinned dependency versions. Check first how your project loads its dependencies, since the scanner defaults to the autoload directive in composer.json and any package loaded some other way needs setAdditionalFilesFor or it will be reported as unused.
Frequently asked questions
How do I install composer-unused?
The recommended route is the phar archive, installed with PHIVE using the listed GPG key or downloaded from the latest release. The project warns against a local install because it depends on specific dependency versions, though `composer require --dev icanhazstring/composer-unused` is documented as an alternative.
Why is a package I use reported as unused?
The scanner decides where to look from the autoload directive in composer.json, so symbols loaded another way are invisible to it. Use `$config->setAdditionalFilesFor('dependency/name', [<list-of-file-paths>])` in composer-unused.php to add the paths for that dependency.
Can composer-unused run in CI?
Yes. Pass `--output-format=json --no-progress` to get machine readable output with progress bars disabled, and use `--excludeDir` and `--excludePackage` to reduce false positives.
Does composer-unused work with Symfony projects?
Yes, through SymfonyConfigurationSet, which the README says automatically scans the bin/, config/, public/, assets/ and migrations/ directories. A complete example lives in examples/symfony-composer-unused.php.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/composer-unused-composer-unused)Star history chart for your README
[](https://hysenlabs.com/projects/composer-unused-composer-unused)Was this useful?