iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🦁

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