dependency guard

Static production dependency analysis.

87
7
PHP

Abandoned

This package is abandoned in favor of
maglnet/composer-require-checker,
which is well maintained and is
natively supported by GrumPHP.
Composer require checker is more rich in
features,
it is kept up-to-date, and importantly, uses the same
MIT license.

Packagist
Packagist
PHP from Packagist
Scrutinizer Code Quality
Code Coverage
Build Status
Build status
Code Intelligence Status
license

Introduction

MediaCT Dependency Guard is a static code analyzer that determines whether your
package is depending on code that is installed as a side-effect of your developer
and / or test environment.

Installation

composer require --dev mediact/dependency-guard

The tool can then be used in 3 ways:

  1. As a composer command
  2. As a stand-alone command
  3. As GrumPHP task

Composer command

composer dependency-guard

Stand-alone

vendor/bin/dependency-guard

GrumPHP task

Invoke GrumPHP using the provided configuration.

Usage

When running the DependencyGuard, it will look for all files that are accessible
through the autoload configuration of composer.json.

It will traverse all files, trying to detect PHP files and then determining the
symbols (class, interface, trait) used by each file.

When the symbols are gathered, it determines to which package each symbol belongs.
If a package is detected that is not installed because of the require section,
but because of the require-dev section in composer.json, a violation is
registered.

Also, when it is determined that a package is installed, while no code is using
that package, another violation is registered.

Violation text

In the example above, the package mediact/data-container is installed as dev
package, yet used directly in the code of the current package.

Additionally, the package league/container is installed, but none of its code
is directly used.

To use custom reporting of these violations, JSON is supported as export format:

Violation JSON

Configuring exceptions

There are circumstances when dependency violations happen intentionally.

Suggest

For instance, when a package is supplying factories for multiple implementations
of a specific factory, that require different packages for each factory. In that
case, the package would require those implementations as dev-package, and also
list them under the suggest
section of their composer.json.

When a package is added to the suggest section, its violations are ignored by
the dependency guard:

{
  "require": {
    "psr/http-message-implementation": "@stable"
  },
  "require-dev": {
    "guzzlehttp/psr7": "^1.4"
  },
  "suggest": {
    "guzzlehttp/psr7": "To use the Guzzle PSR7 implementation."
  }
}

Exclude symbols

To exclude specific symbols, add the following to the
extra.dependency-guard.exclude section of composer.json:

{
  "extra": {
    "dependency-guard": {
      "exclude": [
        "Specific\\Class\\To\\Exclude",
        "Specific\\Namespace\\",
        "Some\\Class\\Matching\\Foo*"
      ]
    }
  }
}

Symbols can be excluded using an exact match, a namespace match or a pattern used
by fnmatch.

The configuration above will exclude:

  • Specific\Class\To\Exclude
  • Specific\Namespace\Bunny
  • Some\Class\Matching\FooBarBaz

Ignore packages

To ignore violation messages for a specific package, add the following to the
extra.dependency-guard.ignore section of composer.json:

{
  "extra": {
    "dependency-guard": {
      "ignore": [
        "acme/tnt",
        "symfony/",
        "league/fly*"
      ]
    }
  }
}

Packages can be ignored using an exact match, a vendor match or a pattern used
by fnmatch.

The configuration above will ignore violations for the following packages:

  • acme/tnt
  • symfony/console
  • league/flysystem

Known limitations

Some scenarios cannot be covered by Dependency Guard, in its current form.
Known limitations are listed below:

🦊 Pokémon exception handling combined with integration tests

Pókemon exception handling is also known as:

This methodology catches any and all exceptions and either forwards them or ignores them:

<?php
try {
    doSomething();
} catch (Throwable $exception) {
    // Do nothing.
}

When this is used to handle exceptions in an application, the following issue is caused by
running integration tests on that code.

When the test sets an expectation / assertion, the assertion may fail. When the assertion
fails, it throws a corresponding exception, which is meant to be caught by the test framework.

Instead it is caught by the exception handling as described above. To counteract this, the
following ends up in production code:

<?php
try {
    doSomething();
} catch (\PHPUnit\Framework\AssertionFailedError $assertionException) {
    // Re-throw the exception, as it is part of the testing framework.
    throw $assertionException;
} catch (Throwable $exception) {
    // Do nothing.
}

The code above causes DependencyGuard, to detect that
\PHPUnit\Framework\AssertionFailedError is a symbol that can only be available when
a development installation is used. It may be expected that, since this symbol is only
used within a catch, it is not “really” a dependency, as it will only be autoloaded
when that specific exception is thrown. DependencyGuard does no such specific inspection
of the symbol at hand. The exception is thus marked as dependency violation.

There are currently no plans to solve this. That being said, pull requests and open
discussions on this matter are welcomed.