Run Composer Binaries From Project Directories

December 4th, 2020 Linux , PHP

As a PHP developer I often find myself having to run executable files installed to a project via Composer (e.g. php-cs-fixer, phpunit, psysh, etc.). These binary files typically reside in vendor/bin relative to the root of a project and in order to run these binary files from the project root I would need to cd into the vendor/bin directory first or type out the relative path (e.g. vendor/bin/php-cs-fixer fix) every time I wanted to run one of these executables.

Similarly, I have several Composer executables installed globally in ~/.config/composer/vendor/bin. To run these I would have to type the full path to these files (e.g. ~/.config/composer/phpunit) with each invocation.

The thing is, I have a low tolerance for even minor annoyances in my development workflow as they tend to add up quickly and get in the way of actual work. So I went looking for a way to avoid these problems and improve my quality of life.

The PATH variable

On all major operating systems the PATH environment variable specifies a set of directories to look in for an executable (or "binary") file when running a command from the command line. On Linux-based operating systems this variable will be composed of multiple directories separated by colons (:). For example a default PATH variable may have a value of /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.

When using the command line to issue a command like ls your system will look for an executable file in the directories defined in PATH, from first to last, and execute the first matching executable found. For the default value above this would first look for an executable at /usr/local/sbin/ls, next at /usr/local/bin/ls and so on until it finds the executable at /usr/bin/ls.

There are two additional things worth mentioning: First, this variable can be customized allowing additional search directories to be added and second, those paths can be absolute or relative.

The Solution

This solution assumes you're familiar with Bash startup files (a.k.a. rc files) and setting environment variables.

By now, it should be pretty obvious where this is going. We can add our Composer binary directories to our PATH environment variable! We can do this by adding the following to our .bashrc file.

export PATH="vendor/bin:${COMPOSER_HOME}/vendor/bin:${PATH}"

This will prepend the local (relative) and global (absolute) Composer binary directories to the existing PATH definition. Now, when executing commands the system will search for an executable at vendor/bin/<something> (relative to the current directory) first, then ${COMPOSER_HOME}/vendor/bin and lastly fall back to the default directories. Therefor, when in a project directory and we attempt to run something like phpunit it will be executed from the locally or globally installed dependency if it exists or gracefully fall back and we never have to leave the comfort of our project root directory.

See the Composer docs for additional information about the COMPOSER_HOME environment variable.

There is, however, one caveat with this approach that was brought up by Paul Redmond when I shared this solution on Twitter.

Specifically, if you have Composer installed globally and installed to your project (i.e. composer require composer/composer) when you attempt to run composer update (or any other composer ... command) from your project root the binary in vendor/bin would take precedence over the global binary due to the PATH definition. To solve this I came up with the following bash alias and added it to my bash startup files as well.

[[ $(command -v composer) ]] && alias composer="$(command -v composer)"

Now, as long as Composer is installed globally, running composer from a project directory will execute the global binary, even if composer exists in vendor/bin! πŸ₯³