Pass a specific PHP version to subscripts in a CLI call

tl;dr Developers who use several PHP versions must ensure that CLI calls start subscripts in child processes with the desired version as well.

Origin

Using the PPA ondrej/php it is possible to run multiple PHP version on one system.

All single versions are useable on the CLI, e.g. php7.4 --version. The command php will point to a default version.

Problem

Now whenever a PHP script is executed by CLI and triggers another PHP script in a child process itself, the default version is used to run this other script.

A typical example is running Composer which then executes a framework command after every install command.

Example: Your default version is 7.2, you run Composer with 7.4, binaries or scripts triggered by Composer will still use the default 7.2.

composer.json

{
    "name": "acme/php-cli-switch",
    "description": "Test passing different PHP versions to CLI",
    "license": "GPL-2.0-or-later",
    "scripts": {
        "php-version": "php --version"
    }
}

Tests

php --version
> PHP 7.2.34

php /usr/local/bin/composer php-version
> PHP 7.2.34

php7.4 --version
> PHP 7.4.22

php7.4 /usr/local/bin/composer php-version
> PHP 7.2.34

As you can see in the last test, the subscript uses the default version. This is fine as long as the subscript supports different PHP versions. But if the subscript uses a PHP 7.4 feature and is started with a lower PHP version then a fatal error will be thrown.

Solutions

Use provided options

Some CLI scripts provide options to set the PHP version used to initiate child processes. For example the {$ENV|value-of:PHP_PATH} option in CaptainHook.

Downside: You need to know about these options for all invoked subscripts. If a third party package does not offer such an option, then you need another solution.

Hack it

It may the tempting to change all occurences of the php calls to pass the desired version. Like "php-version": "php7.4 --version" in the example above.

This however breaks update compatibility and portability of the project. And it is not maintainable for third party packages. Don't do this.

Change system default

Change the systems default version using update-alternatives.

sudo update-alternatives --set php /usr/bin/php7.4
sudo update-alternatives --set phar /usr/bin/phar7.4
sudo update-alternatives --set phar.phar /usr/bin/phar.phar7.4

Repeat the command whenever the version needs to be changed again.

Override default temporarily

If it is not allowed to change the default version on the system, then you may temporarily override the $PATH environment variable instead.

The important part here is to create a symlink first, which points to the desired version and is named php exactly. This is due to the fact, that calls for php are passed to /usr/bin/env, which looks for an executable named php in all directories given by $PATH. It does not work with aliases, shell functions or any other names.

Example for PHP 7.4, with a symlink in the users writeable home directory

mkdir -p ~/.php/versions/7.4/bin/
ln -s /usr/bin/php7.4 ~/.php/versions/7.4/bin/php

Now prepend the version directory to the $PATH variable. The system will then find the php binary in /home/$USER/.php/versions/7.4/bin and so will all subscripts.

Tests

php --version
> PHP 7.2.34

PATH="/home/$USER/.php/versions/7.4/bin:$PATH" php --version
> PHP 7.4.22

PATH="/home/$USER/.php/versions/7.4/bin:$PATH" php /usr/local/bin/composer php-version
> PHP 7.4.22

Success!

Since this is an unpleasantly long command, you may create an alias for it.

.bash_aliases

alias php-cli-7.4='PATH="/home/$USER/.php/versions/7.4/bin:$PATH" php'

Tests

php-cli-7.4 --version
> PHP 7.4.22

php-cli-7.4 /usr/local/bin/composer php-version
> PHP 7.4.22

Detect automatically

The most elegant solution for the user is that the script detects the currently used binary and passes the path to the child process by itself.

In your own CLI scripts you may use the $_SERVER['_'] variable to get the path to the current PHP interpreter. Then start all child processes with it as well.

Composer supports this with the special keyword @php. Prepend this keyword in all scripts to use the dynamic process detection, for example "php-version": "@php --version".

Tests

php --version
> PHP 7.2.34

php /usr/local/bin/composer php-version
> PHP 7.2.34

php7.4 --version
> PHP 7.4.22

php7.4 /usr/local/bin/composer php-version
> PHP 7.4.22

Update: Question via Twitter: “Why isn't it sufficient to use Composers platform setting”. There is a common misconception about the functionality of this setting. The platform setting does not change any local PHP paths. It won't have any effect on the execution of child processes. Composer uses this string instead of phpversion() to fetch package dependencies matching this version only. Nothing more. That's also the reason why this is a version string and not a constraint (settings like "config.platform.php:^7.2" are wrong, they should use an exact version like 7.2.34). So the problem described above would still exist, even if Composer fetched a package which matches the given platform version.

⌛ Warning! This post is old. The information may be outdated.

No comments on this notepad. If you would like to add something, then dont hesitate to contact me via E-Mail or Twitter.