Closure绑定$this导致GC无法触发__destruct的策略计数器问题
解决PHP Strategy类中Closure绑定导致析构函数不执行的问题
问题根源
核心问题是绑定后的Closure与Strategy实例形成了循环引用:
- 构造函数中将Closure绑定到
$this,导致Closure内部持有Strategy实例的强引用 - Strategy实例又通过
$this->strategy持有该Closure的引用
这种循环引用会让PHP垃圾回收器无法及时回收对象,__destruct()自然不会触发,计数器也就无法递减。
解决方案
方法1:使用WeakReference打破循环引用(PHP 7.4+推荐)
利用PHP 7.4引入的WeakReference类,让Closure持有实例的弱引用而非强引用,这样当实例被unset后,垃圾回收器可以正常回收对象,析构函数也能执行。
修改Strategy类的构造函数和析构函数:
class Strategy { private static ?StrategyCounter $strategyCounter = null; private string $name; private Closure $strategy; public function __construct(Closure $strategy) { self::initCounter(); // 创建当前实例的弱引用 $weakRef = WeakReference::create($this); // 重新包装闭包,通过弱引用访问实例 $this->strategy = function (...$args) use ($strategy, $weakRef) { $instance = $weakRef->get(); if (!$instance) { throw new RuntimeException("Strategy实例已被销毁"); } // 将原闭包绑定到实例并执行 return $strategy->bindTo($instance)(...$args); }; $this->name = 'Strategy ' . self::$strategyCounter->getNextStrategyNumber(); } private static function initCounter() { if (!self::$strategyCounter) { self::$strategyCounter = new StrategyCounter(); } } public function __destruct() { self::$strategyCounter->decrementStrategyCounter(); } // 假设你有getName方法 public function getName(): string { return $this->name; } }
方法2:手动解除引用(兼容PHP低版本)
如果你的PHP版本低于7.4,无法使用WeakReference,可以添加一个手动销毁方法,主动解除Closure的引用并递减计数器:
class Strategy { // ... 其他代码不变 public function destroy(): void { // 解除闭包引用 unset($this->strategy); self::$strategyCounter->decrementStrategyCounter(); } }
使用时需要主动调用destroy再unset:
$a = new Strategy(function () {}); // Strategy 1 $b = new Strategy(function () {}); // Strategy 2 $b->destroy(); // 手动解除引用并递减计数器 unset($b); $c = new Strategy(function () {}); // Strategy 2
验证效果
使用方法1的实现后,你的示例代码会按预期运行:
$a = new Strategy(function () {}); // 名称为Strategy 1 $b = new Strategy(function () {}); // 名称为Strategy 2 unset($b); // 对象被回收,__destruct执行,计数器递减 $c = new Strategy(function () {}); // 名称为Strategy 2
内容的提问来源于stack exchange,提问作者LordF
相关产品推荐
相关产品推荐

