Composer Dependency Analyser
Fast detection of composer dependency issues (dead dependencies, shadow dependencies, misplaced dependencies)
#Composer dependency analyser
- 💪 Powerful: Detects unused, shadow and misplaced composer dependencies
- ⚡ Performant: Scans 15 000 files in 2s!
- ⚙️ Configurable: Fine-grained ignores via PHP config
- 🕸️ Lightweight: No composer dependencies
- 🍰 Easy-to-use: No config needed for first try
- ✨ Compatible: PHP 7.2 - 8.5
#Comparison:
| Project | Dead dependency |
Shadow dependency |
Misplaced in require |
Misplaced in require-dev |
Time* |
|---|---|---|---|---|---|
| maglnet/ composer-require-checker |
❌ | ✅ | ❌ | ❌ | 124 secs |
| icanhazstring/ composer-unused |
✅ | ❌ | ❌ | ❌ | 72 secs |
| shipmonk/ composer-dependency-analyser |
✅ | ✅ | ✅ | ✅ | 2 secs |
*Time measured on codebase with ~15 000 files
#Installation:
composer require --dev shipmonk/composer-dependency-analyser
Note that this package itself has zero composer dependencies.
#Usage:
vendor/bin/composer-dependency-analyser
Example output:
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)
#Detected issues:
This tool reads your composer.json and scans all paths listed in autoload & autoload-dev sections while analysing you dependencies (both packages and PHP extensions).
#Shadowed dependencies
- Those are dependencies of your dependencies, which are not listed in
composer.json - Your code can break when your direct dependency gets updated to newer version which does not require that shadowed dependency anymore
- You should list all those packages within your dependencies
#Unused dependencies
- Any non-dev dependency is expected to have at least single usage within the scanned paths
- To avoid false positives here, you might need to adjust scanned paths or ignore some packages by
--config - Dependencies that install no files are never reported, as they cannot possibly be used from your code:
- virtual packages (e.g.
psr/log-implementation), which exist only viaprovideof another package - metapackages (e.g.
roave/security-advisories), which ship requirements and conflicts only - this is detected from
vendor/composer/installed.php, so it requires installed dependencies
- virtual packages (e.g.
#Dev dependencies in production code
- For libraries, this is risky as your users might not have those installed
- For applications, it can break once you run it with
composer install --no-dev - You should move those from
require-devtorequire
#Prod dependencies used only in dev paths
- For libraries, this miscategorization can lead to uselessly required dependencies for your users
- You should move those from
requiretorequire-dev
#Unknown classes
- Any class that cannot be autoloaded gets reported as we cannot say if that one is shadowed or not
#Unknown functions
- Any function that is used, but not defined during runtime gets reported as we cannot say if that one is shadowed or not
#Cli options:
--composer-json path/to/composer.jsonfor custom path to composer.json--dump-usages symfony/consoleto show usages of certain package(s),*placeholder is supported--config path/to/config.phpfor custom path to config file--versiondisplay version--helpdisplay usage & cli options--verboseto see more example classes & usages--show-all-usagesto see all usages--formatto use different output format, available are:console(default),junit--disable-ext-analysisto disable php extensions analysis (e.g.ext-xml)--ignore-unknown-classesto globally ignore unknown classes--ignore-unknown-functionsto globally ignore unknown functions--ignore-shadow-depsto globally ignore shadow dependencies--ignore-unused-depsto globally ignore unused dependencies--ignore-dev-in-prod-depsto globally ignore dev dependencies in prod code--ignore-prod-only-in-dev-depsto globally ignore prod dependencies used only in dev paths
#Configuration:
When a file named composer-dependency-analyser.php is located in cwd, it gets loaded automatically.
The file must return ShipMonk\ComposerDependencyAnalyser\Config\Configuration object.
You can use custom path and filename via --config cli option.
Here is example of what you can do:
<?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]) //// Ignoring unknown symbols ->ignoreUnknownClasses(['Memcached']) ->ignoreUnknownClassesRegex('~^DDTrace~') ->ignoreUnknownFunctions(['opcache_invalidate']) ->ignoreUnknownFunctionsRegex('~^opcache_~') //// Adjust analysis ->enableAnalysisOfUnusedDevDependencies() // dev packages are often used only in CI, so this is not enabled by default ->disableReportingUnmatchedIgnores() // do not report ignores that never matched any error ->disableExtensionsAnalysis() // do not analyse ext-* dependencies //// Use symbols from yaml/xml/neon files // - designed for DIC config files (see below) // - beware that those are not validated and do not even trigger unknown class error ->addForceUsedSymbols($classesExtractedFromNeonJsonYamlXmlEtc) //// Make CLI file paths clickable to your IDE (uses OSC 8 hyperlinks) // Available placeholders: {file}, {relFile}, {line} ->setEditorUrl('phpstorm://open?file={file}&line={line}')
All paths are expected to exist. If you need some glob functionality, you can do it in your config file and pass the expanded list to e.g. ignoreErrorsOnPaths.
#Detecting classes from non-php files:
Some classes might be used only in your DIC config files. Here is a simple way to extract those:
$classNameRegex = '[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*'; // https://www.php.net/manual/en/language.oop5.basic.php $dicFileContents = file_get_contents(__DIR__ . '/config/services.yaml'); preg_match_all( "~$classNameRegex(?:\\\\$classNameRegex)+~", // at least one backslash $dicFileContents, $matches ); // or parse the yaml properly $config->addForceUsedSymbols($matches[1]); // possibly filter by class_exists || interface_exists
Similar approach should help you to avoid false positives in unused dependencies. Another approach for DIC-only usages is to scan the generated php file, but that gave us worse results.
#Scanning codebase located elsewhere:
- This can be done by pointing
--composer-jsontocomposer.jsonof the other codebase
#Disable colored output:
- Set
NO_COLORenvironment variable to disable colored output:
NO_COLOR=1 vendor/bin/composer-dependency-analyser
#Recommendations:
- For precise
ext-*analysis, your enabled extensions of your php runtime should be superset of those used in the scanned project
#Contributing:
- Check your code by
composer check - Autofix coding-style by
composer fix:cs - All functionality must be tested
#Supported PHP versions
- Runtime requires PHP 7.2 - 8.5
- Scanned codebase should use PHP >= 5.3