看到这个标题,有人可能会想吐槽:
你他娘的写了这么久 PHP,怎么现在想起来搭建开发环境了?
呃,情况呢是这么个情况,我之前开发的那个 PHP 项目 (blessing-skin-server) 在两个月前发布 v3.5.0 版本后基本就已经告一段落了。虽然我本意是不再更新(弃坑的委婉说法),因为当前的版本已经足够完善,该有的东西都有了(而且说实话搞了这么久我也腻味,不仅是对这个程序,还有对国内 Minecraft 开发生态以及用户群体的失望)。
不过我的朋友 g-plane 说他愿意接坑,所以现在这个项目的后续开发都是他在搞。而我也乐得清闲,做个甩手掌柜 搞别的 去了。当我摸鱼正快活时,他过来联系我说准备发布 4.0.0-alpha 了,我才想起来这茬:「啊,我连新版本长啥样都还不知道呢!😂」于是急急忙忙 pull 了新代码准备 review 一下,却发现我的新笔电上甚至压根儿就没安装 PHP 开发环境,只能说是非常地真实。
因为 某些原因,我不想继续使用那些 PHP 一键包来搭建开发环境了,所以这次我打算全部自己来。本文记录了我手动安装配置 PHP + Nginx/Apache 开发环境的过程,希望能帮到后来人。
注意,本文中的配置适用于本地开发环境,应用至生产环境时要注意哦。
2020/03/03 更新:
你也可以直接用 scoop 来管理 WNMP 开发环境。
安装:
scoop install nginx mariadb php
启动:
nginx -p %NGINX_HOME% mysqld --standalone php-cgi -b 127.0.0.1:9000
配置或数据文件的位置:
C:\Users\prin\scoop\apps\nginx\current\conf C:\Users\prin\scoop\apps\mariadb\current\data C:\Users\prin\scoop\apps\php\current\cli
比起手动下载方便不少。
0x01 安装 Nginx / Apache
如果你用的是 Nginx:
- 去 官网 下载 Windows 版的 Nginx(我下载的是
nginx-1.14.0.zip
); - 解压至你喜欢的地方(我放在
E:\environment\nginx
里); - 直接双击运行
nginx.exe
; - 如果能正常访问
http://localhost:80
,就可以进行下一步了。
推荐修改的 nginx.conf
配置如下:
# 可以适当调高,但不要超过 CPU 核心数量
worker_processes 4;
events {
# 开发环境下不用太考虑 worker 最大并发连接数
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 启用 gzip
gzip on;
gzip_disable "MSIE [1-6].(?!.*SV1)";
gzip_http_version 1.1;
gzip_vary on;
gzip_proxied any;
gzip_min_length 1000;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/x-javascript application/xml application/xml+rss;
# 这里面的内容等 0x03 再细说
include vhosts.conf;
}
如果你用的是 Apache:
- 因为 Apache 官网并不提供 Windows 版的构建下载,所以需要去 Apache Lounge 或者其他 官方推荐的站点 下载预编译二进制包(我下载的是
httpd-2.4.37-win64-VC15.zip
,你愿意的话也可以自己编译); - 解压至你喜欢的地方(我放在
E:\environment\apache
里); - 直接双击运行
bin/httpd.exe
; - 如果能正常访问
http://localhost:80
,就可以进行下一步了。
推荐修改的 httpd.conf
配置如下:
# 修改为你的安装目录
Define SRVROOT "E:\environment\apache"
ServerRoot "${SRVROOT}"
Listen 80
ServerName localhost
# 按需启用模块
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule ...
# 推荐注释掉自带的 DocumentRoot 和 <Directory>
# 这里面的内容等 0x03 再细说
Include conf/extra/httpd-php.conf
Include conf/vhosts.conf
0x02 安装 PHP
Windows 版 PHP 下载地址:
https://windows.php.net/download
其中有 NTS(Non Thread Safe,非线程安全)和 TS(Thread Safe,线程安全)两种版本,简单来说就是 Nginx 用 NTS 版,Apache 通常用 TS 版(也可以用 NTS),它们之间的具体区别有兴趣的话可以自己去了解一下。
为了同时兼容 Nginx 与 Apache,本文将以 NTS 版为例进行配置。
- 下载合适的版本(我下载的是
php-7.2.11-nts-Win32-VC15-x64.zip
); - 解压至你喜欢的位置(我放在
E:\environment\php
里); - 复制一份
php.ini-development
,重命名为php.ini
并适当修改其中配置; - 直接双击运行
php.exe
; - 如果能正常打开 PHP Interactive Shell,就可以进入下一步了。
如果无法运行,请检查你是否安装了对应的 VC 运行库。
推荐修改的 php.ini
配置如下:
; 扩展所在的目录,自行修改
extension_dir = "E:\environment\php\ext"
; 按自己的需求启用扩展
extension=pdo_mysql
extension=...
; 各种缓存的位置
sys_temp_dir = "E:\environment\php\temp"
upload_tmp_dir = "E:\environment\php\temp"
session.save_path = "E:\environment\php\temp"
; 其他杂七杂八的
upload_max_filesize = 100M
date.timezone = Asia/Shanghai
0x03 配置 FastCGI 转发
接下来才是重头戏,我们要让 Web Server 与 PHP 能够互相通信以完成请求。
作为 Web Server 的后端时,PHP 主要有两种运行方式,一种是 独立进程、使用 FastCGI 协议 与 Web Server 通信(Nginx 用的就是这种),另外一种是 作为模块直接加载到 Web Server 中(比如 Apache 的 mod_php
模块,不过 Apache 也支持 FastCGI 方式)。详细的原理我就不介绍了,有兴趣的选手可以去了解一下。
为了能够同时兼容 Nginx 和 Apache,本文均使用 FastCGI 方式加载 PHP。
首先我们需要配置一下 PHP 的 FastCGI 进程管理器。为什么呢?因为直接运行 PHP 的 FastCGI 进程(在 Windows 上就是 php-cgi.exe
)有以下缺点:
- 配置文件
php.ini
修改后无法平滑重载,需要重新启动php-cgi
进程; - Windows 下
php-cgi
默认处理 500 个请求后就自动退出(PHP_FCGI_MAX_REQUESTS
); - 如果因为其他原因造成
php-cgi
进程崩溃,就无法处理后续请求了。
所以我们需要一个类似守护进程的东西,来保证始终有一定数量的 php-cgi
进程在运行。PHP-FPM (PHP FastCGI Process Manager) 是 PHP 官方钦定的 FastCGI 进程管理器,但遗憾的是,它只适用于类 Unix 系统。在 Windows 上,我们可以使用这些替代品来实现类似的功能(Apache 用户不需要配置这些东西,因为它的 mod_fcgid
模块自带 FastCGI 进程管理功能):
本文将以 php-cgi-spawner 为例进行配置。
在 这里 下载编译好的
php-cgi-spawner.exe
;放到 PHP 的安装目录下(本文为
E:\environment\php
);打开 PowerShell 或者 CMD,运行命令:
# 令 PHP FastCGI 处理程序监听在 9000 端口上 # 至少开启 4 个 php-cgi 进程,高负载时最多可以开到 16 个 .\php-cgi-spawner.exe "php-cgi.exe -c php.ini" 9000 4+16
如果一切正常,你将可以在任务管理器中看到同时运行的多个
php-cgi
进程。
接下来修改 Nginx 配置(即 0x01 中提到的 vhosts.conf
中的内容),通过 FastCGI 协议将请求转发给监听在 9000 端口上的 PHP 进行处理:
server {
listen 80;
server_name localhost;
root E:/wwwroot;
index index.html index.htm index.php;
location ~ [^/]\.php(/|$) {
# 从 URI 中分离出 $fastcgi_script_name 和 $fastcgi_path_info 的值
# 不推荐使用 php.ini 中的 cgi.fix_pathinfo 选项,这可能会造成安全隐患
# 虽然我感觉 8012 年了应该没人用 PATH_INFO 了……不需要的话去掉即可
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
# 当请求的 .php 文件不存在时直接返回 404
# 不然交给 PHP 处理的话那边就会返回 No input file specified.
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
# 自带的配置文件,里面设置了一大堆 CGI 协议中的变量
include fastcgi.conf;
}
}
Apache 用户不需要手动配置 PHP FastCGI 进程管理器,相对简单一些:
- 在上面提到的 Apache Lounge 上下载编译好的
mod_fcgid
模块; - 解压后,将
mod_fcgid.so
放入 Apache 的modules
目录; - 编辑配置文件加载模块(即 0x01 中提到的
conf/extra/httpd-php.conf
):
# 如果嫌麻烦的话这一段也可以直接放到 httpd.conf 里面去
LoadModule fcgid_module modules/mod_fcgid.so
<IfModule fcgid_module>
FcgidInitialEnv PHPRC "E:/environment/php/"
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 1000
AddHandler fcgid-script .php
FcgidWrapper "E:/environment/php/php-cgi.exe" .php
FcgidIOTimeout 384
FcgidConnectTimeout 360
FcgidOutputBufferSize 128
FcgidMaxRequestsPerProcess 1000
FcgidMinProcessesPerClass 0
FcgidMaxProcesses 16
FcgidMaxRequestLen 268435456
ProcessLifeTime 360
</IfModule>
- 编辑
conf/vhosts.conf
允许运行 CGI 程序:
<VirtualHost localhost:80>
DocumentRoot "E:\wwwroot"
<Directory "E:\wwwroot">
DirectoryIndex index.html index.php
# 注意这里的 +ExecCGI,不加的话会 403
Options -Indexes -FollowSymLinks +ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>
如果一切配置正确,你应该就能正常访问 PHP 网页了。
0x04 编写启停脚本
虽然整个系统是跑起来了,但总不能每次启动都得开一大堆控制台窗口吧?弄个小工具管理 Nginx/Apache 和 PHP 的进程还是很有必要的。各种一键包中一般都自带了方便好用的界面来管理这些进程,不过既然我们选择了手动配置,那这一块也得我们自己搞定了。因为图方便,我选择使用 Windows 的批处理脚本(Batch File)来完成这项需求,保存为 .bat
文件双击运行就完事儿了。
启动 Nginx 和 PHP:
@ECHO OFF
set nginx_home=..\nginx
set php_home=..\php
ECHO Starting PHP FastCGI...
.\RunHiddenConsole.exe %php_home%\php-cgi-spawner.exe "%php_home%\php-cgi.exe -c %php_home%\php.ini" 9000 4+16
ECHO Starting Nginx...
.\RunHiddenConsole.exe %nginx_home%\nginx.exe -p %nginx_home%
停止 Nginx 和 PHP:
@ECHO OFF
ECHO Stopping nginx...
taskkill /F /IM nginx.exe > nul
ECHO Stopping PHP FastCGI...
taskkill /F /IM php-cgi-spawner.exe > nul
taskkill /F /IM php-cgi.exe > nul
重载 Nginx 配置:
@ECHO OFF
set nginx_home=..\nginx
ECHO Reloading Nginx...
%nginx_home%\nginx.exe -s reload
启动 Apache(Apache 会帮我们启动 PHP 的):
@ECHO OFF
set apache_home=..\apache
ECHO Starting Apache Httpd...
.\RunHiddenConsole.exe %apache_home%\bin\httpd.exe
停止 Apache:
@ECHO OFF
ECHO Stopping Apache Httpd...
taskkill /F /IM httpd.exe > nul
这些脚本我放在 E:\environment\scripts
目录中,如果你需要放在其他地方,请适当修改脚本中的可执行文件路径。另外,脚本中用到了 RunHiddenConsole.exe
来隐藏命令行窗口,你可以在 这里 下载到这个小工具。
0x05 后记
配置完成后的目录结构大概是这样的(有省略):
E:\environment> tree
.
├── apache
│ ├── bin
│ ├── cgi-bin
│ ├── conf
│ │ ├── extra
│ │ ├── original
│ │ ├── charset.conv
│ │ ├── httpd.conf *
│ │ ├── magic
│ │ ├── mime.types
│ │ ├── openssl.cnf
│ │ └── vhosts.conf *
│ ├── include
│ ├── lib
│ ├── logs
│ └── modules
├── mysql
│ ├── bin
│ ├── data
│ ├── include
│ ├── lib
│ └── share
├── nginx
│ ├── conf
│ │ ├── fastcgi.conf
│ │ ├── fastcgi_params
│ │ ├── koi-utf
│ │ ├── koi-win
│ │ ├── mime.types
│ │ ├── nginx.conf *
│ │ ├── scgi_params
│ │ ├── uwsgi_params
│ │ ├── vhosts.conf *
│ │ └── win-utf
│ ├── contrib
│ ├── logs
│ └── nginx.exe
├── php
│ ├── dev
│ ├── ext
│ ├── extras
│ ├── lib
│ ├── sasl2
│ ├── temp
│ ├── ...
│ ├── php7.dll
│ ├── php-cgi.exe
│ ├── php-cgi-spawner.exe *
│ ├── php.exe
│ └── php.ini *
└── scripts
├── reload-nginx.bat
├── restart-nginx.bat
├── RunHiddenConsole.exe
├── start-all.bat
├── start-apache.bat
├── start-mysql.bat
├── start-nginx.bat
├── stop-all.bat
├── stop-apache.bat
├── stop-mysql.bat
└── stop-nginx.bat
非常清爽,有种一切尽在掌控中的感觉,我喜欢。
如果你是一名 PHP 开发者,却从来没有手动配置过 PHP 环境的话,那我建议你尝试一下。虽然生产环境上一般都是采用成熟的一键包,不过手动配置一下这一套东西可以更深入地了解 PHP 与 Web Server 的协作机制,这对于编写上层应用以及运维也是很有好处的。
本来想顺带凑齐一套 WNMP,但是 MySQL 的安装配置相对比较简单,而且也不像 PHP 与 Web Server 那样耦合紧密,所以这里就按下不表。