同时运行多个版本的PHP并配置Composer:以Windows平台NGINX为例

发布于2019/10/21更新于2024/7/5

前阵子开始考虑在不同PHP版本下测试MyBB的运行情况。由于我的工作机是Windows,为图方便,就使用了XAMPP作为服务套件。在XAMPP中,PHP是以模块形式支持的,配置可同时运行的、多个不同版本的PHP就变得很麻烦。相比之下,以CGI形式提供PHP支持(实际上好像应该是FastCGI),便很容易实现这一点。

我不太了解Apache的FastCGI怎么来做,搜索一下找到这篇文章“Apache, MySQL, PHP server on Windows with multiple, simultaneous PHP versions”。阅读之后,发现在配置FastCGI参数时还是很麻烦,比如php.ini文件位置、环境变量设置等等。而且这篇文章中是直接以FcgidWrapper调用了php-cgi.exe,给我的感觉是如果设置几个站点还好说,如果设置多个站点使用不同版本的PHP,就变得很复杂了。在不知道是否可以以HTTP方式直接接到PHP CGI监听的端口的前提下,我决定换用NGINX。既然是为了开发使用,也就不要在意性能了。

注意:这篇日志为我记录与获取方便而发布;内容中有很多处未涉及严格的考究和对选择的充分讨论,代码部分亦有存在纰漏的可能;请酌情参考,小心使用!

在NGINX中,一般通过FastCGI的方式提供PHP支持。顺便提一下,在PHP方面,除了本身自带的CGI接口,还有FPM(FastCGI Process Manager)可供选择。在Windows平台上为NGINX中配置并使用PHP,在PHP官方看来貌似略显复杂,特意为其提供了一个指向NGINX官方配置的例子“PHP-FastCGI on Windows”。我这篇日志基本也是以这个为基础来写的。

简单来说,在Windows平台之上为NGINX配置多版本PHP支持,就是为不同访问点(IP、域名、路径、文件扩展名等)提供不同的PHP CGI接口,而不同的PHP CGI接口也就是不同的php-cgi.exe的监听端口。

安装NGINX

适用于Windows版本的NGINX有很多,如官方的原生Win API构建版本Cgywin编译版本等。为方便,我选用官方版本。写这篇日志时,最新NGINX版本为:Mainline version 1.17.4Stable version 1.16.1下载页面

实际上,将下载的文件解压至某个文件夹就好了。这里假设,所有有关的文件都存放在C:Programs目录下。这里使用Mainline版本,解压nginx-1.17.4.zip文件至C:Programsnginxnginx-1.17.4目录下。在 C:Programsnginx 目录下新建一个BAT文件start_nginx.bat,文件内容如下:

start nginx.exe

之后,可以在桌面上生成一个快捷方式指向这个BAT文件,用于快速启动NGINX;如有需要在系统启动时同时启动NGINX,可以在Windows的计划任务中添加相应的项目。同时,也可以建立一个快捷方式,用于快速关闭NGINX:

# 快捷方式的“目标”填入:
C:Programsnginxnginx-1.17.4nginx.exe -s stop

# 快捷方式的“起始位置”填入:
C:Programsnginxnginx-1.17.4

此时 C:Programs 目录结构为:

C:Programs
     |- nginx/                     # 可存放不同版本的NGINX的目录
     |   |- nginx-1.17.4/          # nginx 1.17.4
     |   |   |- conf/              # 存放NGINX配置文件
     |   |   |- contrib/           # 一些文件,不操心
     |   |   |- docs/              # 一些文档,不操心
     |   |   |- html/              # 存放NGINX初始指向的Webroot的HTML,不操心
     |   |   |- logs/              # 存放log日志、PID文件等
     |   |   |- temp/              # NGINX自身临时目录,不操心
     |   |   |- nginx.exe          # NGINX主程序
     |   |- start_nginx.bat        # 用于启动NGINX的BAT文件

此时,在没有其他程序占用80端口的情况下,双击刚刚在桌面上建立的快捷方式启动NGINX,在浏览器中转到http://127.0.0.1/即可访问到NGINX的默认页面。

安装PHP for Windows

针对Windows平台,PHP官方提供了原生VC编译版本,访问PHP for Windows门户页面下载页面。写这篇日志时,PHP官方还提供支持的稳定版本是7.1,7.2和7.3。下载前,这里有几个选择需要考虑:

  • VC/VS编译版本:每个大版本的PHP会使用当前主流应用选择的VC/VS版本,如PHP 7.1选用了VC14,7.2和7.3选用了VC15。一般来说大版本内是不会有编译版本更改的,尚在测试未发布稳定版的7.4在早期的测试版中使用了VS19,而在后面的版本(包括未发布的稳定版)则考虑使用VS17,这也是一件有趣的事。
  • 32位或64位:
    • 32位:x86。
    • 64位:x64,这篇日志虽以64位的PHP为例,但对32位的配置是一样的。
  • “线程安全”或“非线程安全”:
    • 线程安全:Thread Safe(TS)。若考虑与Apache/IIS的模块调用方式兼容,则应选择TS。
    • 非线程安全:Non Thread Safe(NTS):若实际运行在FastCGI模式下,NTS则是更好的选择。

一般来说,上面这些考虑选择应该是以以模块方式使用PHP时的情况为准。如在Apache中以模块方式使用PHP,需要PHP的VC/VS编译版本、32/64位、线程安全与Apache的编译版本、32/64位及线程安全等对应。既然这篇日志以FastCGI方式在NGINX中配置并使用PHP,这些选择并不太重要。但最好的实践表明,以FastCGI方式运行PHP,选择非线程安全(NTS)版本的PHP更好。因此,下载以下文件(当前最新的支持版本,更多及已归档的旧版下载页面):

C:Programs 目录下建立PHP文件夹,并将以上文件分别解压至该目录中,子目录分别命名为:

  • php-7.1-nts-Win32-VC14-x64
  • php-7.2-nts-Win32-VC15-x64
  • php-7.3-nts-Win32-VC15-x64

此时目录结构为:

C:Programs
     |- nginx/                          # 可存放不同版本的NGINX的目录
     |   |- nginx-1.17.4/               # NGINX 1.17.4
     |   |- start_nginx.bat             # 用于启动NGINX的BAT文件
     |- PHP/
     |   |- php-7.1-nts-Win32-VC14-x64/        # PHP 7.1
     |   |   |- php.ini                        # PHP 7.1的配置文件
     |   |   |- php.exe                        # PHP 7.1的可执行文件
     |   |   |- php-cgi.exe                    # PHP 7.1的CGI可执行文件
     |   |   |- ...                            # PHP 7.1的其它目录及文件
     |   |- php-7.2-nts-Win32-VC15-x64/        # PHP 7.2
     |   |   |- php.ini                        # PHP 7.2的配置文件
     |   |   |- php.exe                        # PHP 7.2的可执行文件
     |   |   |- php-cgi.exe                    # PHP 7.2的CGI可执行文件
     |   |   |- ...                            # PHP 7.2的其它目录及文件
     |   |- php-7.3-nts-Win32-VC15-x64/        # PHP 7.3
     |   |   |- php.ini                        # PHP 7.3的配置文件
     |   |   |- php.exe                        # PHP 7.3的可执行文件
     |   |   |- php-cgi.exe                    # PHP 7.3的CGI可执行文件
     |   |   |- ...                            # PHP 7.3的其它目录及文件

接下来配置不同版本的PHP,并编写脚本启动/关闭PHP CGI。

配置不同版本的PHP

这里对不同版本的PHP的配置包括两部分:第一部分是配置PHP本身,主要是php.ini文件的配置;第二部分是编写脚本控制不同版本的PHP以CGI方式运行并监听不同的TCP/HTTP端口。

配置php.ini文件

配置php.ini文件一般遵循官方手册或者网上的很多教程就可以了。这里我以php.ini-development文件为蓝本,复制到php.ini文件,修改了下面若干行:

; PHP最长执行时间
max_execution_time = 300

; 指定错误日志
error_log = php_errors.log

; PHP能接受的POST数据最大大小
post_max_size = 128M

; PHP扩展模块的目录
extension_dir = "ext"

; 允许上传文件的总大小
upload_max_filesize = 128M

; 启用扩展
; PHP 7.1及以下扩展模块需要带文件扩展名(.dll),7.2及以上不需要
extension=php_bz2.dll
extension=php_curl.dll
extension=php_fileinfo.dll
extension=php_ftp.dll
extension=php_gd2.dll
extension=php_gettext.dll
extension=php_gmp.dll
extension=php_mbstring.dll
extension=php_exif.dll      ; Must be after mbstring as it depends on it
extension=php_mysqli.dll
extension=php_openssl.dll
extension=php_pdo_mysql.dll
extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll
extension=php_pgsql.dll
extension=php_sqlite3.dll

; 如果需要配置xdebug
; 注意这里是“zend_extension”,表示是PHP内部引擎zend的扩展
zend_extension = C:ProgramsPHPphp-X.Y-nts-Win32-VC??-x64extphp_xdebug-x.y.z-X.Y-vc??-nts-x86_64.dll
xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_host=localhost
xdebug.remote_port=19000      ; 默认端口号是9000

需要为每一个版本的PHP配置php.ini文件。

配置PHP以CGI模式运行并监听端口

根据“PHP-FastCGI on Windows”的介绍,运行php-cgi.exe并监听本地某个端口就可以了。这里用到Lighttpd提供的一个助手程序RunHiddenConsole(可将控制台程序运行于后台),将其解压到C:ProgramsPHP目录下。启动PHP CGI进程的命令如下(127.0.0.1是监听的网络接口,9123是监听的网络端口):

C:ProgramsPHPRunHiddenConsole.exe C:ProgramsPHPphp-X.Y-nts-Win32-VC??-x64php-cgi.exe -b 127.0.0.1:9123

启动/停止PHP CGI的脚本

我将所有PHP的CGI进程监听在127.0.0.1,端口以20000为起始,最后两位代表PHP版本中X.Y.Z的X和Y,即PHP 7.2将监听在20072端口。启动PHP的CGI进程的BAT脚本start_php.bat的内容为(包含了已不再被官方支持的5.6和7.0版本):

@ECHO OFF
ECHO Starting PHP FastCGI...

SETLOCAL ENABLEEXTENSIONS
SET ME=%~n0
SET PARENT=%~dp0
SET PHP_PATH_PARENT=C:ProgramsPHP
SET USE_PHP_NTS=1

REM SPAWN new PHP child process. Only available for PHP 7.1+
SET PHP_FCGI_CHILDREN=8

IF "%USE_PHP_NTS%"=="1" (
	ECHO Using non thread safe build...
	SET PHP_NTS=-nts
)

REM Keep PHP 5.6 & 7.0 for compatibility.
SET PHP56=php-5.6.40%PHP_NTS%-Win32-VC11-x64
SET PHP70=php-7.0.33%PHP_NTS%-Win32-VC14-x64

SET PHP71=php-7.1%PHP_NTS%-Win32-VC14-x64
SET PHP72=php-7.2%PHP_NTS%-Win32-VC15-x64
SET PHP73=php-7.3%PHP_NTS%-Win32-VC15-x64

ECHO Starting PHP 5.6 (%PHP56%) FastCGI...
set PHP_PATH=%PHP_PATH_PARENT%%PHP56%
set PHP_PORT=20056
set PATH=%PHP_PATH%;%PATH%
%~dp0RunHiddenConsole.exe %PHP_PATH%php-cgi.exe -b 127.0.0.1:%PHP_PORT%

ECHO Starting PHP 7.0 (%PHP70%) FastCGI...
set PHP_PATH=%PHP_PATH_PARENT%%PHP70%
set PHP_PORT=20070
set PATH=%PHP_PATH%;%PATH%
%~dp0RunHiddenConsole.exe %PHP_PATH%php-cgi.exe -b 127.0.0.1:%PHP_PORT%

ECHO Starting PHP 7.1 (%PHP71%) FastCGI...
set PHP_PATH=%PHP_PATH_PARENT%%PHP71%
set PHP_PORT=20071
set PATH=%PHP_PATH%;%PATH%
%~dp0RunHiddenConsole.exe %PHP_PATH%php-cgi.exe -b 127.0.0.1:%PHP_PORT%

ECHO Starting PHP 7.2 (%PHP72%) FastCGI...
set PHP_PATH=%PHP_PATH_PARENT%%PHP72%
set PHP_PORT=20072
set PATH=%PHP_PATH%;%PATH%
%~dp0RunHiddenConsole.exe %PHP_PATH%php-cgi.exe -b 127.0.0.1:%PHP_PORT%

ECHO Starting PHP 7.3 (%PHP73%) FastCGI...
set PHP_PATH=%PHP_PATH_PARENT%%PHP73%
set PHP_PORT=20073
set PATH=%PHP_PATH%;%PATH%
%~dp0RunHiddenConsole.exe %PHP_PATH%php-cgi.exe -b 127.0.0.1:%PHP_PORT%

注意这里的PHP_FCGI_CHILDREN=8的环境变量将提示php-cgi.exe按需生成8个子进程。虽然早在PHP 5.x时代,在Unix系统下的PHP就可以根据PHP_FCGI_CHILDREN环境变量自动产生新PHP进程[参考NGINX文档],但在Windows系统下,直到PHP 7.1之后才会识别这个环境变量[参考迁移至PHP 7.1的文档]。因此,上面批处理文件中的PHP 5.6和PHP 7.0是只有一个CGI进程监听对应端口的。这里,还有一个spawn-cgi可以用来生成多个CGI进程,但似乎需要自己编译,暂时不写进来。

相比之下,停止PHP CGI的脚本就简单的多了。不需要考虑该关哪个,直接全部杀掉,BAT脚本stop_php.bat的内容如下:

@ECHO OFF
ECHO Stopping PHP FastCGI...

taskkill /im "php-cgi.exe" /f /t
exit

taskkill的参数/t表示终止该进程及其子进程。

将以上start_php.batstop_php.bat两个脚本文件保存在C:ProgramsPHP目录下。此时目录结构为:

C:Programs
     |- nginx/                     # 可存放不同版本的NGINX的目录
     |   |- nginx-1.17.4/          # NGINX 1.17.4
     |   |- start_nginx.bat        # 用于启动NGINX的BAT文件
     |- PHP/
     |   |- php-7.1-nts-Win32-VC14-x64/        # PHP 7.1
     |   |   |- ...                            # PHP 7.1的目录及文件
     |   |- php-7.2-nts-Win32-VC15-x64/        # PHP 7.2
     |   |   |- ...                            # PHP 7.2的目录及文件
     |   |- php-7.3-nts-Win32-VC15-x64/        # PHP 7.3
     |   |   |- ...                            # PHP 7.3的目录及文件
     |   |- RunHiddenConsole.exe    # 助手程序
     |   |- start_php.bat           # 启动PHP CGI的脚本
     |   |- stop_php.bat            # 停止PHP CGI的脚本

在NGINX中为不同站点配置不同版本的PHP支持

到这里就很简单了,只需要按照NGINX官方给的例子[PHP-FastCGI on Windows],将不同站点内的fastcgi_pass设置部分替换为对应的IP:port即可。如:

  • 站点php72.mydev.test下所有PHP脚本使用PHP 7.2。
  • 站点www.mydev.test下路径/php73下PHP脚本使用PHP 7.3,路径/php71下PHP脚本使用PHP 7.1。

则NGINX配置文件server块部分:

# 站点php72.mydev.test
server {
    # ... 其它必要配置

    # name-based 站点识别
    server_name php72.mydev.test;
    index index.php;

    # PHP配置,使用PHP 7.2
    location ~* .php$ {
        fastcgi_pass   127.0.0.1:20072;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
   }
}

# 站点www.mydev.test
server {
    # ... 其它必要配置

    # name-based 站点识别
    server_name www.mydev.test;
    index index.php;

    # 路径/php73/
    location /php73/ {
        # PHP配置,使用PHP 7.3
        location ~* .php$ {
            fastcgi_pass   127.0.0.1:20073;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
       }
    }

    # 路径/php71/
    location /php71/ {
        # PHP配置,使用PHP 7.1
        location ~* .php$ {
            fastcgi_pass   127.0.0.1:20071;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
       }
    }
}

为不同版本的PHP设置便捷的命令行调用

在命令行中,使用统一的命令加不同的版本为参数来快捷调用PHP,在多个PHP版本共存时,多少还是会方便一些。否则,如果只有一个PHP版本,只需要将php.exe所在路径加入到PATH环境变量中就可以了。希望达到的调用方式是:

REM 使用PHP 7.1执行PHP
php 7.1

REM 使用PHP 7.2执行PHP带参数“-v”
php 7.2 -v

REM 使用PHP 7.3执行当前目录下的“hi.php”文件
php 7.3 hi.php

下面的批处理脚本生硬地照搬版本号参数之后的命令行参数,因此最多只能有8个有效的命令行参数。将以下脚本内容存为php.bat,存放在C:ProgramsPHP目录下:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS EnableDelayedExpansion
SET ME=%~n0
SET PARENT=%~dp0
SET PHP_PATH_PARENT=C:ProgramsPHP
SET USE_PHP_NTS=1

IF "%USE_PHP_NTS%"=="1" (
	ECHO Using non thread safe build...
	SET PHP_NTS=-nts
)

SET PHP56=php-5.6.40%PHP_NTS%-Win32-VC11-x64
SET PHP70=php-7.0.33%PHP_NTS%-Win32-VC14-x64
SET PHP71=php-7.1%PHP_NTS%-Win32-VC14-x64
SET PHP72=php-7.2%PHP_NTS%-Win32-VC15-x64
SET PHP73=php-7.3%PHP_NTS%-Win32-VC15-x64

:: 1st parameter is PHP version
SET PHP_VER=%1
:: 2nd parameter is PHP command
SET PHP_CMD=%~2

IF "%PHP_VER%"=="5.6" (
	SET PHP_VER_NO=56
) ELSE IF "%PHP_VER%"=="7.0" (
	SET PHP_VER_NO=70
) ELSE IF "%PHP_VER%"=="7.1" (
	SET PHP_VER_NO=71
) ELSE IF "%PHP_VER%"=="7.2" (
	SET PHP_VER_NO=72
) ELSE (
	SET PHP_VER_NO=73
)

set PHP_PATH=%PHP_PATH_PARENT%!PHP%PHP_VER_NO%!
set PHP_EXEC=%PHP_PATH%php.exe
set PATH=%PHP_PATH%;%PHP_PATH%bin

echo %PATH%
echo call "%PHP_EXEC%" %2 %3 %4 %5 %6 %7 %8 %9
call "%PHP_EXEC%" %2 %3 %4 %5 %6 %7 %8 %9

将路径C:ProgramsPHP加入到当前用户的PATH环境变量中[参考],即可在任意目录中直接以“php 版本号 参数/文件”以不同版本PHP执行命令了。此时的目录结构为:

C:Programs
     |- nginx/                     # 可存放不同版本的NGINX的目录
     |   |- nginx-1.17.4/          # NGINX 1.17.4
     |   |- start_nginx.bat        # 用于启动NGINX的BAT文件
     |- PHP/
     |   |- php-7.1-nts-Win32-VC14-x64/        # PHP 7.1
     |   |   |- ...                            # PHP 7.1的目录及文件
     |   |- php-7.2-nts-Win32-VC15-x64/        # PHP 7.2
     |   |   |- ...                            # PHP 7.2的目录及文件
     |   |- php-7.3-nts-Win32-VC15-x64/        # PHP 7.3
     |   |   |- ...                            # PHP 7.3的目录及文件
     |   |- RunHiddenConsole.exe    # 助手程序
     |   |- start_php.bat           # 启动PHP CGI的脚本
     |   |- stop_php.bat            # 停止PHP CGI的脚本
     |   |- php.bat                 # 不同版本PHP的PHP命令行统一调用脚本

配置Composer并为其设置便捷的命令行调用

按照Composer下载页面给出的方法,直接在每个PHP版本的目录下依次执行“php …”命令即可完成下载和安装。之后配置以不同版本PHP调用运行Composer命令,方法与上一小节类似,希望达到的调用方式是:

REM 使用PHP 7.1执行composer命令
composer 7.1

REM 使用PHP 7.2执行composer命令并带参数“-v”
composer 7.2 -v

REM 使用PHP 7.3执行composer命令“composer install”
composer 7.3 install

将下面批处理脚本保存为composer.bat,存放在C:ProgramsPHP目录下:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS EnableDelayedExpansion
SET ME=%~n0
SET PARENT=%~dp0
SET PHP_PATH_PARENT=C:ProgramsPHP
SET USE_PHP_NTS=1

IF "%USE_PHP_NTS%"=="1" (
	ECHO Using non thread safe build...
	SET PHP_NTS=-nts
)

SET PHP56=php-5.6.40%PHP_NTS%-Win32-VC11-x64
SET PHP70=php-7.0.33%PHP_NTS%-Win32-VC14-x64
SET PHP71=php-7.1%PHP_NTS%-Win32-VC14-x64
SET PHP72=php-7.2%PHP_NTS%-Win32-VC15-x64
SET PHP73=php-7.3%PHP_NTS%-Win32-VC15-x64

:: 1st parameter is PHP version
SET PHP_VER=%1
:: 2nd parameter is PHP command
SET PHP_CMD=%~2

IF "%PHP_VER%"=="5.6" (
	SET PHP_VER_NO=56
) ELSE IF "%PHP_VER%"=="7.0" (
	SET PHP_VER_NO=70
) ELSE IF "%PHP_VER%"=="7.1" (
	SET PHP_VER_NO=71
) ELSE IF "%PHP_VER%"=="7.2" (
	SET PHP_VER_NO=72
) ELSE (
	SET PHP_VER_NO=73
)

set PHP_PATH=%PHP_PATH_PARENT%!PHP%PHP_VER_NO%!
set PHP_EXEC=%PHP_PATH%php.exe
set PHP_COMPOSER_EXEC=%PHP_PATH%bincomposer.phar

set PATH=%PHP_PATH%;%PHP_PATH%bin

echo call "%PHP_EXEC%" %PHP_COMPOSER_EXEC% %2 %3 %4 %5 %6 %7 %8 %9
call "%PHP_EXEC%" %PHP_COMPOSER_EXEC% %2 %3 %4 %5 %6 %7 %8 %9

上一节已经将该目录加入了PATH环境变量中,因此在任何路径下都可以直接以“composer 版本号 参数/命令”以不同版本PHP执行composer的命令了 。此时目录结构为:

C:Programs
     |- nginx/                     # 可存放不同版本的NGINX的目录
     |   |- nginx-1.17.4/          # NGINX 1.17.4
     |   |- start_nginx.bat        # 用于启动NGINX的BAT文件
     |- PHP/
     |   |- php-7.1-nts-Win32-VC14-x64/        # PHP 7.1
     |   |   |- ...                            # PHP 7.1的目录及文件
     |   |- php-7.2-nts-Win32-VC15-x64/        # PHP 7.2
     |   |   |- ...                            # PHP 7.2的目录及文件
     |   |- php-7.3-nts-Win32-VC15-x64/        # PHP 7.3
     |   |   |- ...                            # PHP 7.3的目录及文件
     |   |- RunHiddenConsole.exe    # 助手程序
     |   |- start_php.bat           # 启动PHP CGI的脚本
     |   |- stop_php.bat            # 停止PHP CGI的脚本
     |   |- php.bat                 # 不同版本PHP的PHP命令行统一调用脚本
     |   |- composer.bat            # 不同版本PHP的Composer命令行统一调用脚本

2 thoughts on “同时运行多个版本的PHP并配置Composer:以Windows平台NGINX为例”

  1. 写的太好了,认真学习后解决了我遇到的问题!感谢大神分享。

Leave a Reply

Your email address will not be published. Required fields are marked *