首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >PHP 为什么使用静态闭包?

PHP 为什么使用静态闭包?

作者头像
Tinywan
发布2026-07-01 16:23:10
发布2026-07-01 16:23:10
140
举报
文章被收录于专栏:开源技术小栈开源技术小栈

为什么使用静态闭包?

在 PHP 中,我们越来越频繁地使用闭包(Closure)[1],例如依赖注入、中间件、集合回调,以及异步处理。

然而,闭包有一个可能出乎意料的行为:在实例方法中创建的任何闭包,都会自动携带对当前对象的引用,即使它根本没有使用$this。 这种行为可能会对对象的生命周期产生意外影响,如果不注意,还可能导致内存泄漏

要理解其中的原因,我们首先需要了解 PHP 是如何管理内存的。

与 Java 等依赖垃圾回收器(延迟释放内存)的语言不同,PHP 使用的是引用计数(PHP 也有垃圾回收器[2]来处理循环引用,但那是另一个话题)。

当你赋值给一个变量时,其内容会存储在内存中;当变量不再被使用时,内存就可以被释放。例如:

代码语言:javascript
复制
<?php
$a = 'Hello';
$b = $a;

PHP 不会为 b 再分配一块新的内存空间,而是让它指向与 a 相同的内存空间。如果你之后给 a 赋新值(如 "Hi"),则会分配新的内存空间,

如果再把 $b 赋值为 NULL,那么存放 "Hello" 的内存空间就不再被任何变量引用,可以被释放。PHP 通过维护引用计数来实现这一点,当计数降为 0 时,内存空间就被释放。

对象的生命周期

对于对象,当引用计数降为 0 时,在释放内存之前,如果类定义了 __destruct 方法,则会先调用它:

代码语言:javascript
复制
<?php
class Foo {
    public function __construct() {
        echo "Construct\n";
    }

    public function __destruct() {
        echo "Destruct\n";
    }
}

new Foo();
echo "End\n";

输出:

代码语言:javascript
复制
Construct
Destruct
End

对象没有被赋值给任何变量,因此构造函数调用后引用计数立即变为 0,__destruct 紧接着被调用。

而如果对象被赋值给变量,销毁就会被推迟:

代码语言:javascript
复制
<?php
$foo = new Foo();
echo "End\n";

输出:

代码语言:javascript
复制
Construct
End
Destruct

只要 $foo 还指向该对象,计数就保持为 1。只有在脚本结束、所有变量都被释放时才会销毁。要强制提前销毁,只需显式释放变量(重新赋值或使用 unset()):

代码语言:javascript
复制
<?php
$foo = new Foo();
echo "Before release\n";
$foo = null;
echo "After release\n";

输出:

代码语言:javascript
复制
Construct
Before release
Destruct
After release

闭包会让对象保持存活

下面来看一个定义了 getCallback() 方法的类 Bar,该方法返回一个读取 $this->id 属性的闭包:

代码语言:javascript
复制
<?php
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";

输出:

代码语言:javascript
复制
Construct
Before releasing the object
After releasing the object
foo
End
Destruct

当我们把 bar 赋值为 null 时,对象并没有被销毁,因为闭包访问了 this->id,从而构成了对对象的引用。只要闭包存在(即脚本结束前),引用计数就不会降为 0。如果我们之后把

即使不使用 $this,对象依然存活

如果闭包中没有使用$this,会发生什么?

代码语言:javascript
复制
<?php
class Bar {
    public function __construct() {
        echo "Construct\n";
    }

    public function __destruct() {
        echo "Destruct\n";
    }

    public function getCallback(): Closure {
        return function(): void {};
    }
}

$bar = new Bar();
$callback = $bar->getCallback();

echo "Before releasing the object\n";
$bar = null;
echo "After releasing the object\n";
$callback = null;
echo "End\n";

输出:

代码语言:javascript
复制
Construct
Before releasing the object
After releasing the object
Destruct
End

对象仍然被保持存活!即使我们没有在闭包中使用 this,PHP 仍然会自动将 this 绑定到在实例方法中创建的任何闭包上——无论是否使用、是否为空。

闭包因此总是隐式地携带对对象的引用,这在阅读代码时是看不出来的。

当然,如果闭包是在静态方法中创建的,就不会有对 $this 的引用,对象会在变量释放时立即被销毁:

代码语言:javascript
复制
<?php
class Bar {
    public function __construct() {
        echo "Construct\n";
    }

    public function __destruct() {
        echo "Destruct\n";
    }

    public static function getCallback(): Closure {
        return function(): void {};
    }
}

$bar = new Bar();
$closure = $bar::getCallback();

echo "Before releasing the object\n";
$bar = null;
echo "End\n";

输出:

代码语言:javascript
复制
Construct
Before releasing the object
Destruct
End

静态闭包

在闭包前加上 static 关键字,可以显式禁止它绑定到 $this。此时 PHP 不再存储任何对对象的引用(即使是隐式的)。

代码语言:javascript
复制
// ...
public function getCallback(): Closure {
    return static function(): void {};
}
// ...

输出:

代码语言:javascript
复制
Construct
Before releasing the object
Destruct
End

如果需要在闭包中使用属性的值,可以通过 use 来传递:

代码语言:javascript
复制
// ...
public function getCallback(): Closure {
    $id = $this->id;
    return static function() use ($id): string {
        return $id;
    };
}

这样,PHP 会在变量释放后立即销毁对象,因为闭包不再持有对它的引用。

如果你试图在静态闭包中使用 $this,PHP 会直接报错:

代码语言:javascript
复制
static function(): string {
    return $this->id; // Error: Using $this when not in object context
};

PHP 引擎通过这种方式保护你,避免意外捕获对象。

短闭包

短闭包(fn() =>)提供了更简洁的语法,并且会自动捕获外部作用域的变量(无需 use)。

但它们在处理 $this 时与普通闭包行为一致:

代码语言:javascript
复制
public function getCallback(): Closure {
    return fn(): string => $this->id;
}

这里 $this 被隐式捕获,对象会一直存活到闭包被释放。

static 关键字同样适用于短闭包。外部变量仍会自动捕获,但 $this 不再被捕获:

代码语言:javascript
复制
public function getCallback(): Closure {
    return static fn(): string => $this->id; // Error: Using $this when not in object context
}

要想传递值而不捕获对象,只需提前提取出来:

代码语言:javascript
复制
public function getCallback(): Closure {
    $id = $this->id;
    return static fn(): string => $id;
}

此时 id 是按值捕获的,this 不再参与其中……

引用链接

[1] 闭包(Closure): https://www.php.net/closure [2] 垃圾回收器: https://www.php.net/manual/en/features.gc.php

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 对象的生命周期
  • 闭包会让对象保持存活
  • 即使不使用 $this,对象依然存活
  • 静态闭包
  • 短闭包
    • 引用链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档