前言
本地装了多个 PHP 版本(比如 PhpStudy 里同时有 7.4、8.0、8.2、8.4),想用其中一个来装 Laravel,结果反复撞上两种怪事:一是在面板里把站点 PHP 切到了 8.2,可composer create-project依然报Your requirements could not be resolved,说需要php >=8.2;二是反过来,命令行装得好好的,浏览器一打开就是 500。
根因只有一个,但很多人一开始没意识到:Web 用的 PHP 和命令行用的 PHP 是两套完全独立的东西。Web 那套由 Web 服务器的处理器决定(Apache 的模块、Nginx 转发到的 FastCGI 端口、IIS 的处理程序映射);命令行那套由PATH环境变量里第一个被找到的php决定。Composer 是命令行程序,它只认后者。面板里点"切换 PHP 版本",改的是前者。
本文把这两条链路分开讲,给出不依赖PATH的调用方式、config.platform的正确用法,以及一套切换后必须做的验证动作。
一、先把两条链路认清楚
| 维度 | Web 侧 PHP | CLI 侧 PHP |
|---|---|---|
| 谁决定用哪个版本 | Apache 的LoadModule/ Nginx 的fastcgi_pass端口 / IIS 的处理器映射 | PATH里第一个php(php.exe) |
| 由谁配置 | 面板的站点设置、Web 服务器配置文件 | 系统环境变量、终端会话 |
| 改完要不要重启 | 要(重启 Web 服务) | 不要(但已开着的终端要重开) |
| Composer 用哪个 | 不用 | 用这个 |
php artisan用哪个 | 不用 | 用这个 |
| 验证命令 | 浏览器访问phpinfo()或探测脚本 | php -v |
把这张表记住,绝大多数"切了没用"的困惑当场就消解了。
先分别验证两边到底是什么版本:
# CLI 侧 php -v php --ini # Windows:列出 PATH 里能找到的所有 php where php # Linux / macOS:列出全部 which -a phpLinux 下如果装了多个版本,用 alternatives 机制切换默认的 CLI 版本:
# Debian / Ubuntu sudo update-alternatives --config php # RHEL 系先看模块流里有哪几个版本 dnf module list php二、Laravel 各版本的 PHP 门槛
这张表决定了"我该切到哪个版本",先看它再动手。
| Laravel 版本 | 最低 PHP | 说明 |
|---|---|---|
| 9.x | 8.0.2 | 仍在维护周期内的旧项目 |
| 10.x | 8.1 | 大量存量项目在这个版本 |
| 11.x | 8.2 | 要求 PHP 8.2 起 |
| 12.x | 8.2 | 与 11.x 的门槛相同 |
Composer 在解析依赖时会读composer.json里require.php的约束,例如"php": "^8.2"。^8.2的含义是"大于等于 8.2 且小于 9.0",所以 PHP 8.4 也能满足;但如果某个包写的是~8.2.0,就只接受 8.2.x,8.4 会被拒。
查看依赖对 PHP 的真实要求,不用猜:
# 看 laravel/framework 的所有可用版本及各自的 require 约束 composer show laravel/framework --all # 在当前环境上逐条验证平台要求是否满足 composer check-platform-reqs三、不依赖 PATH:指定解释器跑 Composer
最稳的做法是永远不要依赖PATH里那个"碰巧是第一个"的 php,而是显式指定解释器。Windows 上写绝对路径即可:
# 用 PHP 8.2 创建 Laravel 12 项目 D:/phpstudy_pro/Extensions/php/php8.2.9nts/php.exe \ D:/phpstudy_pro/Extensions/composer/composer.phar \ create-project laravel/laravel blog "^12.0" # 进入项目后,同样显式指定解释器安装依赖 cd /d D:/phpstudy_pro/WWW/blog D:/phpstudy_pro/Extensions/php/php8.2.9nts/php.exe \ D:/phpstudy_pro/Extensions/composer/composer.phar install每次都敲一长串路径太痛苦。在项目根目录放一个 Windows 批处理包装脚本(存成php82.bat),把它拖进PATH,或者直接放在项目里用相对路径调用:
@echo off rem php82.bat —— 用固定版本的 PHP 执行传入的命令 set "PHP82=D:\phpstudy_pro\Extensions\php\php8.2.9nts\php.exe" "%PHP82%" %*有了这个包装脚本,composer的调用就变成:
php82.bat D:\phpstudy_pro\Extensions\composer\composer.phar install php82.bat artisan migrate php82.bat -v把php82.bat里的路径改成 8.4 的目录,另存成php84.bat,就得到了一个项目级的"版本切换开关",不受系统PATH变化影响。
Linux / macOS 上用 alias 或者一个同名 shell 函数:
# 写进 ~/.bashrc php82() { /usr/bin/php8.2 "$@" }四、config.platform:让依赖解析按目标版本走
有时候你会遇到一种矛盾:开发机上是 PHP 8.4,但生产服务器只有 PHP 8.2。如果不做任何配置,在 8.4 上composer update解析出来的依赖版本,可能包含了要求php >=8.3的包,拿到生产上就装不起来。
解决办法是在composer.json里声明"我在模拟哪个平台":
composer config platform.php 8.2.9它会在composer.json里写入:
"config": { "platform": { "php": "8.2.9" } }从此以后,无论本地实际是 8.4 还是 8.2,Composer 在解析依赖时都当作 8.2.9 来处理。这是正确对齐环境差异的方式,而不是用下面这个:
# 不要这样做:它只是跳过检查,装出来的依赖可能在生产上根本跑不起来 composer install --ignore-platform-reqs--ignore-platform-reqs会让你在 PHP 8.0 上装出需要 8.2 的包,然后在运行时以"语法错误"或"未定义类"的形式爆炸。它的合理用途只有一个:临时在 CI 里装依赖做静态检查。
五、装完之后必须做的验证
切换 PHP 版本后,别急着打开浏览器。按顺序跑这几条:
# 1. 确认 CLI 版本是你要的那个 php -v # 2. 确认加载的 php.ini 是哪个(这一步最常见问题) php -i | grep -i "loaded configuration" # 3. 确认 Laravel 真的能启动 php artisan --version # 4. 确认依赖对平台的判断 composer check-platform-reqs # 5. 确认扩展齐全(Laravel 需要这些) php -m | grep -i -E "pdo_mysql|mbstring|openssl|fileinfo|tokenizer|curl|zip"浏览器侧用一个探测文件确认 Web 的 PHP:
<?php declare(strict_types=1); // public/php-probe.php —— 临时文件,用完删除 header('Content-Type: text/plain; charset=utf-8'); echo 'SAPI : ', PHP_SAPI, PHP_EOL; echo 'PHP 版本 : ', PHP_VERSION, PHP_EOL; echo '加载的 ini : ', (php_ini_loaded_file() ?: '(none)'), PHP_EOL; echo 'ext dir : ', ini_get('extension_dir'), PHP_EOL; echo 'pdo_mysql : ', (extension_loaded('pdo_mysql') ? '已加载' : '未加载'), PHP_EOL;把 CLI 侧的php -v和这个探测页面显示的版本对一遍。两边一致,才算真的切好了。
实战:一条命令完成"指定版本建项目"
把前面的东西拼起来,下面这段脚本可以直接存成new-laravel.bat使用,接受一个参数作为项目目录名。它硬编码了 PHP 路径,因此不受PATH里是哪个版本影响。
@echo off setlocal rem ===== 按需修改这两个路径 ===== set "PHP_EXE=D:\phpstudy_pro\Extensions\php\php8.2.9nts\php.exe" set "COMPOSER_PHAR=D:\phpstudy_pro\Extensions\composer\composer.phar" rem ============================== if "%~1"=="" ( echo 用法: %~nx0 ^<项目目录名^> exit /b 1 ) set "APP_NAME=%~1" set "TARGET=D:\phpstudy_pro\WWW\%APP_NAME%" if exist "%TARGET%" ( echo 目录已存在: %TARGET% exit /b 1 ) echo [1/5] 检查解释器版本... "%PHP_EXE%" -v if errorlevel 1 ( echo 解释器不存在,请检查 PHP_EXE 路径 exit /b 1 ) echo [2/5] 创建项目(Laravel 12)... "%PHP_EXE%" "%COMPOSER_PHAR%" create-project laravel/laravel "%TARGET%" "^12.0" if errorlevel 1 exit /b 1 cd /d "%TARGET%" echo [3/5] 对齐平台版本... "%PHP_EXE%" "%COMPOSER_PHAR%" config platform.php 8.2.9 echo [4/5] 生成应用密钥... "%PHP_EXE%" artisan key:generate echo [5/5] 校验平台依赖... "%PHP_EXE%" "%COMPOSER_PHAR%" check-platform-reqs echo. echo 完成。网站根目录请指向: %TARGET%\public endlocal最后那句提示很关键:Laravel 的站点根目录必须是项目的public子目录。切了 PHP 版本、装好了依赖,站点根目录指错,一样是 404。
常见坑点
坑 1:以为面板里切了 PHP 版本,命令行也跟着变了
❌ 面板里把站点切到 8.2,然后直接composer install,报requires php >=8.2
✅ 面板只管 Web 侧。CLI 侧要单独处理:显式指定解释器路径,或调整PATH顺序
坑 2:用setx改 PATH,把 PATH 截断了
❌setx PATH "%PATH%;D:\php8.2"——setx对 PATH 有长度限制,超出部分会被静默截断,PATH 直接被破坏,命令行一片混乱
✅ 用系统属性对话框改,或用 PowerShell 的环境变量接口:
$old = [Environment]::GetEnvironmentVariable('Path', 'User') [Environment]::SetEnvironmentVariable('Path', "$old;D:\php8.2", 'User')坑 3:切了版本但没重启 Web 服务
❌ 面板里改完站点 PHP 版本直接刷浏览器,得到 502 Bad Gateway
✅ 改完面板配置后重启对应的 Web 服务(Nginx / Apache)。FastCGI 端口变了而 Web 服务器还连着旧端口,就是 502
坑 4:改了站点却始终访问不到新版本
❌ 浏览器里带缓存反复刷新,看到的一直是旧版本
✅ 用curl绕开浏览器缓存直接打探测页:
curl -s http://blog.test/php-probe.php坑 5:把--ignore-platform-reqs当成"强制安装"的常规手段
❌ 每次装依赖都加--ignore-platform-reqs,把版本约束当噪音
✅ 用composer config platform.php 8.2.9声明目标平台,让解析在正确的约束下进行
坑 6:混用php.exe和php-cgi.exe
❌ 命令行里用php-cgi.exe跑artisan,或者反过来把php.exe配给 Web 服务器
✅ 命令行一律用php.exe(SAPI 是cli),Web 侧由 Web 服务器去调用 CGI/FPM 的可执行文件。用php -r "echo PHP_SAPI;"确认
坑 7:Composer 报内存不足,误以为是 PHP 版本问题
❌ 看到Allowed memory size exhausted就去换 PHP 版本
✅ 这是 Composer 本身的内存问题,跟版本无关:
COMPOSER_MEMORY_LIMIT=-1 composer install坑 8:用新版本生成的composer.lock拿到旧环境上跑
❌ 在 PHP 8.4 上composer update,把composer.lock提交,然后部署到 PHP 8.2 的服务器
✅ 更新依赖前先composer config platform.php <生产版本>,让lock文件从一开始就按生产环境解析
总结
| 目标 | 做法 | 验证方式 |
|---|---|---|
| 确认 CLI 用的是哪个版本 | php -v/where php | 输出里的版本号 |
| 不依赖 PATH 跑 Composer | 显式指定php.exe绝对路径 | composer check-platform-reqs |
| 让依赖按目标版本解析 | composer config platform.php 8.2.9 | composer show laravel/framework --all |
| 切换 Web 侧版本 | 面板站点设置 + 重启 Web 服务 | 探测页显示PHP_SAPI与版本 |
| 装完 Laravel 的收尾 | key:generate+ 站点根指向public | 浏览器能打开首页和二级路由 |
"切了 PHP 版本没生效"这句话本身就有歧义:是 CLI 没生效,还是 Web 没生效?先分别打印两边的版本事实,再决定改哪一处。最省心的长期方案是放弃依赖PATH,在项目里放一个固定解释器路径的包装脚本,让"用哪个 PHP"变成项目的一部分而不是环境的一部分。