返回博客

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' 字面量毫无关系。

坑:TP5.0 第二参是 scope,迁自其他框架必踩

| 框架 | 第二参数语义 | |------|-------------| | 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