Composer Global Require Considered Harmful?

Bruno Skvorc

We’ve discussed Composer best practices before, and I’ve always advocated using composer global require when installing packages that can be used across several projects – particularly command line tools. Then, the other day, I ran into this discussion.

Sad Composer

The short of it is – the majority of people now seem to feel like global require is bad practice, unless the globally installed package has zero dependencies. Technically, this makes sense when one is using a single environment for all their projects, but as I commented in that discussion, when using a VM per project or a properly isolated environment like Docker or the like, then this problem is moot and global literally cannot do harm.

The OP’s suggested solution to this problem is:

As an alternative, users should use composer require to install each commandline tool to its own local project, and manage their $PATH or binaries manually (e.g. by creating symlinks from a bin directory already in the $PATH).

This, to me, is an entirely unacceptable complication. Composer has been the pride of PHP in how easy it was to use and how newbie-friendly it made package management – local or global. Having to symlink things around (especially taking into account non-symlink OSs like Windows) would add tedium. The OP then goes further to suggest a change to how the global command works:

a “global” but isolated project could be installed to ~/.composer/global/[something]; its vendor and bin directories would appear in their usual locations, and the contents of the ~/.composer/global/[something]/bin directory could be mirrored (via symlink) in ~/.composer/vendor/bin or, perhaps a better option would be simply ~/.composer/bin. There are various ways that the string [something] could be chosen; the most straightforward would be simply org/project (although this means that long paths such as ~/.composer/global/org/project/vendor/org/project would exist).

I completely agree with this approach, and it seems like the best of both worlds. Obviously, it might cause some backwards-compatibility headaches, but that’s not to say it can’t happen in version 2.0 of Composer. Taylor Otwell echoes this sentiment a bit further below:

Totally agree. It would be amazing to have each composer global installed package installed into its own isolated directory with its own isolated dependencies instead of possibly conflicting with other globally installed packages.

Following all this, in true open-sourcery spirit, the OP then built the alternative global implementation as a separate tool: cgr. Let’s take a look at how it works.

CGR – Composer Global Require Alternative

I’ll be executing all the below commands on a Homestead Improved instance

To start using CGR, we install it as a global package.

composer global require consolidation/cgr

If the bin folder of your Composer isn’t in the PATH variable, add it:

echo "export PATH=\$PATH:\$HOME/.composer/vendor/bin/" >> ~/.bashrc
echo "export CGR_BIN_DIR=\$HOME/.composer/vendor/bin" >> ~/.bashrc
source ~/.bashrc

The above commands extend the $PATH env. variable with the route to Composer’s global bin directory (the default location on Homestead Improved – yours may vary). The second command configures the bin directory for cgr to use, while the third loads these changes. These will also be auto-loaded every time you run the terminal interface as this user (in my case, Vagrant via vagrant ssh).

CGR should then be accessible by just running cgr, and should output Composer’s general help file.

Installing a global Composer package the right way

cgr phpunit/phpunit

On Homestead Improved, there is a useful alias configured where typing phpunit expands into vendor/bin/phpunit which comes in handy when phpunit is installed per-project, so it can be run from the root folder. To test the global installation of PhpUnit, we need to remove this alias first (in ~/.bash_aliases comment the appropriate line) and then exit the shell and re-enter, so the aliases reload. Then, running this newly globally installed PhpUnit with version output should produce something like:

vagrant@homestead:~$ phpunit --version
PHPUnit 5.4.2 by Sebastian Bergmann and contributors.

Now let’s try to install two incompatible packages.

cgr laravel/installer
cgr wp-cli/wp-cli

Sure enough, they both get installed fine. Let’s check if they work.

vagrant@homestead:~$ wp --version
WP-CLI 0.23.1
vagrant@homestead:~$ laravel --version
Laravel Installer version 1.3.2

All good! Global packages that previously conflicted due to mismatches in dependencies can now co-exist side by side and be used OS-wide without a hitch!

What should/can the tool NOT do?

In some cases, you may want to install Composer plugins. As the limitations part states, due to CGR installing each global package into its own folder with its own dependency tree, these would not be globally available across all global projects then. As such, if you want to install plugins that alter composer’s general behavior, you should still use composer global require instead of cgr. CGR itself is one such plugin, for example.

What’s next?

Test, test, test! If you’re a frequent user of the global require command, I urge you to test this new tool and give Greg Anderson some feedback on how well it satisfies your global needs and what could be improved, if anything.

Note that this tool is still just a proof-of-concept, and the implementation may or may not be renamed, repackaged, implemented into Composer’s core eventually, etc. In other words, use it as much as possible, but don’t grow to depend on it too much just yet.

While your global packages are installing, why not tell us how you feel about composer global require? Is it as harmful as many seem to now think, or is it just a matter of being careful and having isolated development environments? Something else? Chime in below!