在 PHP 中,匿名函数和闭包经常被混在一起说,但严格来讲它们描述的是两个维度:匿名函数强调的是函数没有名称,闭包强调的是函数能够捕获并携带外部作用域的变量。日常开发里,只要一个匿名函数通过 use 引入外部变量,它就不再只是普通匿名函数,而是一个真正的闭包。下面从最基础的语法开始,逐步把 use 的规则和 Closure 对象的能力讲清楚。

基础语法:匿名函数如何创建和调用
PHP 从 5.3 开始支持匿名函数,写法就是把 function 后面的函数名去掉,并保留参数列表和函数体。这样的函数没有名字,但会返回一个 Closure 对象,因此可以赋值给变量、存入数组,也可以作为其他函数的参数传入。
<?php
$greet = function($name) {
echo "Hello, $name\n";
};
$greet("PHP");
上面的代码把匿名函数赋给变量 $greet,随后通过变量名加括号的方式完成调用。这种写法特别适合临时定义一次性逻辑,不必为了一个简单处理单独在全局声明函数,也避免了函数名冲突。匿名函数本身可以被赋值、被传递,甚至可以在运行时动态生成,这为后续闭包的使用打下了基础。
需要明确的是,普通匿名函数还不能访问创建它的外部作用域变量。也就是说,如果外部有一个 $name,匿名函数内部直接使用它并不会得到想要的结果。真正解决这个问题的机制,就是下面要讲的 use 关键字。
use 关键字如何把外部变量带进闭包
默认情况下,匿名函数内部不能直接访问外部作用域的变量,必须通过 use 显式导入。use 列表写在 function 参数列表之后、函数体之前。这个导入发生在闭包创建时,且默认是值传递。换句话说,闭包内部拿到的是外部变量当时的一份副本,后续外部变量变化不会影响闭包内部。
<?php
$message = "hello";
$closure = function() use ($message) {
echo $message;
};
$message = "world";
$closure(); // 输出 hello
上面这个例子中,即使 $message 后来被改成 world,闭包执行时输出的仍然是创建闭包那一刻捕获到的 hello。这个行为非常关键,它意味着闭包的数据是快照式的,不是实时读取外部变量。只有在理解这一点之后,才能避免回调函数中变量值不符合预期的奇怪问题。
如果需要在闭包内部修改外部变量,并且让外部同步看到变化,就必须在 use 中加引用符号 &,写法是 use (&$var)。此时变量以引用方式传递,内部和外部的变量指向同一块存储区。
<?php
$count = 0;
$increment = function() use (&$count) {
$count++;
};
$increment();
$increment();
echo $count; // 输出 2
引用传递虽然灵活,但也会让闭包持有外部变量的引用,变量可能不会及时被回收。如果闭包被保存到全局数组、静态容器或长生命周期对象中,容易造成隐蔽的内存占用。因此除非确实需要修改外部变量,否则优先使用默认的值传递方式。
典型应用场景:数组处理与排序
闭包最常见的用途是作为回调函数传给 array_map、array_filter、usort 等数组处理函数。这类函数要求传入一个可调用结构,闭包能直接把逻辑写在调用处,可读性更好,也省去了单独声明具名函数的麻烦。
<?php
$threshold = 10;
$numbers = [5, 12, 8, 20];
$filtered = array_filter($numbers, function($value) use ($threshold) {
return $value > $threshold;
});
print_r($filtered); // 保留大于 10 的数
这里闭包通过 use 捕获了外部阈值 $threshold,让过滤条件可以灵活调整。类似地,usort 可以根据外部配置决定排序方向,而不需要为每种排序方式写多个具名函数。
<?php
$order = "desc";
usort($numbers, function($a, $b) use ($order) {
if ($order === "desc") {
return $b - $a;
}
return $a - $b;
});
这种写法在框架中也大量存在,例如路由注册、事件监听、容器服务定义。闭包让“把逻辑作为参数传递”变得非常自然。但要注意,如果闭包内部依赖大量外部变量,参数列表会变得很长,此时可以考虑把外部状态封装成对象,再通过 use 导入对象,或直接使用面向对象方式处理。
Closure 类与 $this 绑定
前面提到匿名函数本质上返回 Closure 对象。因此可以使用 Closure 类提供的 bind 和 bindTo 方法,动态改变闭包内部的 $this 指向。这在一些框架的扩展机制中很常见,比如给一个已经存在的类动态添加方法,或把闭包注入到对象上下文中执行。
<?php
class User {
private $name = "PHP";
}
$getUserName = function() {
return $this->name;
};
$user = new User();
$bound = $getUserName->bindTo($user, User::class);
echo $bound();
bindTo 的第一个参数是新对象,第二个参数是作用域类,传入类名后闭包就可以访问该类的私有成员。如果不传第二个参数,只能访问公共成员。Closure 类的另一个静态方法 Closure::bind 参数类似,只是调用方式稍有不同。
<?php
class User {
private $name = "PHP";
}
$getUserName = function() {
return $this->name;
};
$user = new User();
$bound = Closure::bind($getUserName, $user, User::class);
echo $bound();
通过 bind 或 bindTo 绑定对象后,闭包内部的 $this 不再指向创建时的上下文,而是指向绑定后的对象。这种动态改变上下文的能力在依赖注入、事件插件和权限控制等场景中非常实用。不过它也会增加代码的理解成本,使用时应当保持绑定逻辑集中、清晰。
静态匿名函数与闭包性能细节
当闭包不需要访问 $this 时,可以声明为 static function() {},这样可以避免隐式绑定当前对象,减小对象引用带来的性能开销和内存泄漏风险。静态匿名函数不会自动绑定 $this,但仍然可以通过 use 导入需要的变量。
<?php
class Worker {
public function run() {
$task = static function($value) {
return $value * 2;
};
return $task(5);
}
}
$worker = new Worker();
echo $worker->run(); // 10
在长生命周期的进程如 Swoole、Workerman 中,对象绑定闭包如果被保存在静态数组或全局变量中,可能导致 Worker 对象无法释放。使用 static 匿名函数可以规避一部分隐式绑定。这个性能细节容易被忽略,但对于常驻内存的 PHP 应用来说影响非常真实。
闭包并不是越用越好,它适合短小、上下文明确的逻辑。如果某段逻辑开始膨胀,或者需要复用多次,就应该考虑提取成具名函数或类方法。匿名函数和闭包最终是代码组织工具,不是为了消灭具名函数而存在。