Consider the following code:
<?php
class ParentC {
public $children = array();
public function __construct() {
print "ParentC::__construct()\n";
}
public function __destruct() {
print "ParentC::__destruct()\n";
}
public function addChild( $child ) {
$child->parentO = $this;
$this->children[] = $child;
}
}
class Child {
public $parentO = null;
public function __construct() {
print "Child::__construct()\n";
}
public function __destruct() {
print "Child::__destruct()\n";
}
}
function setup() {
$parent = new ParentC;
$child = new Child;
$parent->addChild( $child );
return $parent;
}
while( true ) {
$parent = setup();
unset( $parent );
}
?>
This code prints
ParentC::__construct()
Child::__construct()
ParentC::__construct()
Child::__construct()
.
.
.
instead of
ParentC::__construct()
Child::__construct()
Child::__destruct()
ParentC::__destruct()
ParentC::__construct()
Child::__construct()
Child::__destruct()
ParentC::__destruct()
.
.
.
because unset($parent) decrements the refcount from 2 to 1 as the
$child object that is aggregated by the $parent object still holds a
reference to its aggregating object.
The infinite while() loop is used to hide the fact that the objects are
destructed at interpreter shutdown. While this works, it is not desirable
in the context from which this small reproducible script is extracted.
A possible solution to this problem would be the introduction of yet
another "magic" method. This method would be automatically called when
unset() is called on an object that implements it. Unfortunately, the
name __unset() is already taken.
Assuming we had such a method, the following version of ParentC would
behave as desired:
<?php
class ParentC {
public $children = array();
public function __construct() {
print "ParentC::__construct()\n";
}
public function __destruct() {
print "ParentC::__destruct()\n";
}
public function __new_magic_method() {
$this->children = array();
}
public function addChild( $child ) {
$child->parentO = $this;
$this->children[] = $child;
}
}
?>
Is there another solution to this problem (apart from explicitly
unsetting the $children array on the $parent object)?And even if there
is I think that this method would be usefull.
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Hi,
Sebastian Bergmann wrote:
Is there another solution to this problem (apart from explicitly
unsetting the $children array on the $parent object)?And even if there
is I think that this method would be usefull.
You could use references, you would have to move some code around, though.
I'm not sure that's exactly what you want, but give it a shot:
class ParentC {
public $children = array();
public function __construct() {
print "ParentC::__construct()\n";
}
public function __destruct() {
print "ParentC::__destruct()\n";
}
}
class Child {
public $parentO = null;
public function __construct() {
print "Child::__construct()\n";
}
public function __destruct() {
print "Child::__destruct()\n";
}
}
function &setup() {
$parent = new ParentC;
$child = new Child;
$parent->children[] = &$child;
$child->parentO = &$parent;
return $parent;
}
$parent = &setup();
$parent = null;
echo "--end--\n";
Result:
ParentC::__construct()
Child::__construct()
ParentC::__destruct()
Child::__destruct()
--end--
--
Etienne Kneuss
http://www.colder.ch
colder@php.net
Men never do evil so completely and cheerfully as
when they do it from a religious conviction.
-- Pascal
Etienne Kneuss wrote:
ParentC::__construct()
Child::__construct()
ParentC::__destruct()
Child::__destruct()
You only get this result (ie. the __destruct() calls) because you reach
the end of the script, not because unset() is called.
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Hi,
nah, take a look at my echo "--end--\n"; which gets displayed after the
__destruct calls. The $parent = null will trigger the destructors.
Sebastian Bergmann wrote:
Etienne Kneuss wrote:
ParentC::__construct()
Child::__construct()
ParentC::__destruct()
Child::__destruct()You only get this result (ie. the __destruct() calls) because you reach
the end of the script, not because unset() is called.
--
Etienne Kneuss
http://www.colder.ch
colder@php.net
Men never do evil so completely and cheerfully as
when they do it from a religious conviction.
-- Pascal
A possible solution to this problem would be the introduction of yet
another "magic" method. This method would be automatically called when
unset() is called on an object that implements it. Unfortunately, the
name __unset() is already taken.Is there another solution to this problem (apart from explicitly
unsetting the $children array on the $parent object)?And even if there
is I think that this method would be usefull.
To do this >right< you'd need notification, not just that unset() was
called on the object, but what the current state of both of its
relevant reference counts are.
class foo {
public function __delref($objectstore_refcount, $zval_refcount)
{
if ($objectstore_refcount == 1) {
/* Only one zval is pointing to this object,
* e.g. the one used/shared by the child's backreference */
if ($zval_refcount == 1) {
/* Only one variable is pointing to this zval,
* the child's backreference property */
$this->children = array();
}
}
}
}
Of course, that assumes you only have one child. Once you introduce
multiple children, they'll *probably share one object store reference
through a common zval with multiple references, so you could do:
class foo {
public function __delref($objectstore_refcount, $zval_refcount)
{
if ($objectstore_refcount == 1) {
/* Only one zval is pointing to this object,
* e.g. the one used/shared by the child's backreference */
if ($zval_refcount == count($this->children)) {
/* Only one children's properties are pointing to this zval */
$this->children = array();
}
}
}
}
But that doesn't cover the (quite common) case where there are multiple
object store references spread out among the children (and possibly
other variables not contained in the object itself). Unfortunately you
can't aggregate the total number of variable->zval->object references
ince at this point in runtime, only the zval being dereferenced is known
for certain.
By the way, this is part of the reason why PHP doesn't have delete().
-Sara
Sara Golemon wrote:
To do this >right< you'd need notification, not just that unset() was
called on the object, but what the current state of both of its
relevant reference counts are.
I fail to see why. If we introduce a method that is called for
unset() then there should be no problem with resetting the
$children attribute to array() in my example:
unset($parent) -> "magic method" -> $children = array() -> no
references to $child objects
Nothing would happen in the case that you describe where other
references to the $child objects exist, of course.
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Sebastian Bergmann wrote:
Sara Golemon wrote:
To do this >right< you'd need notification, not just that unset() was
called on the object, but what the current state of both of its
relevant reference counts are.I fail to see why. If we introduce a method that is called for
unset() then there should be no problem with resetting the
$children attribute to array() in my example:
In what way is this different from defining method close() and instead
of unset($object) call $object->close()?
What happens if you have more than one non-child reference to parent?
--
Ants Aasma