ThinkPHP 5.0 Config::get 第二参数是 scope 不是 default
在一个后台管理系统里,我把支付渠道读取逻辑从 Laravel 风格迁移到 ThinkPHP 5.0。代码看上去无懈可击:
$channel = Config::get('payment.channel', 'mock');「配置缺失时兜底用 mock 模式」——逻辑完全正确。然而一旦 payment.php 配置文件不存在,接口就以 500 崩溃,日志里只剩一行意义不明的 PHP 8 警告。
现象
Config::get('a.b', 'fallback')返回null,而非字符串'fallback'- PHP 错误日志出现:
strtoupper(): Passing null to parameter #1 ($string) of type string is deprecated - 把 key 改成任何一个已存在的配置 key 后,一切恢复正常
- 删掉第二参数改用
?? 'mock'后,null 问题消失
根因
ThinkPHP 5.0 的 Config::get 签名和几乎所有其他框架都不一样:
// thinkphp/library/think/Config.php (5.0.25)
public static function get($name = null, $range = '')
{
$range = $range ?: self::$range;
// 在 $range 作用域里查找 $name,找不到返回 null
}第二参数 $range 是配置作用域(scope),不是 default value。
Config::get('payment.channel', 'mock') 实际执行的是:「在 scope 名为 mock 的作用域里查找 key payment.channel」——这个 scope 不存在,所以返回 null,跟你传的 'mock' 字面量毫无关系。
| 框架 | 第二参数语义 |
|------|-------------|
| ThinkPHP 5.0 | scope(作用域名) |
| ThinkPHP 5.1 / 6.x | default value |
| Laravel config() | default value |
从 Laravel 或 TP 5.1+ 迁移到 5.0 时,这个签名差异不会报错、不会有 IDE 警告,只会在配置 key 缺失时静默返回 null。PHP 8 把 null 传给 strtoupper / strlen 等函数会触发 TypeError,才让这个坑现身。
正解
有三种等价写法,按业务语义选一种即可。
方式 1 — ?: 三元回退(最常用,null/''/'0'/false 都会回退)
$channel = Config::get('payment.channel') ?: 'mock';
// 全局 helper 同理
$channel = config('payment.channel') ?: 'mock';方式 2 — ?? null 合并(PHP 7+,仅 null 时回退,'0'/false 保留原值)
$channel = Config::get('payment.channel') ?? 'mock';方式 3 — 封装 config_or helper(让调用侧语义和其他框架对齐)
// application/common.php
if (!function_exists('config_or')) {
function config_or($name, $default = null) {
$v = \think\Config::get($name);
return $v === null ? $default : $v;
}
}
// 调用
$channel = config_or('payment.channel', 'mock');TP 5.0 的 Config::get 只传一个参数,回退值用 ?: 'default' 或 ?? 'default' 接在外面。如果项目里大量使用带第二参的写法,用 config_or 封一层,一次性把习惯迁移过来。
全项目扫描
如果是接手存量项目,用这条命令把所有疑似误用的调用找出来:
grep -rnE "Config::get\([^,]+,[^)]+\)" application/ --include="*.php" \
| grep -v "Config::get([^,]*, *'')" \
| grep -v 'Config::get([^,]*, *"")'多数命中都是把第二参当 default 用的 bug;真正想切 scope 的保留即可。
一句话外卖
ThinkPHP 5.0 的
Config::get($name, $scope)与同门 5.1+ 以及 Laravel 的 API 存在语义断层——迁移时永远别依赖第二参数做兜底,必须显式用?: $default。