# composer-dependency-analyser: finding the dependencies your composer.json lies about

> shipmonk/composer-dependency-analyser reads your composer.json and scans the autoload paths to find four kinds of mistake: dependencies you use but never declared, dependencies you declared but never use, dev dependencies used in production code, and production dependencies used only in dev paths. It has no Composer dependencies of its own, runs on PHP 7.2 through 8.5, and the README claims it scans 15,000 files in two seconds.

**shipmonk-rnd/composer-dependency-analyser** — 🚀 Fast detection of composer dependency issues (unused dependencies, shadow dependencies, misplaced dependencies)

- Repository: https://github.com/shipmonk-rnd/composer-dependency-analyser
- Stars: 635 · Forks: 15
- Language: PHP
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/shipmonk-rnd-composer-dependency-analyser

## What it checks, and why the two obvious tools do not

The tool is described as fast detection of composer dependency issues, namely unused dependencies, shadow dependencies and misplaced dependencies, and the README's feature list puts it plainly: powerful, performant, configurable, lightweight, easy-to-use and compatible.

The comparison table in the README is the clearest argument for it. Against maglnet/composer-require-checker, it claims shadow dependency detection, misplaced in require detection and misplaced in require-dev detection, none of which the other has, with a run time of 2 seconds against 124. Against icanhazstring/composer-unused, it claims the same three detections that the other lacks, with 2 seconds against 72. The time footnote says it was measured on a codebase with around 15,000 files.

So the pitch is not that it does one thing faster. It is that it does four things that the existing tools do not do at all, and does them quickly. The word powerful in the feature list is doing that work rather than making a claim about quality.

Two design choices explain the speed and the reach. The package itself has zero composer dependencies, which is stated twice, once in the feature list as lightweight and once as a note after the installation command. A static analyser with no dependencies of its own is one less thing to version, one less thing to conflict with your project, and one fewer thing to break when you upgrade PHP. And the scanning is of paths rather than of a full project model, which is why it can run over a large codebase quickly.

The primary language is PHP, the licence is MIT, the last push was on 2026-08-11, and the recent releases are 1.8.4 from 2025-11-25, 1.8.3 from 2025-05-12 and 1.8.2 from 2024-12-30.

## Installing it and running the first pass

Installation is a dev dependency, which is the right place for a tool that only ever runs in CI or on a developer machine:

```sh
composer require --dev shipmonk/composer-dependency-analyser
```

And running it is the vendor binary:

```sh
vendor/bin/composer-dependency-analyser
```

The example output shows what a run looks like, and it is worth reading because the two categories are reported separately with an explanation of each:

```txt

Found shadow dependencies!
(those are used, but not listed as dependency in composer.json)

  • nette/utils
    e.g. Nette\Utils\Strings in app/Controller/ProductController.php:24 (+ 6 more)

Found unused dependencies!
(those are listed in composer.json, but no usage was found in scanned paths)

  • nette/utils

(scanned 13970 files in 2.297 s)
```

Two details stand out. The shadow report names the file and line where the symbol was used, so you can go and look rather than guess, and it says how many more places there are. The unused report lists the package and stops there, because there is no location to point at.

The scanner covers both packages and PHP extensions. The extension analysis is the part that distinguishes it from a package-only tool, and it can be turned off with --disable-ext-analysis, for instance for ext-xml. That matters for projects where the extension is loaded by configuration rather than referenced in code.

The default output format is console, and there is a junit format available through the --format option, which is what you want in CI so the results land in a test report rather than in a build log.

## The four errors and what each one costs you

The tool reads your composer.json and scans all paths listed in the autoload and autoload-dev sections. The four error types are the substance of the project, and the README explains the consequence of each.

Shadowed dependencies are dependencies of your dependencies which are not listed in your composer.json. Your code can break when your direct dependency gets updated to a newer version that no longer requires that shadowed dependency. The fix is to list all those packages within your dependencies. This is the error class that the comparison table shows the other tools missing entirely, and it is the one that bites hardest, because the failure appears in someone else's release cycle rather than in yours.

Unused dependencies are non-dev dependencies with no usage found in the scanned paths. The README warns about false positives here and says you might need to adjust scanned paths or ignore some packages by --config. There is also a deliberate exemption: dependencies that install no files are never reported, since they cannot possibly be used from your code. That covers virtual packages such as psr/log-implementation, which exist only via the provide of another package, and metapackages such as roave/security-advisories, which ship requirements and conflicts only. The detection comes from vendor/composer/installed.php, so it requires installed dependencies to be present.

