经过多次追踪后,我终于弄清楚我的代码中出了什么问题,所以这个问题不是" 如何解决?" ,而是" 为什么会发生这种情况?"。
请考虑以下代码:
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);
}
}
看起来很简单 - 创建时会打开内存流并设置属性$handle
和$id
。在破坏它时,将使用fclose
来关闭此流。
用法:
$foo = Foo::create(1); // works
var_dump( $foo->bar()->handle ); // works
var_dump( Foo::create(2)->bar()->handle ); // doesn't work
这里的问题似乎是我希望这两个来电都返回完全相同,但出于某种原因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;
据我所知,这就是发生的事情:
var_dump( Foo::create(2)->bar()->handle );
// run GC before continuing.. ^^ .. but I'm not done with it :(
但为什么?为什么PHP认为我已经完成了变量/类实例,因此感觉需要破坏它?
演示:
eval.in demo
3v4l demo (only HHVM can figure it out - all other PHP versions can't)
答案 0 :(得分:8)
这一切归结为refcounts以及PHP如何处理resources differently。
销毁类实例时,将关闭所有非数据库链接资源(请参阅上面的资源链接)。其他地方引用的所有非资源仍然有效。
在第一个示例中,您指定了$temp = Foo::create(1)
,它将引用计数增加到Foo
的实例,从而防止它被销毁,从而使资源保持打开状态。
在你的第二个例子var_dump( Foo::create(2)->bar()->handle );
中,这是事情的发展方式:
Foo::create(2)
,创建Foo
。bar()
,返回$this
会将引用计数增加一。bar()
的范围,下一个操作不是方法调用或分配,refcount会减少一个。Unknown
。作为补充证据,这很好用:
$temp = Foo::create(3)->bar();
// $temp keep's Foo::create(3)'s refcount above zero
var_dump( $temp->handle );
就像这样:
$temp = Foo::create(4)->bar()->bar()->bar();
// Same as previous example
var_dump( $temp->handle );
而且:
// 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 );
此不有效:
$temp = Foo::create(6)->handle;
// Nothing has a reference to Foo, it gets destroyed, all resources closed.
var_dump($temp);
这两个都没有:
$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);
销毁Foo
时,将关闭所有打开的资源(数据库链接除外)。引用Foo
中的其他属性仍然有效。
答案 1 :(得分:2)
似乎所有关于变量范围的都是。
简而言之,如果您将
Foo::create()
分配给全局变量,则可以 访问全局范围内的handle
,析构函数不会成功 直到脚本结束才被调用。然而,如果你实际上没有将它分配给全局变量 本地范围内的方法调用将触发析构函数;手柄 已于
Foo::create(1)->bar()
关闭,因此->method
现已关闭 你试图访问它。
进一步的调查显示,前提是有缺陷的 - 这里肯定会发生一些奇怪的事情! 仅似乎会影响资源。
案例1
$foo = Foo::create(1);
var_dump( $foo->bar()->handle );
结果:
resource(3) of type (stream)
在这种情况下,我们已将全局变量$foo
指定为使用Foo
创建的Foo::create(1)
的新实例。我们现在使用bar()
访问该全局变量,然后返回公共handle
。
案例2
$bar = Foo::create(2)->bar();
var_dump( $bar->handle );
结果:
resource(4) of type (stream)
同样,它仍然可以,因为Foo::create(2)
创建了Foo
的新实例,而bar()
只返回了它(它仍然可以在本地范围内访问它) )。已将其分配给全局变量$bar
,并且正在检索handle
。
案例3
var_dump( Foo::create(3)->bar()->handle );
结果:
resource(5) of type (Unknown)
这是因为当Foo::create()
返回Foo
的新实例时,bar()
使用了该实例...但是当bar()
关闭时{39}}不再使用该实例的任何本地,并且调用__destruct()
方法来关闭句柄。如果您只是写下来,那就得到了同样的结果:
$h = fopen('php://memory', 'r+');
fclose($h);
var_dump($h);
如果你尝试,你会得到完全相同的结果:
var_dump( Foo::create(3)->handle );
Foo::create(3)
将调用析构函数,因为没有对该实例的本地调用。
修改强>
进一步的修补使水更加混乱......
我已添加此方法:
public function handle() {
return $this->handle;
}
现在,如果我的前提是正确的,那么:
var_dump( Foo::create(3)->handle() );
应该导致:
resource(3) of type (stream)
...但它不会再次获得 Unknown 的资源类型 - 似乎在return $this
之前调用了析构函数访问公共类成员!然而,在它上面调用方法绝对没问题:
public function handle() {
return $this->bar();
}
那将很乐意回馈你的对象:
object(Foo)#1 (2) {
["id":"Foo":private]=>
int(3)
["handle"]=>
resource(3) of type (stream)
}
在调用析构函数之前,似乎无法以这种方式访问资源类成员?!
正如 Alex Howansky 所指出的那样,标量很好:
public function __destruct() {
$this->id = 2000;
fclose($this->handle);
}
public function handle() {
return $this->id;
}
现在:
var_dump( Foo::create(3)->handle() );
结果:
int(3)
...在调用析构函数之前返回了原始$ id。
这对我来说肯定闻起来像个臭虫。