composer-dependency-analyser

repository·master·Indexed 20 days ago

https://github.com/shipmonk-rnd/composer-dependency-analyser

A high-performance PHP tool to detect unused, shadowed, and misplaced Composer dependencies. It identifies issues such as dev-dependencies leaking into production, direct dependencies relying on unlisted sub-dependencies, and production dependencies used only in development paths. The tool can be used via CLI or the ShipMonk\ComposerDependencyAnalyser\Analyser class and supports custom configuration via a PHP file to adjust scanned paths, ignore specific errors, and force-mark symbols as used.

Tokens
4.9K
Snippets
14
Records
19
Agent score
69%

What's inside composer-dependency-analyser

  1. Understand detected dependency issues

    master

    The tool detects several types of dependency misconfigurations:

    • Shadowed dependencies: Dependencies of your dependencies that are used in your code but not explicitly listed in composer.json. These can break if a direct dependency updates and removes the requirement.
    • Unused dependencies: Non-dev dependencies listed in composer.json that have no detected usage in the scanned paths.
    • Dev dependencies in production code: Packages listed in require-dev that are actually used in your production source code. This can cause failures in environments running with --no-dev.
    • Prod dependencies used only in dev paths: Packages listed in require that are only used within development paths (e.g., tests). Moving these to require-dev reduces unnecessary overhead for users.
    • Unknown classes/functions: Symbols that cannot be autoloaded or defined during runtime. These are reported because the tool cannot determine if they are shadowed or not.
  2. Configure the analyser with a PHP config file

    master

    The tool automatically loads a file named composer-dependency-analyser.php if it is located in the current working directory. This file must return an instance of ShipMonk\ComposerDependencyAnalyser\Config\Configuration. You can also specify a custom config path using the --config CLI option.

    Use the Configuration object to:

    • Adjust scanned paths (addPathToScan, addPathToExclude).
    • Ignore specific errors (ignoreErrors, ignoreErrorsOnPath, ignoreErrorsOnPackage, etc.).
    • Ignore unknown symbols (ignoreUnknownClasses, ignoreUnknownFunctions).
    • Adjust analysis behavior (enableAnalysisOfUnusedDevDependencies, disableExtensionsAnalysis).
    • Force-mark symbols as used (addForceUsedSymbols) to handle usages in non-PHP files like YAML or XML.
    <?php
    
    use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
    use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
    
    $config = new Configuration();
    
    return $config
         ->addPathToScan(__DIR__ . '/build', isDev: false)
         ->addPathToExclude(__DIR__ . '/samples')
         ->ignoreErrors([ErrorType::DEV_DEPENDENCY_IN_PROD])
         ->ignoreErrorsOnPackage('symfony/polyfill-php73', [ErrorType::UNUSED_DEPENDENCY])
         ->ignoreUnknownClasses(['Memcached'])
         ->enableAnalysisOfUnusedDevDependencies();
  3. Understand the types of issues detected by the Analyser

    master

    The Analyser::run() method identifies several categories of dependency-related issues:

    • Shadowed dependencies: A dependency is used that is not explicitly listed in composer.json (often because it's a transitive dependency).
    • Unused dependencies: A package listed in composer.json is not actually used in the scanned code.
    • Dev dependencies in production code: A package marked as a dev dependency in composer.json is being used in a file that is not considered a dev path.
    • Prod dependencies used only in dev paths: A production dependency is only ever used within files designated as dev paths.
    • Unknown classes/functions: Code references a class or function that cannot be found via the registered class loaders or reflection.
    • Unknown symbols: Symbols that are not part of the core PHP extensions or the user-defined code.
  4. Force-mark symbols used in non-PHP files

    master

    If your project uses classes or functions within non-PHP configuration files (like YAML, XML, or NEON for Dependency Injection Containers), the analyser might report them as unused or unknown. You can use addForceUsedSymbols() in your configuration to prevent this.

    A common pattern is to use regex to extract class names from these files and pass them to the config.

    $classNameRegex = '[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*';
    $dicFileContents = file_get_contents(__DIR__ . '/config/services.yaml');
    
    preg_match_all(
        "~$classNameRegex(?:\\\$classNameRegex)+~",
        $dicFileContents,
        $matches
    );
    
    $config->addForceUsedSymbols($matches[1]);
  5. Ignore specific unused symbols

    master

    When running the dependency analyser, you can ignore specific unknown classes or functions that are reported as issues. The UnusedSymbolIgnore configuration allows you to specify a symbol (either as a literal string or a regular expression) and its type to prevent it from being flagged as an error.

    To configure an ignore rule, you must provide:

    • The symbol name or pattern (unknownSymbol).
    • A flag indicating if the pattern is a regular expression (isRegex).
    • The type of symbol (symbolKind), which must be either a class-like entity or a function.
    /* 
    Note: This is a programmatic configuration object. 
    In a configuration file, you would typically provide the values for:
    - unknownSymbol: string
    - isRegex: bool
    - symbolKind: SymbolKind::CLASSLIKE | SymbolKind::FUNCTION
    */
  6. Reference: CLI options

    master

    The following options are available for the composer-dependency-analyser command line interface:

    OptionDescription
    --composer-json path/to/composer.jsonCustom path to composer.json
    --dump-usages symfony/consoleShow usages of certain package(s) (supports * placeholder)
    --config path/to/config.phpCustom path to configuration file
    --versionDisplay version
    --helpDisplay usage & CLI options
    --verboseSee more example classes & usages
    --show-all-usagesSee all usages
    --format [console|junit]Output format (console is default)
    --disable-ext-analysisDisable PHP extensions analysis (e.g. ext-xml)
    --ignore-unknown-classesGlobally ignore unknown classes
    --ignore-unknown-functionsGlobally ignore unknown functions
    --ignore-shadow-depsGlobally ignore shadow dependencies
    --ignore-unused-depsGlobally ignore unused dependencies
    --ignore-dev-in-prod-depsGlobally ignore dev dependencies in prod code
    --ignore-prod-only-in-dev-depsGlobally ignore prod dependencies used only in dev paths
  7. Define paths to scan and exclude

    master

    You can control the scope of the analysis by specifying which directories to include in the scan and which to skip.

    • Add paths to scan: Use addPathToScan(string $path, bool $isDev) to include a directory. The $isDev flag indicates if the path belongs to development dependencies.
    • Exclude paths: Use addPathToExclude(string $path) to skip specific directories.
    • Exclude via regex: Use addPathRegexToExclude(string $regex) to exclude files matching a regular expression.
    • Set file extensions: Use setFileExtensions(array $extensions) to define which file types should be analyzed (defaults to ['php']).
    $config->addPathsToScan(['src', 'app'], false)
        ->addPathToExclude('vendor')
        ->addPathRegexToExclude('/^tests\/fixtures\//')
        ->setFileExtensions(['php', 'inc']);
  8. Ignore unknown classes and functions

    master

    If the analyser reports unknown classes or functions that you know are safe to ignore (e.g., due to magic methods or dynamic loading), you can suppress them specifically.

    • By name: Use ignoreUnknownClasses(array $classNames) or ignoreUnknownFunctions(array $functionNames).
    • By regex: Use ignoreUnknownClassesRegex(string $classNameRegex) or ignoreUnknownFunctionsRegex(string $functionNameRegex) to match patterns of unknown symbols.
    $config->ignoreUnknownClasses(['My\Dynamic\Class'])
        ->ignoreUnknownFunctionsRegex('/^dynamic_func_/')
        ->ignoreUnknownClassesRegex('/^Tests\\Mock\\.*/');
  9. Access analysis findings via AnalysisResult

    master

    The AnalysisResult class is the primary data object containing the findings of a dependency analysis run. It provides structured access to various types of detected issues, such as unused dependencies, shadowed dependencies, and unknown symbols.

    When consuming the results of an analysis, you can use the following methods to retrieve specific error categories:

    • getScannedFilesCount(): Returns the total number of files scanned.
    • getElapsedTime(): Returns the time taken for the analysis in seconds.
    • getUsages(): Returns a map of package names to their class usages (package => [ classname => usage[] ]).
    • getUnknownClassErrors(): Returns errors where classes were used but not found (package => usages).
    • getUnknownFunctionErrors(): Returns errors where functions were used but not found (package => usages).
    • getShadowDependencyErrors(): Returns errors for shadowed dependencies (package => [ classname => usage[] ]).
    • getDevDependencyInProductionErrors(): Returns errors for dev dependencies used in production code (package => [ classname => usage[] ]).
    • getProdDependencyOnlyInDevErrors(): Returns a list of production dependencies that are only used in dev paths.
    • getUnusedDependencyErrors(): Returns a list of dependencies that are not used at all.
    • getUnusedIgnores(): Returns the list of ignore rules applied to unused symbols or errors.
  10. Run dependency analysis with the Analyser class

    master

    The ShipMonk\ComposerDependencyAnalyser\Analyser class is the main entrypoint for performing dependency scanning. To use it, you must instantiate it with a Stopwatch, a default vendor directory, an array of Composer ClassLoader instances, a Configuration object, and an array of your composer.json dependencies.

    Calling the run() method executes the analysis and returns an AnalysisResult object containing details about used symbols, unknown classes/functions, shadowed dependencies, unused dependencies, and other common dependency issues.

    use ShipMonk\ComposerDependencyAnalyser\Analyser;
    use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
    use ShipMonk\ComposerDependencyAnalyser\Stopwatch;
    
    // ... setup $classLoaders, $composerJsonDependencies, $config, $stopwatch, $defaultVendorDir ...
    
    $analyser = new Analyser(
        $stopwatch,
        $defaultVendorDir,
        $classLoaders,
        $config,
        $composerJsonDependencies
    );
    
    $result = $analyser->run();