October 26, 2024
Chicago 12, Melborne City, USA
PHP

PHP, immediate garbage collecting with Weak References causes error


I have a large object structure that of course has many refrences to other objects. I have a lot of one-to-many relations between these objects. So for instance: my class users contains a property Array $users that contains children of class user, whose property users $container references back to the "containing" class.

If two objects reference each other they will never be garbage collected, since there will always be references to both. Therefore, I use weak references to reference to containers. I made a trait that handles this perfectly and has been working for years.

In a constructor I can do this:

class user {
    use weakObjReference;
    function __construct(users $container) {
         $this->weakReference($container, 'container');
         // can use $this->container now everywhere; the trait has stored it internally as a WeakReference 
         // and via __get gives it back as a normal (strong) object reference
    }
}

This works! No objects are kept forever.
However I sometimes have a problem when I make instances on the fly. A very basic example:

class a  {
     public readonly b $b;

     function __construct(public string $name) { 
          $this->b = new b($this);
     }

     function hoohoo(): mixed {
          return $this->b;
     }
}


class b  {

     use weakObjReference;

     function __construct(a $container) { 
          $this->weakReference($container, 'container');  // $this->container "created"
     } 

     function heehee($name): mixed {
          $x = $name . ', '. $this->container->name;
          return $x;

     }
}

$a = new a('world 1');
var_dump($a->hoohoo()->heehee('hello'));  // EXAMPLE 1 
var_dump((new a('world 2'))->hoohoo()->heehee('good afternoon'));  // EXAMPLE 2.

https://3v4l.org/7DpN2

EXAMPLE 1. WORKS. Returns ‘hello, world 1’.

EXAMPLE 2. ERROR. weakObjReferenceStorage::get(): Return value must be of type object, null returned, in other words: the Weak Reference is invalid since the instance has been deleted.

This means the garbage collection is too agressive for my purposes. As soon as heehee() gets executed, the instance of class a is already destructed, so the WeakReference back to a is not working anymore. I thought that garbage collection would be done after a statement, but apparently I am very wrong; it’s done immediately, while the statement still is being executed.

I made an ugly solution by making the instances temporarily strong again. But it’s not sustainable over a whole system with many classes, since it involves knowing exactly when to make a reference temporarily strong and when weak again over multiple classes. To see how messy it gets, I added a level of containers:

class a  {
     public readonly b $b;

     function __construct(public string $name) { 
          $this->b = new b($this);
     }

     function hoohoo(): mixed {
          $this->b->temporaryStrong('container');
          return $this->b;
     }
}


class b  {
     public readonly c $c;

     use weakObjReference;

     function __construct(a $container) { 
          $this->weakReference($container, 'container');  // $this->container "created"
          $this->c = new c($this);
     } 

     function heehee(): mixed {
          return $this->c;
     }
}

class c  {

     use weakObjReference;

     function __construct(b $container) { 
          $this->weakReference($container, 'container');  // $this->container "created"
     } 

     function haahaa($name): mixed {
          $x = $name . ', '. $this->container->container->name;
          $this->container->weakAgain('container');
          return $x;

     }
}

$a = new a('world 1');
var_dump($a->hoohoo()->heehee()->haahaa('hello'));  // EXAMPLE 1 
var_dump((new a('world 2'))->hoohoo()->heehee()->haahaa('good afternoon'));  // EXAMPLE 2. 

https://onlinephp.io/c/61a46

It works, but clearly this is not a practical/structural solution with ->temporaryStrong()s and ->weakAgain()s scattered all over the system.

This seems a niche situation since it only occurs when you create an instance on the fly. However, in my case that is pretty standard (via a factory). And with a few different classes, too.

Any ideas on how to solve this gracefully, so without forcing the use of a variable in userland? I’ve been stuck for three hours now…



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video