Dev dependencies in production code are risky for libraries, because your users might not have those installed, and for applications it can break once you run composer install with --no-dev. The fix is to move them from require-dev to require.

Prod dependencies used only in dev paths is the mirror image, and for libraries the miscategorization can lead to uselessly required dependencies for your users. The fix is to move them from require to require-dev.

The rule of thumb that falls out of this is simple: in a library, require-dev is for your tests and require is for your public API, and in an application the distinction is about what composer install --no-dev leaves you with. A tool that checks this automatically is doing something a code review will not reliably do across a large codebase.

## Unknown classes and functions, and the two ignores that matter

Two error types are about what the analyser could not work out rather than about a mistake you made, and they are reported for a specific reason: any class that cannot be autoloaded gets reported, since the tool cannot say if that one is shadowed or not, and the same applies to any function that is used but not defined during runtime.

That is a defensible design, because a symbol that cannot be resolved is exactly the case where a shadow dependency would be invisible. If your code calls a class that only exists because a transitive dependency happens to provide it, the analyser has to complain rather than assume.

The cost is noise, and the configuration gives you two ways to deal with it. You can ignore specific symbols, with ignoreUnknownClasses for a list and ignoreUnknownClassesRegex for a pattern, and the same pair exists for functions with ignoreUnknownFunctions and ignoreUnknownFunctionsRegex. The example configuration shows them used for Memcached, for a pattern matching DDTrace, for opcache_invalidate and for a pattern matching opcache_, which are the classic cases of classes and functions provided by an extension or by a runtime rather than by a package.

There are also global switches for the same purposes, --ignore-unknown-classes and --ignore-unknown-functions, so you can silence the category entirely for a run, plus --ignore-shadow-deps and --ignore-unused-deps for the two main error types, and --ignore-dev-in-prod-deps and --ignore-prod-only-in-dev-deps for the misplaced ones.

Every one of those also has a scoped form in the configuration file, which is the difference between a blunt tool and a usable one in a real project.

## Configuration is a PHP file returning a Configuration object

When a file named composer-dependency-analyser.php is located in the current working directory, it gets loaded automatically, and it must return a ShipMonk\ComposerDependencyAnalyser\Config\Configuration object. You can use a custom path and filename via the --config option.

The API is a fluent builder, and the shape of it tells you what the tool thinks is worth configuring:

```php
<?php

use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;

$config = new Configuration();

return $config
     //// Adjusting scanned paths
    ->addPathToScan(__DIR__ . '/build', isDev: false)
    ->addPathToExclude(__DIR__ . '/samples')
    ->disableComposerAutoloadPathScan() // disable automatic scan of autoload & autoload-dev paths from composer.json
    ->setFileExtensions(['php']) // applies only to directory scanning, not directly listed files

    //// Ignoring errors
    ->ignoreErrors([ErrorType::DEV_DEPENDENCY_IN_PROD])
    ->ignoreErrorsOnPath(__DIR__ . '/cache/DIC.php', [ErrorType::SHADOW_DEPENDENCY])
    ->ignoreErrorsOnPackage('symfony/polyfill-php73', [ErrorType::UNUSED_DEPENDENCY])
    ->ignoreErrorsOnPackageAndPath('symfony/console', __DIR__ . '/src/OptionalCommand.php', [ErrorType::SHADOW_DEPENDENCY])
    ->ignoreErrorsOnExtension('ext-intl', [ErrorType::SHADOW_DEPENDENCY])
    ->ignoreErrorsOnExtensionAndPath('ext-sqlite3', __DIR__ . '/tests',  [ErrorType::SHADOW_DEPENDENCY])
```

The ignore methods come in a graded set: by error type, by path, by package, by package and path together, and by extension, each with an extension and path variant. That is the level of precision a large project needs, because the usual situation is that one package is used in exactly one place that the scanner cannot see.

The path controls are equally important. You can add a path to scan and mark whether it is a dev path, exclude a path, disable the automatic scan of the autoload and autoload-dev paths from composer.json entirely, and restrict the file extensions, which applies only to directory scanning and not to directly listed files. A generated directory of cached classes is the typical reason for excluding a path, and the example excludes a samples directory and a cache/DIC.php file.

There is also a way to use symbols from yaml, xml, neon files, with addForceUsedSymbols, and the comment warns that those are not validated and do not even trigger an unknown class error. That is the escape hatch for a project whose dependency usage is declared in configuration rather than in PHP.

