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.
The only gotcha here is vendor/bin/composer
β Paul Redmond πΊπΈ (@paulredmond) August 20, 2020
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
! π₯³