PHP 多版本环境管理指南(phpenv 教程)

AI摘要
本文分享了在Ubuntu虚拟机中使用phpenv管理多版本PHP开发环境的配置过程。作者针对同时维护多个不同PHP版本项目的痛点,详细介绍了从安装phpenv和php-build工具、配置编译选项、设置pecl自动处理、安装编译依赖,到自动配置PHP-FPM服务及最终使用方法的完整步骤。这是一篇典型的【知识分享】型技术教程,旨在提高多版本PHP项目的开发效率。

谁懂这种痛苦?

在 OrbStack 的 Ubuntu 虚拟机里开发 PHP 项目,经常需要同时维护几个不同版本的项目:

  • 项目A还停留在7.4
  • 项目B刚升级到8.1
  • 新项目已经跑在8.3

以前的做法要么在 Ubuntu 里装一堆版本,用php7.4、php8.1这样的命令手动指定,要么用 update-alternatives 切全局版本。时间长了很容易敲错,尤其是那些半年不碰一次的边缘项目,突然要进行修改完全想不起当时用的是哪个版本,还得去翻历史记录或服务器配置。

后来我把 phpenv 整起来了,现在进项目目录自动切换版本,用起来顺手多了。下面把整个配置过程记录一下,基本都是一次性搞定,后续装新版本几乎不用再动。


1.安装 phpenv 和 php-build

先把工具和 php-build 插件拉下来,我这里命令行用到是 zsh,顺便把环境变量设置上。

Bash

# 克隆 phpenv git clone https://github.com/phpenv/phpenv.git ~/.phpenv echo 'export PATH="$HOME/.phpenv/bin:$PATH"' >> ~/.zshrc echo 'eval "$(phpenv init -)"' >> ~/.zshrc source ~/.zshrc # 安装 php-build 插件,php 多版本编译主要靠这个插件 git clone https://github.com/php-build/php-build $(phpenv root)/plugins/php-build 

2. 设置默认编译选项

php-build 默认的编译参数比较保守,我直接改成一套常用的扩展全开,顺带把 PEAR 也装上,后面用 pecl 就方便了。这个配置也可以根据自己的需求自行调整

# 找到配置文件 cd ~/.phpenv/plugins/php-build/share/php-build vim default_configure_options # 直接把下面这段贴进去,覆盖原来的 # 在 PHP 8+ 中,--with-pear 可能不再自动安装 PEAR/PECL,需要手动安装 --with-pear --enable-sockets --enable-exif --with-zlib --with-zlib-dir=/usr --with-bz2 --enable-intl --with-openssl --enable-soap --enable-xmlreader --with-xsl --enable-ftp --enable-cgi --with-curl=/usr --with-tidy --with-xmlrpc --enable-sysvsem --enable-sysvshm --enable-shmop --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-pdo-sqlite --enable-pcntl --with-readline --enable-mbstring --disable-debug --enable-fpm --enable-bcmath --enable-phpdbg 

3. pecl 自动生成 ini 文件

默认情况下用 pecl install 装扩展后需要手动建 ini 文件,容易忘。装这个插件可以自动处理。
在 PHP 8+ 中,–with-pear 可能不再自动安装 PEAR/PECL,需要手动安装。

# 安装插件 git clone https://github.com/sergeyklay/phpenv-pear-setup.git ~/.phpenv/plugins/phpenv-pear-setup # 每次新增 php 版本后跑一下这个 phpenv pear-setup phpenv rehash 

4. 安装编译依赖

如果是新启动的 Ubuntu php 版本编译时使用到的依赖是缺失的,如果这时候直接去安装 php 版本会报错,提示依赖库不存在,这个是需要使用到依赖,可以提前安装一下或者碰到缺少哪个依赖再去安装哪个依赖库也行

sudo apt update sudo apt install -y autoconf automake libtool bison re2c pkg-config build-essential bzip2 libbz2-dev libxml2-dev libxslt1-dev libssl-dev libcurl4-openssl-dev libpng-dev libjpeg-turbo8-dev libicu-dev libreadline-dev libsqlite3-dev libonig-dev libzip-dev libtidy-dev 

5. 自动配置 PHP-FPM(不使用 FPM 可以忽略)

phpenv 只管 PHP 二进制,不处理 FPM 的启动和 socket。我写了个 after-install 脚本,装完版本后自动:

  • 创建 socket 目录
  • 修改 www.conf 使用 unix socket
  • 生成并启用systemd user服务

创建目录:mkdir -p ~/.phpenv/plugins/php-build/share/php-build/after-install.d
创建脚本:vim ~/.phpenv/plugins/php-build/share/php-build/after-install.d/setup-fpm

#!/usr/bin/env bash set -e # 动态获取 PHP 路径和版本 PHP_PREFIX="$PREFIX" VERSION="$(basename "$PHP_PREFIX")" USER_NAME=$(whoami) # 定义路径 SOCKET_DIR="$PHP_PREFIX/var/run" SOCKET_PATH="$SOCKET_DIR/php$VERSION.sock" SYSTEMD_DIR="$HOME/.config/systemd/user" SERVICE_NAME="phpenv-${VERSION}-fpm.service" SERVICE_PATH="$SYSTEMD_DIR/$SERVICE_NAME" echo "=== 正在自动配置 PHP-FPM $VERSION ===" # 创建目录 mkdir -p "$SOCKET_DIR" mkdir -p "$SYSTEMD_DIR" # 修改 FPM 配置,改用 Unix Socket 并修正权限 POOL_CONF="$PHP_PREFIX/etc/php-fpm.d/www.conf" if [ -f "$POOL_CONF" ]; then sed -i "s|^listen = .*|listen = $SOCKET_PATH|" "$POOL_CONF" sed -i "s|;listen.owner = .*|listen.owner = $USER_NAME|" "$POOL_CONF" sed -i "s|;listen.group = .*|listen.group = $USER_NAME|" "$POOL_CONF" sed -i "s|;listen.mode = .*|listen.mode = 0666|" "$POOL_CONF" fi # 写入 Systemd User Service cat > "$SERVICE_PATH" <<EOF [Unit] Description=PHP-FPM $VERSION (phpenv) After=network.target [Service] ExecStart=$PHP_PREFIX/sbin/php-fpm -F -y $PHP_PREFIX/etc/php-fpm.conf Restart=always [Install] WantedBy=default.target EOF # 注册并启动 systemctl --user daemon-reload systemctl --user enable "$SERVICE_NAME" systemctl --user restart "$SERVICE_NAME" echo "=== PHP-FPM $VERSION 启动成功 ===" echo "Socket: $SOCKET_PATH" 

