Skip to content

Add require-tools section and handle scoped vendor dirs #9636

@nicolas-grekas

Description

@nicolas-grekas

This issue is a follow up of #5390 (comment)

Composer has become the primary installation method of libraries and is so good at it that it is pretty commonplace nowadays to also install tools like static analyzers or testing frameworks with it.

But this comes at a price: tools are micro-applications with a lot of dependencies, and those dependencies can often be found in your application, or in other tools.

Every time you add one of them to your toolchain, your application becomes more constrained, and very often, for no good reason, because in most cases, you do not reference classes or interfaces from those tools in your code.

Soon enough, you find yourself in the dependency hell Composer is supposed to protect you from.

I'm dreaming of a new section in our composer.json files, called require-tools, that would be aimed at solving this issue by letting you install one or several tools inside separate vendor dirs, so that:

  1. Tools have zero influence on each other.
  2. Tools have zero influence on the dependencies of your project,
    meaning you will always be able to upgrade your project's dependencies,
  3. In the case your tool has dependencies in common with your project, the
    tool will take your project's constraints into account.
  4. Tools packages might have a flag to opt-out of the above, so that if they never load the application code they can avoid being constrained by the application's dependencies.

The proposed section would not resolve the issue with tools that do require a version of a dep that is not compatible with your project. This is by design: as discussed in #5390 (comment), a phar could be the way to go for such tools.

A minimal example could look like the following:

"require-tools": {
    "phpstan": { "phpstan/phpstan": "^0.9.2" }
}

Some tools like Behat are extensible, and you would be able to install behat and its extensions like this:

"require-tools": {
    "behat": {
        "behat/behat": "^3.4.3",
        "behat/mink": "^1.7.1",
        "behat/mink-browserkit-driver": "^1.3.3",
        "behat/symfony2-extension": "^2.1.4"
    }
}

Of course, we could install phpstan next to behat like that:

"require-tools": {
    "phpstan": { "phpstan/phpstan": "^0.9.2" },
    "behat": {
        "behat/behat": "^3.4.3",
        "behat/mink": "^1.7.1",
        "behat/mink-browserkit-driver": "^1.3.3",
        "behat/symfony2-extension": "^2.1.4"
    }
}

The goal of these sections would be to create scoped vendor directories per entry in the require-tools sections. E.g. vendor/composer/tools/phpstan could contain all the deps of phpstan, etc.

Composer would also generate an autoload.php file in these scoped directories. Requiring this file would load two autoloaders: one for the vendor-dir of the project, and one for the vendor-dir of the tool.

Tools would always be considered as dev-deps, so that running composer i --no-dev would not install them (tools for prod could instead be added as direct deps of the project.)

When running composer u, composer would first resolve and install the deps as usual, then it would resolve the deps of each tool one by one, after locking the deps of the project.

The composer.lock file should be updated to store info about tool sections.

I'm sure there are more ramifications to this proposal.

Maybe this could be implemented as a Composer plugin btw, if anyone would like to give it a try?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions