发布于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.4,Stable 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.bat和stop_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命令行统一调用脚本
写的太好了,认真学习后解决了我遇到的问题!感谢大神分享。
谢谢。如果只是用FastCGI模式而没有类似FPM来调度的话,实际使用时还有很多问题。另外,如果只是为了短期使用PHP,可以考虑用
php -S开启一个内置的web服务器,参考https://www.php.net/manual/en/features.commandline.webserver.php和https://www.php.net/manual/en/features.commandline.options.php。