After much tracking down I finally figured out what's going wrong in my code, so this question isn't "how do I fix it", but rather "why does this happen?".
Consider the following code:
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);
}
}
Seems simple enough - when created it will open up a memory stream and set the property $handle and $id. When destructing it will use fclose to close this stream.
Usage:
$foo = Foo::create(1); // works
var_dump( $foo->bar()->handle ); // works
var_dump( Foo::create(2)->bar()->handle ); // doesn't work
What seems to be the issue here is that I'm expecting both calls to return exactly the same but for some reason the Foo::create(2) call where I don't save the instance to a variable calls the garbage collector somewhere between the return $this part of the bar() method and me actually using the property $handle.
In case you're wondering, this is the output:
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;
From what I can see this is what happens:
var_dump( Foo::create(2)->bar()->handle );
// run GC before continuing.. ^^ .. but I'm not done with it :(
But why? Why does PHP think I'm done with the variable/class instance and hence feels the need to destruct it?
Demos:
eval.in demo
3v4l demo (only HHVM can figure it out - all other PHP versions can't)
$this->handle) and it's returning the class instance (where$instance->handleis defined)?new Foo()to a variable and then return that variable it works fine too.create($id) { $foo = new Foo($id); return $foo; }then it's OK - even though the GC will destroy$fooat the close of the method (when $foo is returned). This may have something to do with the fact that constructors return null maybe - though I'm clutching at straws a bit here...create($id) { return (new Foo($id)); }- now I'm beginning to think this is to do with class member access on instantiation added in PHP 5.4 : docs.php.net/manual/en/migration54.new-features.php