# [RFC] Magic Method that handles unset($object)

- Thread: https://externals.io/message/26883
- Original archive: https://news-web.php.net/php.internals/26883
- Started by Sebastian Bergmann on 2006-12-10T09:57:22+00:00
- Messages: 7
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #26883 — Sebastian Bergmann — 2006-12-10T09:57:22+00:00

URL: https://externals.io/message/26883#26883

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

---

## #26884 — Etienne Kneuss — 2006-12-10T14:55:14+00:00

URL: https://externals.io/message/26883#26884 · In reply to: #26883

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

---

## #26885 — Sebastian Bergmann — 2006-12-10T15:07:21+00:00

URL: https://externals.io/message/26883#26885 · In reply to: #26884

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

---

## #26886 — Etienne Kneuss — 2006-12-10T15:16:36+00:00

URL: https://externals.io/message/26883#26886 · In reply to: #26885

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:
>   
> > […]
>
>  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

---

## #26887 — Sara Golemon — 2006-12-10T16:17:16+00:00

URL: https://externals.io/message/26883#26887 · In reply to: #26883

>  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

---

## #26888 — Sebastian Bergmann — 2006-12-10T16:26:09+00:00

URL: https://externals.io/message/26883#26888 · In reply to: #26887

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

---

## #26891 — Ants Aasma — 2006-12-10T23:02:53+00:00

URL: https://externals.io/message/26883#26891 · In reply to: #26888

Sebastian Bergmann wrote:
> Sara Golemon wrote:
> > […]
> 
>  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
