Zsens Admin 插件选项读取:我因 `get_option` 返回字符串 `"false"` 导致条件分支永久为真,踩了 PHP 弱类型的暗坑

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 42 浏览 0 回复

上周给 Zsens Admin 加"功能开关"时,我自信满满写了段看似无害的代码,结果用户后台疯狂报错,而我本地复现不了。最后远程连过去才发现,问题藏在 get_option 的返回值类型里——一个我从没当回事的细节。

先看我的"翻车现场":

// 错误写法:我以为存了布尔 false,读出来就能当布尔用
function zsens_should_enable_beta_feature() {
    $enabled = get_option( 'zsens_beta_feature_enabled', false );
    
    // 致命假设:$enabled 是布尔 false
    if ( $enabled ) {
        return true;
    }
    return false;
}

// 数据库里实际存的是:s:5:"false"; (序列化后的字符串)
// 或者更常见:用户勾选了复选框又取消,插件存了 '0' 或空字符串 ''

问题在哪?get_option 的第二个参数 false 只在选项不存在时生效。一旦选项被创建过——哪怕存的是字符串 "false""0"""——它永远返回那个字符串,而不是布尔。

PHP 的坑来了:字符串 "false" 在条件判断里是 true(非空非零字符串),字符串 "0"false,但空字符串 "" 又是 false。三种"假值"表现不一致,而我的代码假设它们统一按布尔处理。

更隐蔽的是,有些用户是从旧版本升级来的,数据库里残留着序列化后的字符串;有些是通过 WP-CLI 导入的配置,JSON 解码后全成了字符串类型。本地开发时我用的 wp_options 是全新安装的,选项压根不存在,反而走了默认值分支,完美掩盖了 bug。

修复后的写法,我现在的强制规范:

function zsens_should_enable_beta_feature() {
    $raw = get_option( 'zsens_beta_feature_enabled', '0' );
    
    // 显式归一化:只认 '1'、1、true,其余全算关
    return in_array( $raw, [ '1', 1, true, 'true' ], true );
}

如果选项值来源复杂,我会再加一层"类型消毒":

function zsens_normalize_bool( $value ) {
    if ( is_bool( $value ) ) {
        return $value;
    }
    if ( is_string( $value ) ) {
        $value = strtolower( trim( $value ) );
        return in_array( $value, [ '1', 'true', 'yes', 'on' ], true );
    }
    if ( is_numeric( $value ) ) {
        return (int) $value !== 0;
    }
    return false; // 未知类型保守处理
}

存储端也要对称处理,别让用户界面和数据库语义脱节:

// 保存时统一存 '1' 或 '0',拒绝序列化布尔
update_option( 'zsens_beta_feature_enabled', $is_enabled ? '1' : '0' );

额外踩过的关联坑:有人用 !!get_option(...) 强制转布尔,但 !!"false" 仍然是 true;也有人用 filter_var,可 filter_var( "false", FILTER_VALIDATE_BOOLEAN ) 返回 false——这反而对了,但 filter_var"0" 也是 false,行为又和直觉不同。

我的结论:插件配置项但凡涉及开关,一律按"字符串契约"处理,存 '1'/'0',读时显式白名单匹配。别信 PHP 的弱类型"智能转换",它在跨环境、跨导入方式时只会给你惊喜。

评论0
回复 · 0
还没有回复
微信客服 微信客服