记得给执行权限: chmod +x ~/.phpenv/plugins/php-build/share/php-build/after-install.d/setup-fpm


6. 使用方式

现在你想装一个 PHP 8.2.29,只需要: phpenv install 8.2.29
安装过程会自动触发脚本,完成FPM配置并启动服务。在Nginx里直接配置:

fastcgi_pass unix:/home/youruser/.phpenv/versions/8.2.29/var/run/php8.2.29.sock;

项目里切换版本,到项目目录下,执行下面这个命令就可以,之后在这个项目目录下执行 php 命令自动指向这个 8.2.29 版本了。

# 设置当前目录 php 版本,会生成一个 .phpenv 文件 phpenv local 8.2.29 # 验证设置是否成功,会输出当前目录下使用的 php 版本 phpenv version
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 6

补充一个 PHP8+ 版本自动安装 PECL 脚本

在这个目录下创建脚本即可 ~/.phpenv/plugins/php-build/share/php-build/after-install.d

#!/usr/bin/env bash PHP_PREFIX="$1" [ -z "$PHP_PREFIX" ] && PHP_PREFIX="$PREFIX" VERSION="$(basename "$PHP_PREFIX")" # 检查 pear 是否已安装在正确位置 if [ -f "$PHP_PREFIX/bin/pear" ]; then echo "PEAR 已经在正确位置,跳过。" exit 0 fi TEMP_PEAR="/tmp/go-pear-${VERSION}.phar" curl -f -L -sS https://pear.php.net/go-pear.phar -o "$TEMP_PEAR" echo "=== [Hook] 正在通过 Expect 强制安装 PEAR 到 $PHP_PREFIX ===" # 使用 expect 模拟人工操作 /usr/bin/expect <<EOD set timeout 30 spawn "$PHP_PREFIX/bin/php" "$TEMP_PEAR" # 1. 看到菜单,输入 1 改 Installation base expect "1-12, 'all' or Enter to continue:" send "1\r" # 2. 输入实际的安装路径 expect "Installation base" send "$PHP_PREFIX\r" # 3. 再次看到菜单,确认路径已改,输入 4 改 Binaries directory expect "1-12, 'all' or Enter to continue:" send "4\r" # 4. 输入实际的 bin 路径 expect "Binaries directory" send "$PHP_PREFIX/bin\r" # 5. 回车确认并开始安装 expect "1-12, 'all' or Enter to continue:" send "\r" # 6. 看到是否修改 php.ini,输入 Y expect "Would you like to alter php.ini" send "Y\r" # 7. 看到 Press Enter to continue,最后按一下回车 expect "Press Enter to continue:" send "\r" expect eof EOD rm -f "$TEMP_PEAR" # 刷新 phpenv phpenv rehash echo "=== [Hook] PEAR 强制安装完成! ==="
1个月前 评论

感谢分享, 一直在用 update-alternatives, 还在想哪天换一个类似rbenv之类的. 这个很全, 太赞了!

1个月前 评论

为啥不直接上docker?使用dnmp或者Laradock不是更简单?

3周前 评论

@她来听我的演唱会

其实我最开始也是用 Laradock 的。单项目的时候,开箱即用、隔离性强,确实很香。

但后来参与的项目多了,各种微服务并行,开发环境得同时兼容 PHP 7.4、8.1 和 8.3,且不全是 Laravel 项目,Laradock 就显得有些力不从心了:

  • 切换成本高: 一开始切换不频繁的时候尝试在 Laradock 里修改 .env 再 rebuild 来切换版本。但 rebuild 耗时极长,尤其是安装扩展时,一旦遇到网络波动,心态直接炸裂。

  • 维护和使用太繁琐: 后来尝试魔改 docker-compose.yml ,硬塞进多个 PHP-FPM(如 php74-fpm、php81-fpm),也用过 Hyperf 官方容器。虽然能用,但最烦的还是装扩展(比如 tideways),得在 workspace 和每个 php-fpm 容器里都折腾一遍;要不然就是得重新 rebuild;而且 Hyperf 这种常驻内存的项目,总要不停地 docker exec 进进出出,体验很割裂。

  • 手动切换笨重: 中间也折腾过“虚拟机 + update-alternatives ”,手动切来切去。不仅版本号难记,总觉得差点意思,切换起来很不顺手。

于是我就在想,PHP 有没有像 Node.js nvm 那样丝滑的多版本管理工具,后来找到了 phpenv 和 phpbrew 。

针对我个人“多版本共存 + 快速切换 + 轻量化”的需求, phpenv 是目前用着最顺手的,用了快两年了,就写篇文章分享一下自己这个方案。 适合自己的才是最好的,给大家提供多一种可能性。

3周前 评论

这个不错,虽然目前在windows下开发,收藏以备后用

3周前 评论