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.
{
"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.
Update: I published a package which overrides the PHP version used in a shell
session by reading the desired version from a .php-version
file. It was
inspired by scripts like nvm use
and php-version
. Check it out:
php-version-pickup.