
前言想在本地换上 PHP 8.4再用它建一个最新版的 Laravel 项目动手之前总会犹豫会不会一装就满地红字会不会 composer 解依赖解到一半说没有兼容的包先给出结论可以装。PHP 8.4 是 2024 年底发布的版本而 Laravel 从 11.x 起把最低要求定在 PHP 8.2约束写法是php: ^8.2——它的含义是大于等于 8.2、小于 9.08.4 落在这个区间里所以满足要求。但能装和装完不出问题是两件事。真正会在 PHP 8.4 上炸的不是 Laravel 自身而是第三方依赖包里遗留的隐式可空参数写法——PHP 8.4 把它标成了弃用项。症状是页面能跑但日志里滚动着一屏黄字或者composer在跑脚本时刷出成千上万条Deprecated。本文讲清楚判断能不能装的唯一依据是什么、隐式可空参数这个 8.4 上的新弃用项怎么批量找出来、8.4 带来的新语法哪些能用上以及确实不兼容时的三条出路。一、判断能不能装只看一个东西不要看博客、不要看教程里的年份。判断一个项目能不能在当前 PHP 上装起来唯一权威的依据是composer.json里require.php的约束以及 Composer 实际解析的结果。# 1. 先确认 CLI 侧的 PHP 版本Web 侧版本与此无关 php -v # 2. 看 laravel/framework 所有可用版本以及各自的 require 约束 composer show laravel/framework --all # 3. 在当前环境上逐条验证平台要求 composer check-platform-reqs第 2 条输出里会列出每个版本对应的php约束第 3 条会逐项告诉你当前环境满足/不满足。这两条命令的输出比任何对照表都准确——因为它读的是你本机 Composer 缓存里的真实元数据。约束写法之间的差别很关键它决定了松还是紧约束写法含义PHP 8.4 能否满足^8.2 8.2 且 9.0满足8.28.2 及以上无上限满足~8.2.0 8.2.0 且 8.3.0不满足8.2.*只接受 8.2.x不满足^8.1 \\^8.2第二行和第三行的区别是很多人踩过的坑一个包写~8.2.0就意味着它只保证在 8.2.x 上工作8.4 会被直接拒掉。二、Laravel 版本与 PHP 版本的对照这张表是已知的对应关系。注意 Laravel 每年发一个大版本新的大版本可能抬高门槛所以动手前一定用上一节的命令复核一遍。Laravel 版本最低 PHPComposer 安装命令9.x8.0.2composer create-project laravel/laravel demo ^9.010.x8.1composer create-project laravel/laravel demo ^10.011.x8.2composer create-project laravel/laravel demo ^11.012.x8.2composer create-project laravel/laravel demo ^12.0在 PHP 8.4 上上表里 11.x 和 12.x 都在支持范围内。真正要盯的是第三个参数之外的东西你的composer.lock里锁定的那些传递依赖它们是否声明了对 8.4 的支持。三、PHP 8.4 上最容易撞的坑隐式可空参数这是升级到 8.4 之后日志量暴涨的头号原因务必先理解它。在 PHP 8.4 之前下面这种写法是有特殊含义的?php // 在 PHP 8.4 之前$name 的类型是 string但默认值是 null // PHP 会善意地把它当成 ?string 处理 function greet(string $name null): string { return $name null ? 陌生人 : $name; }PHP 8.4 起这种写法会触发弃用提示Deprecated: Implicitly marking parameter $name as nullable is deprecated, the explicit nullable type must be used instead修法有两个看你的意图?php declare(strict_types1); // ✅ 修法一确实允许传 null就显式写 ? function greet(?string $name null): string { return $name null ? 陌生人 : $name; } // ✅ 修法二本来就不该传 null就把默认值改成有意义的值 function greet(string $name 陌生人): string { return $name; } echo greet(), PHP_EOL; echo greet(null), PHP_EOL; echo greet(访客), PHP_EOL;坏消息是这类写法在大量第三方库里存在Laravel 自身的新版本已经清理过但它的历史依赖、以及各种老旧 SDK 未必。所以升级 8.4 后如果日志刷屏先别怀疑自己的代码用下面这个脚本把候选位置扫出来。四、扫描隐式可空参数下面这个脚本做的是静态文本扫描用token_get_all()去掉注释再用正则找形如Type $param null的签名并排除已经写了?或联合类型里已经含null的情况。它给出的是候选清单不是判决书——最终确认要看运行时的弃用日志。需要 PHP 8.0用到了str_contains?php declare(strict_types1); // scan-implicit-nullable.php —— 用法: php scan-implicit-nullable.php ./vendor function stripComments(string $code): string { $out ; foreach (token_get_all($code) as $token) { if (is_array($token)) { if ($token[0] T_COMMENT || $token[0] T_DOC_COMMENT) { // 用等长空格替换保证后续定位的行号不失真 $out . str_repeat( , strlen($token[1])); continue; } $out . $token[1]; } else { $out . $token; } } return $out; } $root $argv[1] ?? .; if (!is_dir($root)) { fwrite(STDERR, 目录不存在: {$root}\n); exit(1); } // 类型可能带命名空间和联合类型 变量名 null // (?!\?) 排除已经是 ?string $x null 的写法 $pattern /(?!\?)\b([A-Za-z_\\\\][A-Za-z0-9_\\\\]*(?:\s*\|\s*[A-Za-z_\\\\][A-Za-z0-9_\\\\]*)*)\s\$([A-Za-z_][A-Za-z0-9_]*)\s*\s*null\b/; $hits 0; $scanned 0; $iterator new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if (!$file-isFile() || strtolower($file-getExtension()) ! php) { continue; } $path $file-getPathname(); $code file_get_contents($path); if ($code false) { continue; } $scanned; $clean stripComments($code); if (preg_match_all($pattern, $clean, $matches, PREG_OFFSET_CAPTURE) 0) { continue; } $total count($matches[0]); for ($i 0; $i $total; $i) { $type strtolower($matches[1][$i][0]); $name $matches[2][$i][0]; $pos $matches[0][$i][1]; // 联合类型里已经含 null/mixed 的不算隐式可空 if (str_contains($type, null) || str_contains($type, mixed)) { continue; } $line substr_count($clean, \n, 0, $pos) 1; printf(%s:%d %s \$%s null\n, $path, $line, $matches[1][$i][0], $name); $hits; } } printf(\n扫描了 %d 个文件发现 %d 处候选。\n, $scanned, $hits); echo 注意这是候选清单最终请以运行时 Deprecated 日志为准。\n;跑法php scan-implicit-nullable.php ./vendor php scan-implicit-nullable.php ./app./app是你自己的代码先把它清干净./vendor里的属于第三方能升级就升级不能升级就临时压一压日志级别——但要清楚这是绕过而不是修复。五、PHP 8.4 带来的新写法装完之后能用什么顺带确认一下 8.4 已经就位。下面这个脚本全部使用 PHP 8.4 的新特性在 8.3 及以下会因为语法错误或未定义函数直接失败能跑通就说明版本对了。?php declare(strict_types1); // php84-check.php —— 需要 PHP 8.4 // —— 1. array_find / array_find_key / array_any / array_allPHP 8.4 新增—— $users [ [name Ann, age 34], [name Bob, age 17], [name Cara, age 28], ]; $adult array_find($users, fn(array $u): bool $u[age] 18); echo 第一个成年人: , $adult[name], PHP_EOL; // Ann $minorKey array_find_key($users, fn(array $u): bool $u[age] 18); echo 第一个未成年人的键: , var_export($minorKey, true), PHP_EOL; // 1 echo 是否存在未成年人: , var_export(array_any($users, fn(array $u): bool $u[age] 18), true), PHP_EOL; echo 是否全是成年人 : , var_export(array_all($users, fn(array $u): bool $u[age] 18), true), PHP_EOL; // —— 2. Property HooksPHP 8.4 新增—— class Temperature { public float $celsius 0.0; public float $fahrenheit { get $this-celsius * 9 / 5 32; set (float $value) { $this-celsius ($value - 32) * 5 / 9; } } } $t new Temperature(); $t-celsius 100.0; printf(100.00 C %.2f F\n, $t-fahrenheit); // 212.00 $t-fahrenheit 32.0; printf( 32.00 F %.2f C\n, $t-celsius); // 0.00 // —— 3. 类实例化后直接访问成员不再需要外层括号PHP 8.4 新增—— $today new DateTimeImmutable()-format(Y-m-d); echo 今天: , $today, PHP_EOL; // —— 4. mb_trim / mb_ltrim / mb_rtrimPHP 8.4 新增—— // trim() 按 ASCII 判断空白mb_trim() 会考虑多字节空白字符 echo [, mb_trim( hello ), ], PHP_EOL; // —— 5. #[\Deprecated] 属性PHP 8.4 新增—— class Legacy { #[\Deprecated(请改用 newMethod())] public function oldMethod(): string { return old; } public function newMethod(): string { return new; } } echo (new Legacy())-newMethod(), PHP_EOL;这里要划一条线上面这些写法只在 PHP 8.4 上可用。如果项目要在 PHP 8.2 或 8.3 的服务器上部署代码里就不能出现array_find()、property hooks 和new Foo()-bar()这种不带括号的语法——前者是运行时未定义函数后者是语法错误连php -l都过不去。六、把流程走一遍# 1. 确认 CLI 的 PHP 版本 php -v # 2. 创建项目不写版本约束时Composer 会取当前环境下最新的可安装版本 composer create-project laravel/laravel demo # 3. 进去看看实际装到了什么版本、约束是什么 cd demo composer show laravel/framework | head -20 composer check-platform-reqs # 4. 生成密钥create-project 一般会自动执行导入现有项目才需要手动做 php artisan key:generate # 5. 启动内置服务器看看 php artisan servecomposer create-project的第三个参数可以指定版本约束比如^12.0或^11.0。不写的话取最新可安装版本——这正是能否装最新版这个问题的最直接答案。站点部署到 Web 服务器时别忘了根目录要指向项目的public子目录这一点跟 PHP 版本无关。七、装不上时的三条出路如果composer create-project真的失败了别急着上--ignore-platform-reqs。按下面的顺序试出路一降 Laravel 大版本。最新的装不上就用次新的比如把^12.0换成^11.0。Laravel 11 和 12 的 PHP 门槛相同通常不是版本问题但值得排除。出路二把问题定位到具体的包。报错信息会指明是哪个包拒绝了当前 PHP。升级那个包或者找替代品。出路三对齐平台约束。如果开发机是 8.4、生产服务器是 8.2在composer.json里声明目标平台composer config platform.php 8.2.9这会让 Composer 在假装自己是 8.2.9的前提下解析依赖解析出来的composer.lock拿到生产上就能装。至于composer install --ignore-platform-reqs它的作用是跳过检查不是解决问题。跳过之后你会在 PHP 8.2 的服务器上装出需要 8.3 语法的包然后在运行时收到一个语法错误。它只适合在 CI 里临时装依赖跑静态分析。常见坑点坑 1用 8.4 生成的composer.lock直接部署到 8.2 的服务器❌ 本地 8.4 上composer update把composer.lock提交上去生产报锁文件里没有兼容的包组合✅ 更新依赖前先声明目标平台composer config platform.php 8.2.9 composer update坑 2看到满屏Implicitly marking parameter ... as nullable就去翻自己的代码❌ 花几个小时改自己的业务代码日志一行没少✅ 用第四节的扫描脚本先定位到文件。绝大多数情况下这些提示来自vendor/里的老依赖坑 3把~8.2.0当成^8.2来读❌ 看到某个包写~8.2.0就以为它支持 8.4然后对着解析失败的报错找不到原因✅ 波浪号是允许最后一位变动~8.2.0的意思是 8.2.x8.4 会被拒。要宽松的语义用^坑 4把--ignore-platform-reqs当成常规安装选项❌ 每次composer install都加上这个参数把版本约束当噪音✅ 它跳过检查但不改变依赖本身的要求。用composer config platform.php才是正确做法坑 5在 PHP 8.4 上写了 8.4 专属语法又要求兼容 8.2❌ 代码里用了array_find()部署到 8.2 后报Call to undefined function array_find()✅ 这类函数属于 PHP 8.4 新增。要兼容 8.2 就用foreach自己实现或者把最低版本一并抬到 8.4坑 6new Foo()-bar()不带括号的写法被当成通用语法❌$x new DateTimeImmutable()-format(Y-m-d);写到只支持 8.3 的项目里php -l直接报语法错误✅ 这也是 PHP 8.4 才允许的写法。需要兼容旧版本就老老实实写两行$dt new DateTimeImmutable(); $day $dt-format(Y-m-d);坑 7只在 Web 侧换了 PHP 8.4命令行还是旧的❌ 面板里把站点切到 8.4然后composer create-project报版本不满足✅ Composer 走的是 CLI 的php与站点设置无关。用php -v确认真实版本必要时显式指定解释器路径坑 8装完项目忘了站点根目录要指向public❌ 浏览器打开站点看到目录列表或者能下载到.env✅ 网站根目录设置成项目路径/public这是 Laravel 的固定要求总结问题答案怎么验证PHP 8.4 能装 Laravel 吗能Laravel 11.x / 12.x 要求 PHP 8.2^8.2覆盖 8.4composer check-platform-reqs最新版是哪个不靠猜看 Composer 解析结果composer show laravel/framework --all装完会有什么新报错隐式可空参数弃用多来自vendor/第四节扫描脚本8.4 新语法能用吗array_find系列、property hooks、无括号实例化都可以第五节验证脚本要兼容旧服务器怎么办composer config platform.php 8.2.9生成的composer.lock能否在生产安装装不上怎么办降 Laravel 大版本、升级具体依赖、对齐 platform不要用--ignore-platform-reqs兜底PHP 8.4 与 Laravel 最新版本的组合在依赖层面是兼容的真正会拖后腿的是历史依赖里的隐式可空参数写法。所以升级动作应该是这样的顺序先把版本约束确认清楚再跑一遍候选扫描然后装项目最后用运行时的弃用日志把候选清单收敛成准确的待改列表。