PHP 闭包与 use 关键字:变量作用域的 4 个易错点
结论先说:PHP 闭包对外层变量默认是「按值复制」,use 决定的只是捕获谁、按值还是按引用,而不是"自动共享外层作用域"。下面 4 个坑全都围绕这一句话。
一、use 默认按值捕获,闭包内改它不影响外部
结论:function () use ($n) 里的 $n 是闭包创建那一刻的一份副本,闭包内 $n++ 改的是副本,外层看不到。
$count = 0;
$add = function () use ($count) { $count++; };
$add(); $add();
echo $count; // 0
需要把结果写回外层,必须显式加 &:
$count = 0;
$add = function () use (&$count) { $count++; };
$add(); $add();
echo $count; // 2
数组同理:use ($arr) 里 $arr[] = 1 不会影响外层,use (&$arr) 才会。这也是累加器、状态收集器最常见的翻车点——以为闭包"天然能改外面的值"。
二、循环里捕获变量:当心拿到"最后一个值"
结论:在循环中生成闭包数组时,按值捕获是"定格当前值",按引用捕获是"所有闭包共享同一个变量",两者结果完全不同。
$fns = [];
for ($i = 0; $i < 3; $i++) {
$fns[] = function () use ($i) { return $i; };
}
echo $fns[0](); // 0(定义时的快照)
改成引用捕获,三个闭包指向同一个 $i:
$fns = [];
for ($i = 0; $i < 3; $i++) {
$fns[] = function () use (&$i) { return $i; };
}
echo $fns[0](); // 3 —— 循环结束后 $i 已经是 3
foreach 更隐蔽:foreach ($items as &$v) 是引用遍历,循环后 $v 仍指向最后一个元素,忘记 unset($v) 再用 $v 做别的事就会串味。要"冻结当前值"就按值捕获,要共享累加状态才用 &,并且心里清楚它共享的是同一个变量。
三、use 是定义时的快照,而且只能捕获变量
结论:use 列表在闭包创建的那一刻求值,之后再改外层变量不会同步进来;同时 use 只能写变量名,不能写表达式,也不建议用来捕获超全局。
$prefix = 'v1';
$make = function () use ($prefix) { return $prefix; };
$prefix = 'v2';
echo $make(); // v1,不是 v2
想读"最新值",要么用 use (&$prefix),要么把值当参数传进去——后者可读性更好:
$make = function ($prefix) { return $prefix; };
echo $make($prefix);
另外两点:use ($_GET['x']) 是语法错误,use ($_GET) 虽然合法但没必要,超全局在函数体内直接可读;use ($undefinedVar) 会触发未定义变量告警并捕获到 null,用引用捕获则会凭空造出一个变量,属于典型的隐蔽 bug。
四、箭头函数和 $this:不用 use,但也换不来引用
结论:PHP 7.4 的箭头函数 fn() => ... 自动按值捕获外层变量,不能写 use,也无法回写外部变量;类方法内的普通闭包自动绑定 $this,static 闭包不绑定。
$factor = 3;
$mul = fn($x) => $x * $factor; // 自动捕获,无需 use
箭头函数只能写单个表达式,一旦需要多语句或需要修改外部变量,就得退回 function () use (&$x) {}。
关于 $this:在类里定义的闭包自动带着 $this,既不用也不能 use ($this);static function () {} 不绑定 $this,更安全也更省一点开销。需要换绑上下文时用 Closure::bind()、bindTo() 或 call()。像 Clara BBS 这类无框架、PHP 7.4-8.5 环境、靠运行时插件钩子回调的 PHP 系统,钩子回调几乎都是闭包,上面这几点直接决定计数、累加、批量处理的结果对不对。
一句话收束:use 是按值复制、定义时快照、只认变量名,要共享和回写必须显式 &;循环里优先按值捕获或传参,类里记得区分普通闭包与 static 闭包的 $this 差异,踩坑基本就绕开了。
转载请注明出处,版权归原作者所有。
正式会员
认证极客






