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 协议》,转载必须注明作者和本文链接
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 2

补充一个 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 强制安装完成! ==="
13小时前 评论

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

5小时前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!