# composer-unused wants the phar, not a composer require

> 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.

**composer-unused/composer-unused** — Show unused composer dependencies by scanning your code

- Repository: https://github.com/composer-unused/composer-unused
- Stars: 1,695 · Forks: 58
- Language: PHP
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/composer-unused-composer-unused

## 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 DB82D6DEA49B570163338FA33135AA4CB4F1AB0B
```

or fetched from the latest release:

```
curl -OL https://github.com/composer-unused/composer-unused/releases/latest/download/composer-unused.phar
```

The 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-unused
```

after 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/console
```

The 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-progress
```

Those 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.phar
```

## composer-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:

```php
$config->addNamedFilter(NamedFilter::fromString('dependency/name'));
```

Ignoring by pattern covers a whole organisation, and the README gives symfony as the example form:

```php
$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:

```php
$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:

```php
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:

```php
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.

## 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.

## FAQ

### 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.

## Sources

- [composer-unused/composer-unused on GitHub](https://github.com/composer-unused/composer-unused)
- [Issues](https://github.com/composer-unused/composer-unused/issues)
- [License: MIT](https://github.com/composer-unused/composer-unused/blob/main/LICENSE)
- [README](https://github.com/composer-unused/composer-unused/blob/main/README.md)
- [Releases](https://github.com/composer-unused/composer-unused/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/composer-unused-composer-unused
