iTranslated by AI
Switching PHP versions with direnv + brew-php-switcher
What's this?
I use Homebrew to install and manage multiple PHP versions. I also tried phpenv, but I got tired of finding and installing dependencies, so I didn't stick with it... I wish I could manage PHP versions on a per-project basis, just like with Python or JavaScript... After reading PHP: the Right Way for the first time in years, I saw brew-php-switcher introduced. It looked like it would do the job, so I paired it with direnv.
Requirements
- Mac
- Homebrew
- direnv
- brew-php-switcher
Currently, my setup looks like this. The php entry without a version specified is PHP 8.1.
brew list | grep php
brew-php-switcher
php
php@8.0
.envrc
Next, write the following into your .envrc.
Since I only use PHP versions 8.0 and 8.1, it looks like the snippet below.
Please modify the grep pattern part as needed.
While it would be even cleaner to write this as a function in your direnvrc and simply call it from .envrc, I don't feel I need that level of abstraction in my current environment yet, so I will write it directly in each .envrc.
if type brew-php-switcher &>/dev/null; then
actual_php_version=$(/usr/local/bin/php --version \
| grep -m 1 -o "PHP 8.[0-9]" \
| cut -d' ' -f2)
expect_php_version=$(jq -r .require.php <composer.json \
| cut -d'|' -f1 \
| grep -o "8\.[0-9]")
[[ "$actual_php_version" != "$expect_php_version" ]] && brew-php-switcher "$expect_php_version" -s
fi
Verification
Let's test whether the version switches when opening a project that uses PHP 8.0 while PHP 8.1 is currently active.
jq -r .require.php <composer.json | grep -o "8.[0-9]"
8.0
❯ php -v
PHP 8.1.2 (cli) (built: Jan 21 2022 04:47:46) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.2, Copyright (c) Zend Technologies
with Zend OPcache v8.1.2, Copyright (c), by Zend Technologies
Update the .envrc and run direnv allow.
direnv allow
direnv: loading ~/src/github.com/watarukura/php-sample-project/.envrc
direnv: ([/usr/local/bin/direnv export fish]) is taking a while to execute. Use CTRL-C to give up.
Switching to php@8.0
Switching your shell
Unlinking /usr/local/Cellar/php@8.0/8.0.15.reinstall... 0 symlinks removed.
Unlinking /usr/local/Cellar/php/8.1.2.reinstall... 24 symlinks removed.
Linking /usr/local/Cellar/php@8.0/8.0.15.reinstall... 25 symlinks created.
If you need to have this software first in your PATH instead consider running:
echo 'fish_add_path /usr/local/opt/php@8.0/bin' >> ~/.config/fish/config.fish
echo 'fish_add_path /usr/local/opt/php@8.0/sbin' >> ~/.config/fish/config.fish
All done!
direnv: export ~XPC_SERVICE_NAME
php -v
PHP 8.0.15 (cli) (built: Jan 21 2022 04:49:41) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.15, Copyright (c) Zend Technologies
with Zend OPcache v8.0.15, Copyright (c), by Zend Technologies
It switched successfully!
Let's try running it again.
❯ direnv allow
direnv: loading ~/src/github.com/watarukura/php-sample-project/.envrc
direnv: export ~XPC_SERVICE_NAME
Nothing happened. It seems to be a success.
References
I used to use a Fish script I wrote to manually switch between PHP 8.0 and 8.1, but now that it's automated, I no longer need it.
This should also make switching to the upcoming PHP 8.2 and later versions hassle-free.
I'll leave it here as a memorial.
function php80
if contains "8.1" (/usr/local/bin/php --version | /usr/local/bin/ggrep -m 1 -oP "PHP 8.[0-9]" | cut -d' ' -f2)
brew unlink php && brew link --force --overwrite php@8.0
else
echo "already php8.0"
end
end
function php81
if contains "8.0" (/usr/local/bin/php --version | /usr/local/bin/ggrep -m 1 -oP "PHP 8.[0-9]" | cut -d' ' -f2)
brew unlink php@8.0 && brew link --force --overwrite php
else
echo "already php8.1"
end
end
2022/03/06 Update
When I tried to pecl install, I got an error from PECL and phpize saying /usr/local/Cellar/php@8.0/8.0.16/bin/php is not found, so I looked into the cause.
It seems that when you reinstall with Homebrew, it gets installed into a directory named version_number.reinstall.
Updating the paths in pecl and phpize made it work successfully.
There might be a better way, though...
❯ which php
/usr/local/bin/php
❯ ls -l /usr/local/bin/php
lrwxr-xr-x 1 watarukura admin 42 3 2 05:58 /usr/local/bin/php -> ../Cellar/php@8.0/8.0.16.reinstall/bin/php
❯ which pecl
/usr/local/bin/pecl
❯ which phpize
/usr/local/bin/phpize
❯ sudo sed -i.org -e 's/8.0.16/8.0.16.reinstall/g' /usr/local/bin/pecl
❯ sudo sed -i.org -e 's/8.0.16/8.0.16.reinstall/g' /usr/local/bin/phpize
❯ diff -u /usr/local/bin/pecl.org /usr/local/bin/pecl
--- /usr/local/bin/pecl.org 2022-03-06 13:27:42.073462374 +0900
+++ /usr/local/bin/pecl 2022-03-02 15:57:12.987867522 +0900
@@ -4,10 +4,10 @@
if test "x$PHP_PEAR_PHP_BIN" != "x"; then
PHP="$PHP_PEAR_PHP_BIN"
else
- if test "/usr/local/Cellar/php@8.0/8.0.16/bin/php" = '@'php_bin'@'; then
+ if test "/usr/local/Cellar/php@8.0/8.0.16.reinstall/bin/php" = '@'php_bin'@'; then
PHP=php
else
- PHP="/usr/local/Cellar/php@8.0/8.0.16/bin/php"
+ PHP="/usr/local/Cellar/php@8.0/8.0.16.reinstall/bin/php"
fi
fi
@@ -16,12 +16,12 @@
INCDIR=$PHP_PEAR_INSTALL_DIR
INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
else
- if test "/usr/local/Cellar/php@8.0/8.0.16/share/php@8.0/pear" = '@'php_dir'@'; then
+ if test "/usr/local/Cellar/php@8.0/8.0.16.reinstall/share/php@8.0/pear" = '@'php_dir'@'; then
INCDIR=`dirname $0`
INCARG=""
else
- INCDIR="/usr/local/Cellar/php@8.0/8.0.16/share/php@8.0/pear"
- INCARG="-d include_path=/usr/local/Cellar/php@8.0/8.0.16/share/php@8.0/pear"
+ INCDIR="/usr/local/Cellar/php@8.0/8.0.16.reinstall/share/php@8.0/pear"
+ INCARG="-d include_path=/usr/local/Cellar/php@8.0/8.0.16.reinstall/share/php@8.0/pear"
fi
fi
❯ diff -u /usr/local/bin/phpize.org /usr/local/bin/phpize
--- /usr/local/bin/phpize.org 2022-03-06 13:27:16.089971124 +0900
+++ /usr/local/bin/phpize 2022-03-02 15:57:49.274987158 +0900
@@ -1,8 +1,8 @@
#!/bin/sh
# Variable declaration
-prefix='/usr/local/Cellar/php@8.0/8.0.16'
-datarootdir='/usr/local/Cellar/php@8.0/8.0.16/share'
+prefix='/usr/local/Cellar/php@8.0/8.0.16.reinstall'
+datarootdir='/usr/local/Cellar/php@8.0/8.0.16.reinstall/share'
exec_prefix="`eval echo ${prefix}`"
phpdir="`eval echo ${exec_prefix}/lib/php`/build"
includedir="`eval echo ${prefix}/include`/php"
@@ -152,7 +152,7 @@
phpize_replace_prefix()
{
$SED \
- -e "s#/usr/local/Cellar/php@8.0/8.0.16#$prefix#" \
+ -e "s#/usr/local/Cellar/php@8.0/8.0.16.reinstall#$prefix#" \
< "$phpdir/phpize.m4" > configure.ac
}
I just learned that both PECL and phpize are shell scripts.
❯ pecl install --configureoptions 'enable-sockets="yes" enable-openssl="yes" enable-http2="yes" enable-mysqlnd="yes" enable-swoole-json="yes" enable-swoole-curl="yes"' openswoole-4.10.0
❯ php --ri openswoole
openswoole
Open Swoole => enabled
Author => Open Swoole Group <hello@openswoole.com>
Version => 4.10.0
Built => Mar 6 2022 13:08:25
coroutine => enabled with boost asm context
kqueue => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 1.1.1m 14 Dec 2021
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
pcre => enabled
zlib => 1.2.11
brotli => E16777225/D16777225
mysqlnd => enabled
async_redis => enabled
Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 262144 => 262144
I tried installing openswoole. It looks like it was installed successfully.
Discussion