【发布时间】:2015-03-27 13:17:28
【问题描述】:
经过多次追踪,我终于弄清楚我的代码出了什么问题,所以这个问题不是“我该如何解决它”,而是“为什么会这样? ”。
考虑以下代码:
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) 调用我不将实例保存到变量在bar() 方法的return $this 部分和我实际使用属性$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)
【问题讨论】:
-
也许在 bar 调用之后,GC 杀死了对象,因为它没有分配给变量?我的意思是,它可能在 bar 调用之后进行操作,但是由于它没有分配给任何东西,所以它不在乎并且无论如何都会将其杀死。
-
@JamesHunt 但它仍然在类(
$this->handle)中定义,并且它正在返回类实例($instance->handle被定义)? -
句柄可以在类中定义,但是对象本身还是需要赋值给var_dump的变量。 var_dump 使用您的参数调用,您的参数使用静态函数 Foo::create(2) 创建类 Foo 的实例,然后您可以在其中使用它的 bar 函数。 bar 函数完成后,对象本身并没有存储在变量中,它只是作为可能的参数浮动,所以可能 GC 在 var_dump 获取句柄变量之前将其清除?
-
经过一番玩弄之后,我得出的结论是,这有点奇怪...如果在您的创建方法中将
new Foo()分配给一个变量,然后返回该变量它可以工作没关系。create($id) { $foo = new Foo($id); return $foo; }然后没关系 - 即使 GC 将在方法结束时销毁$foo(返回 $foo 时)。这可能与构造函数可能返回 null 的事实有关——尽管我在这里有点抓不住... -
如果你在它周围加上一些括号,它似乎也可以工作:
create($id) { return (new Foo($id)); }- 现在我开始认为这与添加的类成员访问实例化有关在 PHP 5.4 中:docs.php.net/manual/en/migration54.new-features.php