
1. 先聊两句这套组合到底卡人卡在哪我最近给一个老项目补环境重新走了一遍 ThinkPHP 6.0.2 PHPStudy Composer 这套流程。说实话网上相关的教程一抓一大把但真正能一口气跑通的并不多。问题不是步骤难而是教程之间互相矛盾有的让你用集成环境自带的 PHP 版本有的让你先改 PHPStudy 的端口还有的直接甩一个几十 MB 的项目压缩包让你解压到根目录结果访问一片空白谁都不知道是哪一步出了错。我写这篇不是想凑热闹而是把这次实际搭建的完整过程连同两年里在这套环境上反复踩过的坑一起整理出来。目标很明确让一个从没配过 ThinkPHP 6 的人照着操作能在半小时内看到默认欢迎页并且知道每一个关键步骤背后“为什么一定要这样做”。适合三类人看刚学 ThinkPHP 的新手、要接手旧项目跑本地环境的开发者以及想用 PHPStudy 快速验证 TP6 功能模块的技术爱好者。先说结论这套环境真的不难但有一个绕不开的命门——Composer 拉取依赖的速度和稳定性。只要把 Composer 的源和 PHP 版本选对后面基本就是一路下一步。2. 环境准备PHPStudy 与 PHP 版本选择的讲究2.1 别下错 PHPStudy 版本PHPStudy 现在官方叫“小皮面板”官网直接搜就能找到。它有 Windows 版和 Linux 版Windows 下是一个桌面控制面板Linux 下是网页面板两者界面差异很大教程不能互相套用。我平时本地开发用 Windows 版服务器上用 Linux 版。如果你是跟着这篇走本地开发下载 Windows 版即可版本号其实不用太追新v8.x 或者最新的 8.1 都行核心功能差别不大。安装时有一个不少人忽略的点安装路径不要带中文和空格。我见过有人装在D:\软件\phpstudy后面 Composer 和 PHP 扩展加载各种报错改回D:\phpstudy_pro才消停。这不是玄学是 PHP 的某些扩展和工具链对路径里的特殊字符敏感用纯英文路径省心很多。另外安装完面板后第一次启动会提示选择 Apache 还是 Nginx。这里不用太纠结先选哪个都能跑后面伪静态配置我会分别给写法。我自己倾向于 Nginx占内存小、并发表现好而且 ThinkPHP 官方生产环境文档也以 Nginx 为主。2.2 ThinkPHP 6.0.2 对 PHP 版本的硬性要求ThinkPHP 6.0 的官方写的是 PHP 7.2.5也就是说 PHP 7.2.5 以上都能跑。但版本能跑和跑得舒服是两码事。我在 PHPStudy 里装了 7.2、7.4、8.0 三个版本实测下来 ThinkPHP 6.0.2 配 PHP 7.4 是最稳的。7.2 也能跑但有些第三方包已经开始放弃对 7.2 的兼容装依赖时容易碰到坑。8.x 在 TP6.0.2 早期版本上偶尔会有“函数未定义”这类兼容问题因为 PHP 8 删掉了一批旧函数。虽然 6.0.2 有兼容处理但没必要拿老框架去挑战新运行时。给你们一个选版本的建议表PHP 版本ThinkPHP 6.0.2 兼容性我的推荐度备注7.2可运行依赖兼容性下降不推荐第三方包很多已放弃支持7.4稳定运行强烈推荐目前 TP6 本地开发最佳选择8.0大部分功能正常可以但没必要老项目改造时容易出幺蛾子8.1风险较高不推荐建议直接用 ThinkPHP 82.3 PHP 版本怎么在面板里切换和确认PHPStudy 装完默认可能给的是 PHP 8 或者更老的 5.x需要在面板的“软件管理 - PHP”里看一下已经装了哪些版本。如果没有 7.4点“安装”按需装一个这个过程会下载安装包稍微等一下就行。确认版本有个很简单的办法在 PHPStudy 的“设置 - 配置文件 - PHP”能看到当前站点用的 PHP 版本也可以在项目根目录写一个临时 PHP 文件放一句?php phpinfo();访问后看第一行显示的版本对不对。注意一点你切换了 PHPStudy 里的 PHP 版本并不代表命令行里的 PHP 也跟着变了。Windows 环境下PHPStudy 的 PHP 是装在D:\phpstudy_pro\Extensions\php\7.4.3nts这种路径下的跟系统 PATH 里的 PHP 是两回事。这一点正好是下一节 Composer 的问题源头。2.4 开发环境里必须检查的三个开关进 PHPStudy 面板之后不需要改太多东西但有三处我要建议你提前确认MySQL 的端口如果不是 3306ThinkPHP 的.env文件里数据库配置要跟着改不然连不上库。Nginx/Apache 的进程没有占用特别是 80 端口被 IIS 或者其它进程抢走时站点起不来。PHP 扩展里fileinfo、curl、openssl这些必须打开。ThinkPHP 6 的依赖里有的会用到它们缺了某些扩展Composer 安装时候直接报错弹出来的提示还不直观。打开扩展的位置在面板的“PHP 扩展”栏勾选后点“应用更改”PHPStudy 会自动帮你改php.ini并重启服务不用手动去翻配置文件这点比我们自己改配置省力很多。3. Composer 是第一个大坎安装、换源与版本指定3.1 Windows 下 Composer 的两种安装方式Composer 在 Windows 下推荐有两种方式直接下载Composer-Setup.exe安装包一路下一步。它会帮你把composer.bat加入系统 PATH以后在命令行直接敲composer就能用。下载composer.phar文件放在项目目录或者一个固定目录用php composer.phar的方式调用。好处是不污染系统坏处是每次命令都要带php前缀。我推荐第一种省事。但安装过程中有一个关键选择Composer 安装程序会让你指定 PHP 的路径。这时候如果你机器上装过多个 PHP务必确保选的是 PHPStudy 里同一版本的php.exe。我见过有人系统里装了别家的 PHP比如小皮之外的Composer 用的是那个 PHP依赖装好后跑起来却是另一个版本最后项目直接白屏。最简单的做法在已安装的其他环境菜单里勾选“排除安装”让 Composer 只认识一个 PHP。或者在 PHPStudy 的面板里找到当前站点用的 PHP 可执行文件Composer 安装时手动指定到那个路径。3.2 不换源创建项目能等到你怀疑人生Composer 默认的依赖源是 Packagist 的国外服务器在国内网络环境下拉取速度非常不稳定。我最初用默认源创建一个 ThinkPHP 6.0.2 项目卡在updating dependencies阶段十几分钟不动最后直接超时失败。换成国内镜像后同样的操作两三分钟完成。切换全局镜像源的方法命令行执行composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/这条命令是把全局的 Composer 镜像源改成阿里云镜像。改动之后以后所有composer create-project和composer install都会走这个源。想验证是否成功执行composer config -g -l可以看到repositories.packagist的值是我们刚设置的地址。如果换了源仍慢还可以临时指定源composer create-project topthink/think6.0.2 tp6 --prefer-dist --no-interaction当然阿里云镜像偶尔有缓存延迟遇到某个包版本拉取不到的时候可以临时切到腾讯源比较一下composer config -g repo.packagist composer https://mirrors.cloud.tencent.com/composer/注意镜像源只影响 Composer 下载不影响项目代码的运行切换后重新composer update也不会破坏已有项目结构。3.3 多 PHP 版本共存时 Composer 用错版本怎么办这是我认为整套流程里最容易踩但又最没人提的一个坑。PHPStudy 装了多个 PHP 版本时Composer 默认使用PATH里那个 PHP。如果你在命令行里敲php -v显示的是 PHP 8那 Composer 会用 PHP 8 去执行依赖解析。很多依赖在 PHP 8 下安装会出现版本兼容警告甚至直接拒绝安装。我当时碰到的情况是项目要跑在 PHP 7.4但 Composer 用的是 PHP 8结果composer create-project时提示某些插件不被当前 PHP 支持。排查后发现是 PATH 顺序问题。解决办法有两个方法一临时指定 PHP 版本来执行 ComposerD:\phpstudy_pro\Extensions\php\7.4.3nts\php.exe D:\composer\composer.phar create-project topthink/think6.0.2 tp6方法二直接把 7.4 的目录提到 PATH 最前面。右键“此电脑 - 属性 - 高级系统设置 - 环境变量”在Path里把 PHP 7.4 的目录移到最上边。我个人建议用方法一因为改动最小、不容易影响系统里其它工具。3.4 常见 Composer 报错现场还原我把这两年收集到的 Composer 高频报错整理成一个表方便你对着排查报错信息实际情况解决办法Composer is not initializedComposer.lock 缺失或者composer install没找到锁文件首次用composer update生成 lock 后再 installCould not resolve host网络访问不了默认源切国内镜像源重试Your requirements could not be resolved依赖版本冲突用composer create-project topthink/think6.0.2指定版本避免乱拉最新The openssl extension is requiredphp.ini 里没开 opensslPHPStudy 面板勾选 openssl 扩展并重启App directory already exists目标目录已存在同名文件换一个目录名或删掉目标目录后重试最后一行的App directory already exists是我看到新手问得最多的。这问题特别简单你创建项目的目录不是空的Composer 不敢覆盖。换一个新目录名就行比如tp6别直接建think再往里塞。4. 创建 ThinkPHP 6.0.2 项目命令、目录结构与首次访问4.1 创建命令与版本锁定准备工作都做完后创建 TP6.0.2 项目的完整流程如下cd D:\www composer create-project topthink/think6.0.2 tp6分解一下这条命令create-project是 Composer 的创建项目指令等同于先git clone再composer install。topthink/think是 ThinkPHP 框架的主包名。6.0.2是版本约束锁定在 6.0.2 版本。tp6是要创建的目标目录名。如果你不写6.0.2默认会拉取该包可用的最新 6.x 版本。作者标题里既然提到 6.0.2说明项目需求是明确的那就老老实实锁版本。命令执行过程中会下载 ThinkPHP 核心和一组依赖包。下载完成后你会在tp6目录下看到类似下面的结构tp6/ ├── app/ 应用目录 │ ├── controller/ 控制器目录 │ ├── model/ 模型目录 │ └── ... ├── config/ 配置目录 ├── route/ 路由定义目录 ├── runtime/ 运行时生成目录缓存、日志 ├── vendor/ Composer 依赖包目录 ├── view/ 视图目录部分版本可能没有需自行创建 ├── public/ Web 根目录 │ └── index.php 入口文件 ├── .env 环境配置文件默认没有需复制 .env.example 或自己建 ├── composer.json 依赖声明文件 └── think 命令行入口Linux/macOS4.2 不能用根目录当站点根目录public 的定位刚接触 ThinkPHP 6 的人容易有的一个错误认知以为项目根目录就是网站根目录。其实不是。ThinkPHP 6 强制要求 Web 服务器把站点根目录指到public目录原因有两条一是入口安全。TP6 的入口文件只有public/index.php。如果站点根目录是项目根目录访问者直接访问http://你的域名/app/controller/User.php就能看到源码安全性等于裸奔。而指到public后根目录以上的文件都无法直接通过 URL 访问。二是路由纯净。URL 重写时会把所有访问请求交给index.php处理如果把根目录暴露出去静态资源和入口会混在一起伪静态规则容易冲突。理解这一点后面配置 PHPStudy 就不会迷糊。4.3 首次访问欢迎页出现才算第一步完成先在项目里建立一个访问入口。如果创建后public下已经有.example这类示例文件直接删掉就行不影响。然后在命令行执行cd D:\www\tp6 php think run这条命令会调用 PHPStudy 里配置好的 PHP 内置服务器默认监听127.0.0.1:8000。浏览器访问http://127.0.0.1:8000正常情况下能看到 ThinkPHP 的默认欢迎页。看到欢迎页说明 Composer 拉取的框架代码没问题、PHP 扩展没问题、目录权限没问题。后面要做的就是用 PHPStudy 把域名和站点串起来。另一种更快的方式是在 PHPStudy 面板里直接创建一个站点把域名指向项目目录。但这就要先解决“运行目录指向 public”的事所以我把这一个主题拆成了独立的一节来说。5. 必踩的坑PHPStudy 里运行目录不指向 public 会怎样5.1 小皮面板里创建站点并指定运行目录打开 PHPStudy 面板在左侧菜单点“网站”再点“创建网站”。填写以下几项域名比如tp6.test端口默认 80如果被占用可以改成 8000、8080 这类网站目录选到你的项目根目录比如D:\www\tp6运行目录这里最关键要手动选择为\publicPHP 版本选择 PHP 7.4对应你项目需要的版本截图省略因为不同版本界面稍有差异但关键点都一样运行目录必须选public。如果创建网站时没有设置运行目录默认指向网站根目录。此时访问站点可能出现的情况有两种一是直接显示目录结构列表把app、config都列出来非常不安全二是访问http://tp6.test/index.php能出页面但访问http://tp6.test/是 403 或者 404。两种都说明目录指向错了。5.2 本地域名解析hosts 文件别忽略PHPStudy 创建的站点域名tp6.test本地浏览器访问时实际上查的是系统 DNS。如果系统配置的 DNS 服务器不认识这个域名会解析失败。开发环境的常规做法是在 hosts 文件里手动加一行解析记录。hosts 文件在C:\Windows\System32\drivers\etc\hosts以管理员身份打开记事本在最下面加127.0.0.1 tp6.test如果 PHPStudy 创建站点时勾选了“创建 hosts 解析”它会自动帮你写进去没勾照做别忘了这一步。5.3 设置之后仍然打不开的三个检查点有次我帮一个同事排查他运行目录也选了publichosts 也配了访问却还是 404。最后发现是下面三个点的问题PHPStudy 是 Nginx 环境伪静态规则没有加上。ThinkPHP 6 默认需要 URL 重写功能Nginx 下如果没配置伪静态访问http://tp6.test时index.php没被正确路由自然打不开页面。关于这一点下一节专门展开。项目目录没有给足读写权限。TP6 运行时会在runtime目录写日志、缓存如果 PHPStudy 进程对该目录没有写权限会出现页面空白但错误日志里没有任何记录的情况。Windows 下大部分情况不会有这个权限问题但 Linux 面板必须注意经常是runtime目录权限为 755、所属用户不对导致的问题。端口冲突。站点的端口如果是 80而系统里 IIS 或者其它 Web 服务也占用着 80PHPStudy 的站点就起不来。解决办法是把 PHPStudy 的 Nginx 或者 Apache 停掉重新启动或者在创建站点时换个端口。6. 伪静态与 URL 重写让 URL 里没有 index.php6.1 为什么要处理伪静态ThinkPHP 6 默认的路由格式是http://tp6.test/index.php/index/hello如果你把index.php留在 URL 里性能上没大影响但两个问题URL 不美观对外分享的时候很长某些环境下的安全策略会把index.php当成漏洞特征虽然多数是误报但没必要自己给自己找麻烦。处理掉index.php的办法叫“伪静态”实质是在 Web 服务器层面把所有不存在的文件请求重写到入口文件index.php。6.2 Apache 环境下的伪静态配置Apache 下最简单的方式是利用 ThinkPHP 自带的.htaccess文件。TP6 的public目录里自带了一个.htaccess内容大致是IfModule mod_rewrite.c Options FollowSymlinks -Multiviews RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L] /IfModule把这段配置放到public/.htaccess里并且确保 Apache 开启了mod_rewrite。PHPStudy 的 Apache 默认会开这个模块一般不用手动改。6.3 Nginx 环境下的伪静态配置Nginx 环境没有.htaccess需要在站点的 Nginx 配置里加上一段 location 规则。在 PHPStudy 面板里找到该站点对应的 Nginx 配置文件“网站 - 操作 - 配置文件”在server {}段里加location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; break; } }保存后在面板里重启 Nginx。这里有个小细节Nginx 的if (!-e $request_filename)意思是请求的文件不存在时才进入重写避免把真实的静态资源如 CSS、JS、图片也转给index.php。6.4 配置完成后的验证方式配置完伪静态后验证三步访问http://tp6.test应该直接出现欢迎页或你写的控制器页面。访问一个有控制器但不存在的路由比如http://tp6.test/nonexist应该返回 TP6 的 404 页面而不是 Nginx 默认的 404。在public目录放一个静态文件比如public/robots.txt能正常访问到文件内容说明静态资源没有被重写规则误伤。这三步走通伪静态就基本到位了。7. 冷门但真实$_SERVER[REQUEST_URI] 为空导致路由失效这个坑我在热搜词里看到有人也搜了很值得单说一节。因为它不是一个常见配置问题而是环境层面的“幽灵问题”。7.1 问题现象与排查链路有次我搭好 TP6 项目后访问首页正常但一访问任何二级路由就白屏。打开调试模式看错误显示路由解析失败类不存在的提示。一开始我以为是路由定义写错检查了半天没发现问题。后来在public/index.php里临时写var_dump($_SERVER[REQUEST_URI]); var_dump($_SERVER[REQUEST_METHOD]); die;发现$_SERVER[REQUEST_URI]居然是空字符串。ThinkPHP 6 的路由解析依赖$_SERVER[REQUEST_URI]或者$_SERVER[PATH_INFO]来识别当前请求的地址。如果REQUEST_URI为空框架无法正确解析控制器的路由所以首页通过默认路径能打开但二级路径全部失效。7.2 为什么会出现这个情况实际上REQUEST_URI是由 Web 服务器注入到 PHP 环境变量里的。正常情况下 Nginx 会通过fastcgi_param REQUEST_URI $request_uri;传给 PHP-FPM。如果它为空说明fastcgi_params或php-fpm接收方配置里缺少这一行或者被覆盖了。常见的触发场景有几个某些精简版的 Nginx 配置模板里漏了fastcgi_param REQUEST_URI而 ThinkPHP 恰好依赖它Apache 环境下mod_rewrite规则和AcceptPathInfo的相互作用导致REQUEST_URI被改写为空某些安全软件拦截了带有路径信息的请求转发时把REQUEST_URI置空。7.3 兜底解决思路与代码示例第一个解决路径是检查 Web 服务器配置Nginx 的fastcgi.conf或者站点配置里是否包含fastcgi_param REQUEST_URI $request_uri;。没有就补上然后重载配置。第二个解决路径是代码层面兜底在public/index.php入口文件的最上方也就是框架加载之前加上判断if (empty($_SERVER[REQUEST_URI])) { if (isset($_SERVER[HTTP_X_ORIGINAL_URL])) { $_SERVER[REQUEST_URI] $_SERVER[HTTP_X_ORIGINAL_URL]; } elseif (isset($_SERVER[REQUEST_URI_FALLBACK])) { $_SERVER[REQUEST_URI] $_SERVER[REQUEST_URI_FALLBACK]; } else { $_SERVER[REQUEST_URI] $_SERVER[SCRIPT_NAME]; if (isset($_SERVER[QUERY_STRING]) $_SERVER[QUERY_STRING]) { $_SERVER[REQUEST_URI] . ? . $_SERVER[QUERY_STRING]; } } }这段代码的原理是手动把缺失的变量用其它服务器变量拼装回来保证框架读取路由时拿到的地址是完整可用的。它不是万能的但能解决大多数 Web 服务器没有注入导致的空值问题。如果用了这个兜底后问题还在那我建议你把错误模式开大看 TP6 日志里的完整请求参数是什么顺着日志里缺少的 $_SERVER 键名继续补。8. 进阶避坑笔记关联删除、二级域名与安全底线8.1 模型关联删除的三种实现ThinkPHP 6 的模型关联删除是很多人在实际业务里绕不开的需求。比如删一篇文章要把它的评论、点赞关联数据一起删掉。三种方式我都用过方式一使用with()关联后控制层手动循环删除。缺点是 N1 次查询数据量小还行数据量大了性能难看。方式二使用模型事件在模型类里定义一个删除监听protected static function onBeforeDelete($model) { // 删除前先清理关联数据 $model-comments()-delete(); }这种方式的优点是把逻辑内聚在模型里不污染控制器代码推荐业务层使用。方式三数据库外键级联。在数据表设计时给外键加上ON DELETE CASCADE数据库层自己处理。这种方式性能最好但外键约束在某些分表场景下比较麻烦动手要谨慎。选哪种取决于你的业务复杂度。简单说单条数据删除用事件批量的用查询后再统一删数据量大时优先考虑数据库设计。8.2 二级域名设置的两个层面“ThinkPHP 开启二级域名设置”这个关键词也很热。坦率地讲二级域名设置有硬件层和业务层两个层面经常被人搞混域名解析 服务器配置层面需要把admin.tp6.test解析到你的服务器并在 Nginx 或 Apache 里新增一个站点或者 server 块。框架路由层面如果两个域名指向的是同一个 TP6 项目可以在route/route.php里用Route::domain()做域名分组Route::domain(admin.tp6.test, function () { Route::get(index, admin.Index/index); });如果只是本地开发想模拟二级域名PHPStudy 创建站点时直接填admin.tp6.test并在 hosts 里加一行解析效果一样不用专门上外网服务器操作。8.3 关于 thinkphp 漏洞的三个安全提醒最后想多说一句安全相关的。搜“thinkphp 漏洞”的人不少很多是运维或开发在自查。有一些基础操作可以大幅降低框架版本漏洞的风险本地开发可以用 6.0.2 这个版本但上生产环境前一定要composer update topthink/framework到 6.0.x 的最新版本。旧版本没有后续安全补丁等于把一个已公开的问题留在线上。关闭调试模式。TP6 的.env文件里默认是APP_DEBUG true生产环境务必改成false并且开启APP_TRACE前先想想日志里会不会带出敏感信息。不要把 MySQL 的 root 密码写在项目里尽量用最小权限账号.env 文件不要让 Web 服务器目录直接可读。PHPStudy 运行目录指到public后.env在项目根目录无法被 URL 访问但文件系统层面该设的权限还是要设。我个人的习惯是本地随便折腾生产环境打死不偷懒依赖锁版本、配置最小化、日志定期清理。任何框架版本都有生命周期ThinkPHP 6.0 也不例外别让一个“能跑”变成“不敢动”。这套环境真正用顺之后你会发现后面写业务逻辑的速度远大于配环境的效率。把这次记录里的每一个坑都避开你剩下的时间就可以花在更有意思的功能实现上了。