使用静态方法创建实例时的PHP垃圾回收

h2o*_*ooo 4 php garbage-collection

在经过多次追踪后,我终于弄明白我的代码出了什么问题,所以这个问题不是" 我该如何修复它 ",而是" 为什么会发生这种情况? ".

请考虑以下代码:

class Foo {
    private $id;
    public $handle;

    public function __construct($id) {
        $this->id = $id;
        $this->handle = fopen('php://memory', 'r+');

        echo $this->id . ' - construct' . PHP_EOL;
    }

    public function __destruct() {
        echo $this->id . ' - destruct' . PHP_EOL;

        fclose($this->handle);
    }

    public function bar() {
        echo $this->id . ' - bar - ' . get_resource_type($this->handle) . PHP_EOL;

        return $this;
    }

    public static function create($id) {
        return new Foo($id);
    }
}
Run Code Online (Sandbox Code Playgroud)

看起来很简单 - 创建它时会打开一个内存流并设置属性$handle和$id.在破坏它时将用于fclose关闭此流.

用法:

$foo = Foo::create(1); // works

var_dump( $foo->bar()->handle ); // works

var_dump( Foo::create(2)->bar()->handle ); // doesn't work
Run Code Online (Sandbox Code Playgroud)

这里的问题似乎是我期望两个调用返回完全相同但由于某种原因Foo::create(2),我不将实例保存到变量的调用return $this在bar()方法的一部分和我之间的某处调用垃圾收集器实际上使用该属性$handle.

如果你想知道,这是输出:

1 - construct                 // echo $this->id . ' - construct' . PHP_EOL;
1 - bar - stream              // echo $this->id . ' - bar - ' ...
resource(5) of type (stream)  // var_dump
2 - construct                 // echo $this->id . ' - construct' . PHP_EOL;
2 - bar - stream              // echo $this->id . ' - bar - ' ...
2 - destruct                  // echo $this->id . ' - destruct' . PHP_EOL;
resource(6) of type (Unknown) // var_dump
1 - destruct                  // echo $this->id . ' - destruct' . PHP_EOL;
Run Code Online (Sandbox Code Playgroud)

从我可以看到这是发生的事情:

var_dump( Foo::create(2)->bar()->handle );
// run GC before continuing..  ^^ .. but I'm not done with it :(
Run Code Online (Sandbox Code Playgroud)

但为什么呢?为什么PHP认为我已经完成了变量/类实例,因此感觉需要破坏它?

演示:

eval.in演示
3v4l演示(只有HHVM可以搞清楚 - 所有其他PHP版本都不能)

Mr.*_*ama 8

这一切都归结为refcounts以及PHP如何以不同方式处理资源.

销毁类实例时,将关闭所有非数据库链接资源(请参阅上面的资源链接).其他地方引用的所有非资源仍然有效.

在您的第一个示例中,您指定$temp = Foo::create(1)将refcount增加到实例Foo,从而防止它被销毁,从而保持资源打开.

在你的第二个例子中var_dump( Foo::create(2)->bar()->handle );,这是事情的发展方式:

  1. Foo::create(2)被调用,创建一个实例Foo.
  2. 您bar()在新实例上调用方法,返回$this将refcount增加1.
  3. 你离开bar()的范围,下一个动作不是方法调用或赋值,refcount减1.
  4. 实例的引用计数为零,因此它被销毁.所有非数据库链接资源都已关闭.
  5. 您尝试访问已关闭的资源,返回Unknown.

作为额外的证明,这很好用:

$temp = Foo::create(3)->bar();
// $temp keep's Foo::create(3)'s refcount above zero
var_dump( $temp->handle );
Run Code Online (Sandbox Code Playgroud)

就像这样:

$temp = Foo::create(4)->bar()->bar()->bar();
// Same as previous example
var_dump( $temp->handle );
Run Code Online (Sandbox Code Playgroud)

还有这个:

// Assuming you made "id" public.
// Foo is destroyed, but "id" isn't a resource.  It will be garbage collected later.
var_dump( Foo::create(5)->id );
Run Code Online (Sandbox Code Playgroud)

这不起作用:

$temp = Foo::create(6)->handle;
// Nothing has a reference to Foo, it gets destroyed, all resources closed.
var_dump($temp);
Run Code Online (Sandbox Code Playgroud)

这也不是:

$temp = Foo::create(7);
$handle = $temp->handle;
unset($temp);
// $handle is now a reference to a closed resource because Foo was destroyed
var_dump($handle);
Run Code Online (Sandbox Code Playgroud)

当Foo被破坏,所有打开的资源(除了数据库链接)关闭.引用其他属性Foo仍然有效.

演示:https: //eval.in/271514