如何定义与使用变量和常量?请说明区别与命名规范
解读
面试官问这道题,并不是想听“变量前面加$、常量用define”这种课本式回答,而是想确认三件事:
- 你对PHP符号表、内存分配、作用域机制有没有实战级理解;
- 你是否能在高并发、长生命周期(Swoole、WorkerMan)场景下避开常量重复定义、变量内存泄漏等坑;
- 你是否熟悉国内团队普遍遵循的《PHP PSR-12》命名规范以及《阿里巴巴PHP开发手册》的落地细节。
因此,回答要“先原理、后场景、再规范”,用“线上故障案例”做背书,才能拉开与普通候选人的差距。
知识点
- 变量(variable):运行时符号表中的可变成员,zval结构体管理引用计数、类型标志、is_ref__gc字段。
- 常量(constant):EG(zend_constants)哈希表中的不可变成员,编译阶段即绑定,生命周期与请求无关(SAPI生命周期内)。
- 作用域差异:变量默认受函数作用域隔离;常量全局可见,但受命名空间前缀约束。
- 命名空间下的常量:const在编译期解析,define在运行期解析;后者无法定义数组,前者可以。
- 运行时缓存:OPcache把常量键名放进持久化SHM,重复define会触发Warning并返回false。
- 国内规范:
– 变量:小写蛇形_userId(部分团队)。
– 常量:大写蛇形+模块前缀ORDER_STATUS_PAID;禁止魔法数字,必须对应注释写明业务含义。 - 易踩的坑:
– 在Swoole协程中把常量当配置,导致热更新失败;
– 在PHPUnit数据供给器里动态define,造成用例间污染;
– 把$_GLOBALS当常量用,引发FPM Worker内存暴涨。
答案
“变量通过美元符userOrderList,常量采用大写蛇形+业务前缀PAYMENT_STATUS_SUCCESS,并配套PHPDoc说明业务取值范围。
线上曾出现一次事故:开发在Swoole Server启动脚本里用define配置数据库节点,运维滚动发布时只更新了配置文件,未重启Master,导致新Worker仍读到旧常量,引发全站下单路由到已下线的旧库。解决方案是把配置放进协程安全的Config类,用数组模拟可热更新的‘运行时常量’,同时把真正的业务常量固化在代码层,避免运行时变更。
因此,变量与常量的区别不仅是‘能不能改’,更是‘生命周期、作用域、内存位置、热更新策略’的综合差异,架构阶段就必须分清楚。”
拓展思考
- PHP8的const可见性:现在class里可以写private const,面试可延伸“为什么还需要私有常量?会不会破坏常量全局可见的语义?”——引导到“编译期优化+IDE跳转友好”的层面。
- 枚举(enum)替代常量:PHP8.1引入Enum,可用来彻底干掉魔法常量,面试官爱问“Enum和常量性能差距多少?”——可答“OPcache会把两者都放进持久SHM,性能差异<1%,但Enum自带类型校验,可省掉大量if/throw”。
- 容器化场景:Kubernetes ConfigMap热挂载后,PHP-FPM需要USR2信号重载才能刷新常量,如何做到“代码层常量+配置层变量”分层?可引入“env()读配置,config:cache生成数组,常量只保留魔法数字”的阿里规范方案。
题目导航