Here is a sample solution which would be "reasonably usable" when implemented in well defined relationship/reference patterns, usually I drop something like this in my composites. To actually use the current code as I've done in this example in a real life would be going through too much trouble but it's just to illustrate the how-to-point.
An object can't just magically disappear - it will only be deleted (garbage collected) once there is nothing pointing at it. So "all" an object has to do is to keep track of everything that refers to it. It is fairly low friction when you have all the reference management built in - objects are created and passed on only by fixed methods.
Lets begin with a simple interface so we would be able to tell if it is safe to pass our reference to an object or not.
interface removableChildInterface
{
public function removeChild($obj);
}
A class which can safely hold a reference to our object.
class MyParent implements removableChildInterface
{
public $children = array();
public function removeChild($child)
{
$key = array_search($child, $this->children);
unset($this->children[$key]);
}
}
And finally a class with the ability to self-destruct aka trigger the process of being removed by all of its parents.
class Suicidal
{
private $parents = array(); // Store all the reference holders
private $id; // For example only
private $memory = ''; // For example only
public static $counter = 0; // For example only
public function __construct(&$parent)
{
// Store a parent on creation
$this->getReference($parent);
// For the example lets assing an id
$this->id = 'id_' . ++self::$counter;
// and generate some weight for the object.
for ($i = 0; $i < 100000; $i++) {
$this->memory .= md5(mt_rand() . $i . self::$counter);
}
}
// A method to use for passing the object around after its creation.
public function getReference(&$parent)
{
if (!in_array($parent, $this->parents)) {
$this->parents[] = &$parent;
}
return $this;
}
// Calling this method will start the removal of references to this object.
// And yes - I am not actually going to call this method from within this
// object in the example but the end result is the same.
public function selfDestruct()
{
foreach ($this->parents as &$parent) {
if (is_array($parent)) {
$key = array_search($this, $parent);
unset($parent[$key]);
echo 'removing ' . $this->id . ' from an array<br>';
} elseif ($parent instanceof removableChildInterface) {
$parent->removeChild($this);
echo 'removing ' . $this->id . ' from an object<br>';
}
// else throw your favourite exception
}
}
// A final shout out right before being garbage collected.
public function __destruct()
{
echo 'destroying ' . $this->id . '<br>';
}
}
And for the example of usage, holding the reference in an array
, in an object
implementing our interface
and the $GLOBALS array
.
// Define collectors
$array = array();
$parent = new MyParent();
// Store objects directly in array
$array['c1'] = new Suicidal($array);
$array['c2'] = new Suicidal($array);
// Make a global reference and store in object
$global_refrence = $array['c1']->getReference($GLOBALS);
$parent->children[] = $array['c1']->getReference($parent);
// Display some numbers and blow up an object.
echo 'memory usage with 2 items ' . memory_get_usage() . ' bytes<br>';
$array['c1']->selfDestruct();
echo 'memory usage with 1 item ' . memory_get_usage() . ' bytes<br>';
// Second object is GC-d the natural way after this line
echo '---- before eof ----' . '<br>';
Output:
memory usage with 2 items 6620672 bytes
removing id_1 from an array
removing id_1 from an array
removing id_1 from an object
destroying id_1
memory usage with 1 item 3419832 bytes
---- before eof ----
destroying id_2