PHP闭包为何总需static修饰,有何深意?

摘要:为什么 PHP 闭包要加 static? 在 PHP 中,闭包的使用越来越普遍:依赖注入、中间件、集合回调,以及异步编程中的回调工具。 但闭包有一个行为可能会让人意外:在实例方法内部创建的闭包会自动携带对当前对象的引用,即使闭包内部并未使用
为什么 PHP 闭包要加 static? 在 PHP 中,闭包的使用越来越普遍:依赖注入、中间件、集合回调,以及异步编程中的回调工具。 但闭包有一个行为可能会让人意外:在实例方法内部创建的闭包会自动携带对当前对象的引用,即使闭包内部并未使用 $this。这种行为可能对对象生命周期产生意外影响,若不谨慎处理,还可能引发内存泄漏。 PHP 的内存管理机制 要理解这一点,需要先了解 PHP 如何管理内存。与 Java 等依赖垃圾回收器延迟释放内存的语言不同,PHP 使用引用计数(当然,PHP 实际上也有针对循环引用的垃圾回收器,但那是另一回事)。 当变量被赋值时,其内容需要存储在内存中;当变量不再使用时,内存可以被释放。写出如下代码: $a = 'Hello'; $b = $a; PHP 不会为 $b 创建第二块内存空间,而是直接标记它指向与 $a 相同的内存空间。如果随后给 $a 赋新值(如 "Hi"),则会分配新内存空间并让 $a 指向它,而 $b 继续指向原来的空间。如果将 NULL 赋给 $b,那么原来存储 "Hello" 的内存空间就不再被任何变量引用,可以被释放。PHP 通过维护引用计数来实现这一点,当计数归零时,空间即被释放。 对象的生命周期 对于对象,当引用计数归零后,在释放内存之前,如果类定义了 __destruct 方法,会先调用它: class Foo { public function __construct() { echo "Construct\n"; } public function __destruct() { echo "Destruct\n"; } } new Foo(); echo "End\n"; 输出: Construct Destruct End 对象未被赋给任何变量:它的计数器在构造函数调用后立即归零,__destruct 随即被调用。 如果将对象赋给变量,销毁则会延迟: $foo = new Foo(); echo "End\n"; 输出: Construct End Destruct 只要 $foo 指向对象,计数器就保持为 1。销毁发生在脚本末尾,所有变量被释放之后。要强制提前销毁,只需显式释放变量: $foo = new Foo(); echo "Before release\n"; $foo = null; echo "After release\n"; 输出: Construct Before release Destruct After release 闭包会让对象保持存活 来看 Bar 类的例子,它定义了 getCallback() 方法,返回一个读取 $this->id 属性的闭包: class Bar { public function __construct(private string $id) { echo "Construct\n"; } public function __destruct() { echo "Destruct\n"; } public function getCallback(): Closure { return function(): string { return $this->id; }; } } $bar = new Bar('foo'); $getId = $bar->getCallback(); echo "Before releasing the object\n"; $bar = null; echo "After releasing the object\n"; echo $getId() . "\n"; echo "End\n"; 输出: Construct Before releasing the object After releasing the object foo End Destruct 给 $bar 赋 null 时对象并未被销毁,因为闭包访问了 $this->id,这构成了对对象的引用。只要闭包存在,计数器就不会归零,直到脚本结束。如果提前给 $getId 重新赋值,__destruct 会更早被调用,因为释放变量同时也释放了对 $this 的引用。
阅读全文