## Where the analysis stops, and what to do with the result

Two things the tool deliberately does not do by default are worth knowing, because they are the reason a clean report is not the same as a correct composer.json.

The first is analysis of unused dev dependencies. The configuration exposes it as enableAnalysisOfUnusedDevDependencies, with the comment that dev packages are often used only in CI, so it is not enabled by default. On a project whose dev tooling is invoked from a CI configuration rather than from PHP code, enabling it will produce a wall of false positives unless you also list those symbols with addForceUsedSymbols.

The second is reporting ignores that never matched. That is on by default, and disabling the reporting is called disableReportingUnmatchedIgnores. This is a genuinely good idea to have in the opposite direction from what it sounds: an ignore rule that no longer applies is usually a sign that the problem it was hiding has been fixed, and a tool that flags stale ignores is telling you something true.

So the practical workflow has three steps. Run it once with no configuration, because the README says no config is needed for a first try. Fix the shadow dependencies first, since those are the ones that break when someone else updates a package. Then add the ignores for the remaining false positives, one path or package at a time, and keep the JUnit output in CI so that a new shadow dependency fails the build rather than waiting to be discovered.

The CLI options fill in the rest. --composer-json takes a custom path to composer.json. --dump-usages shows the usages of certain packages and supports a wildcard. --show-all-usages and --verbose differ in how much they print, with verbose showing more example classes and usages. --version, --help and --format round out the list.

The repository tree adds one more piece of context. Alongside src/ and tests/ there is a collision-detector.json and a cache/ directory, a coverage-guard.php, a phpcs.xml.dist, a phpstan.neon.dist and a phpunit.xml.dist. The collision detector and the coverage guard suggest the project is careful about the two failure modes of an analyser that runs in CI: two packages claiming the same symbol, and a coverage report that drops when the analysis changes the execution path.

## Conclusion

Adopt composer-dependency-analyser if your PHP project has grown past the point where a human reads composer.json reliably, because it is the only tool in the comparison that finds shadow dependencies and misplaced require and require-dev entries, and it does so in a fraction of the time. Do not adopt it expecting a clean bill of health on the first run, because unknown classes and functions are reported by design and the false positives around unused dev dependencies need configuration to settle. Before you commit, run it once with no config, fix the shadow dependencies first since those are the ones that break when an upstream package is updated, add scoped ignores for the residue, and wire the junit output into CI so a new shadow dependency fails the build rather than surfacing in someone's error report.

## FAQ

### What does composer-dependency-analyser detect?

Four error types: shadow dependencies, which are dependencies of your dependencies that are not listed in your composer.json, unused dependencies, dev dependencies used in production code, and production dependencies used only in dev paths. It also reports unknown classes and unknown functions, which it cannot classify as shadowed or not.

### How do I install and run composer-dependency-analyser?

Install it as a dev dependency with composer require --dev shipmonk/composer-dependency-analyser, then run vendor/bin/composer-dependency-analyser. The package itself has zero composer dependencies, and the README says no configuration is needed for a first try.

### Why are some unused dependencies not reported?

Dependencies that install no files are never reported, since they cannot be used from your code. That covers virtual packages such as psr/log-implementation, which exist only via the provide of another package, and metapackages such as roave/security-advisories, which ship requirements and conflicts only. The detection comes from vendor/composer/installed.php, so installed dependencies are required.

### How do I configure the scanner in composer-dependency-analyser?

Put a file named composer-dependency-analyser.php in the current working directory, or pass a path with --config. The file must return a Configuration object, and you can adjust scanned paths, ignore errors by type, path, package or extension, ignore unknown classes and functions, and enable analysis of unused dev dependencies.

### Which PHP versions does composer-dependency-analyser support?

PHP 7.2 to 8.5, according to the feature list. The output format is console by default, with a junit format available through the --format option, which is the one you want in a CI pipeline.

## Sources

- [Issues](https://github.com/shipmonk-rnd/composer-dependency-analyser/issues)
- [License: MIT](https://github.com/shipmonk-rnd/composer-dependency-analyser/blob/master/LICENSE)
- [README](https://github.com/shipmonk-rnd/composer-dependency-analyser/blob/master/README.md)
- [Releases](https://github.com/shipmonk-rnd/composer-dependency-analyser/releases)
- [shipmonk-rnd/composer-dependency-analyser on GitHub](https://github.com/shipmonk-rnd/composer-dependency-analyser)

---

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