global、static、local 作用域分别在什么场景生效?

解读

在国内一线/二线互联网公司的 PHP 面试中,作用域是“手写代码”环节的高频扣分点。很多候选人能背出三者的定义,却说不清“什么时候必须用、什么时候坚决不能用”。面试官真正想考察的是:

  1. 你是否理解 Zend 引擎的符号表(symbol_table)与活动记录(active_symbol_table)分离机制;
  2. 你是否能在高并发、长生命周期脚本(Swoole、WorkerMan)里避开“全局污染”与“内存泄漏”;
  3. 你是否能根据业务场景选择闭包、依赖注入、单例而非简单粗暴的 global/static。

知识点

  1. Local 作用域
    函数/方法内部声明的变量默认是 local,在 Zend 虚拟机里对应 active_symbol_table,函数返回即销毁。
    场景:

    • 纯计算、无副作用的工具函数(如价格格式化、加密)。
    • 需要并发安全时,必须保持局部变量,禁止任何 global/static。
  2. Global 作用域
    函数内部通过 global$GLOBALS[] 把变量挂到 symbol_table,生命周期与请求绑定(FPM 下请求结束即销毁;Swoole 下是 Worker 级,可能跨请求)。
    场景:

    • 传统 FPM 短生命周期里,临时透传“不可变”配置(如一次性的渠道号)。
    • 绝不能用于“可写”状态(订单库存、用户会话),否则 Swoole/WorkerMan 会出现请求间串数据。
  3. Static 作用域
    函数内部 static $var 只在第一次调用时初始化,变量被 Zend 挂在 function.static_variables 数组里,生命周期与 OPcache 缓存的函数条目一致(FPM 下跨多次调用,Swoole 下跨请求)。
    场景:

    • 需要“函数级缓存”且数据量极小、幂等、无外部依赖(如解析 XML 配置、编译正则)。
    • 禁止用于“与请求相关”的数据(用户 ID、订单号),否则 Swoole 常驻内存会泄漏。
    • 禁止与“可变参数”结合,static 变量不会随调用栈回收,会导致脏数据。

答案

local:函数内部默认生效,用于一次性、无状态的计算,保证并发安全。
global:在函数内用 global 关键字显式引入,只在“短生命周期 FPM”里读取不可变配置时可用;在 Swoole/WorkerMan 常驻进程里禁止使用。
static:在函数内用 static 关键字声明,用于“函数级缓存”且数据与请求无关、体积固定、线程安全;禁止缓存用户态数据,禁止在常驻进程里滥用。

拓展思考

  1. Swoole 4.x 协程环境下,global 变量是 Worker 级,协程间共享,会导致数据错乱;正确做法是使用 Context::get() 协程上下文或依赖注入容器。
  2. static 变量虽然快,但 OPcache 重启前不会释放,若缓存了大型数组会造成内存膨胀;可改用 apcu_entry() 或 Redis,并设置 TTL。
  3. 单元测试时,static 变量会跨测试用例“残留”,导致用例互相污染;PHPUnit 官方建议 @runInSeparateProcess 或重构为“无状态服务类”。