Nullable 类型声明的两种写法及推荐风格?
解读
国内一线/二线公司在面试时,常把“Nullable 写法”作为考察 PHP7 以后类型系统是否熟练的“门槛题”。它看似简单,却能迅速区分候选人是否停留在 PHP5 时代。面试官真正想听的是:
- 两种语法都能脱口而出;
- 知道 PHP 8.1+ 推荐的“联合类型”写法;
- 能解释背后的 nullable 语义与底层实现(null 掩码位);
- 最后给出团队可落地的编码规范(PSR-12 风格)。
答不到第 3、4 点,基本会被判定为“只用过,没研究过”。
知识点
- 旧语法(PHP 7.0 引入):
?Type- 等价于
Type|null的语法糖 - 只能放在类型前,不能混写
- 等价于
- 新语法(PHP 8.0 起支持的联合类型):
Type|null- 通用联合类型,可扩展为
int|float|null - 与
?Type在底层生成的类型掩码完全一致,性能无差异
- 通用联合类型,可扩展为
- 默认值与可空性区别:
function foo(?int $v = null)合法function foo(int $v = null)非法(默认值为 null 但类型不可空)
- 属性、返回值、参数同时支持上述两种写法
- PSR-12 未强制 nullable 风格,但国内大厂普遍在 8.1+ 项目里用联合类型,以便后续平滑升级到 PHP 9 可能废弃
?前缀的传言
答案
两种写法:
- 问号前缀:
?string $name - 联合类型:
string|null $name
推荐风格(PHP 8.1 及以上新项目):
统一使用联合类型 string|null,理由:
- 与未来可能的纯联合类型语法保持一致,降低升级成本;
- 可读性更高,一眼看出“到底联合了谁”;
- 方便在代码审查工具(PHPStan level 9)中做更细粒度的类型收窄提示。
老项目(PHP 7.x 维护分支):保持 ?Type 即可,避免大规模重构。
拓展思考
- 底层实现:两种写法最终都生成同一份
ZEND_TYPE_NULLABLE掩码,OPcache 优化后字节码完全一致,性能无差异;面试时可反问“是否需要性能基准测试”,展示深度。 - 泛型与 Nullable:如果公司用 Psalm/PHPStan,泛型模板里写
@param T|null比?T更易被静态分析识别,减少误报。 - 向前兼容策略:在公共库中建议加
#[\AllowDynamicProperties]与string|null联合类型并存,既保证老用户不报错,又能让新用户享受严格类型。 - 面试加分句:“我们团队在 CI 里加了
php-cs-fixer规则nullable_type_declaration_for_default_null_value:false,强制所有新代码用联合类型,存量代码通过脚本一次性转换,合并请求里只 Review 业务逻辑,不再争论 nullable 风格。”