Jump to content

Circular referencing objects


mralston

Recommended Posts

I'm writing a daemon which has object arrays representing servers and clusters. The server and cluster objects are derived from records in a couple of database tables, which have foreign keys to their counterparts.

 

A server can belong to one or more clusters and each cluster can have zero or more servers. It's a many-to-many relationship.

 

I'm looking to have an array of clusters and and array of servers so that I can iterate through either, depending on what I'm looking for. If I pull out a cluster object, it should contain a load of server objects (each of which contains a cluster object). So there will be a lot of circular references.

 

In the past I've worked around this problem by only storing the foreign key in an object when it needs to reference another object. So when I ask a server for its cluster, it creates a new cluster object on the fly based on the clusterId foreign key it has stored. If I ask a cluster for its servers it creates a bunch of server objects on the fly and returns them in an array.

 

My problem though is that this is all going into a daemon which is going to be running all the time and handling a lot of traffic. I don't want the overhead of constantly creating and deleting objects all of the time (and potential memory leaks and eventual crash if I forget).

 

So what I'm thinking is that I have an array of clusters, and array of servers. These will contain exactly one object for each cluster and each server and these will be the only such objects that will ever be created. The server and cluster objects will contain pointers to the cluster and server objects they wish to reference. Like in C, with pointers to bits of memory, so that if server A and server B both have references to cluster 1, they will both have references to the exact same object instance of cluster 1. Likewise if cluster 1 and cluster 2 both reference server A.

 

So is creating two arrays (external to the objects themselves) the best way of doing this? What's the be best way of creating those arrays in an OOP compliant way? It would be nice to just load up an array of clusters then each of them would load up their own server arrays, storing the arrays themselves internally but the servers they reference in a global / static manner.

 

So I know what I'm trying to achieve, just not sure of the best way to kick it all off. Any ideas?

 

Thanks,

 

- Matt

Link to comment
Share on other sites

Yes, I know. Probably a poor use of terminology on my part, but they basically amount to the same thing. A variable does not contain an actual object, only a reference to where it exists in memory.

 

My point though, is that if cluster A contains server 1 and cluster 2 contains server 1, I only want one instance of server 1 instantiated with cluster A and cluster B containing references to the same instance.

 

In my current way of doing things (which is what I'm aiming to change) I'd create objects for cluster A and cluster B and each of these would automatically create their own server 1 instance causing a total of two instances of server 1. Not a problem in such small quantities, but multiply this up and I will end up with a lot of duplicate object instances.

 

 

Link to comment
Share on other sites

I think you are looking for an Identity Map:

 

class ServerMap {
    private $map = array();
    
    public function get($serverID) {
        if(!array_key_exists($serverID, $this->map)) {
            $this->map[$serverID] = $this->load($serverID); // returns Server or null
        }
        return $this->map[$serverID];
    }
}

class ClusterFactory {
    private $map;
    
    public function __construct(ServerMap $map) { $this->map = $map; }
    
    public function getCluster($clusterID) {
        $cluster = new Cluster();
        foreach($this->db->fetchAll('SELECT server_id FROM servers WHERE cluster_id = ' . $clusterID) as $server) {
            $cluster->addServer($this->map->get($server->server_id));
        }
        return $cluster;
    }
}

 

$map = new ServerMap();
$factory = new ClusterFactory($map);
$clusterA = $factory->getCluster('A');
$clusterB = $factory->getCluster('B');

Link to comment
Share on other sites

I've had a little play around with this concept and it seems to be pretty much what I'm after.

 

I've actually created a static class called ObjectFactory which is responsible for creating all new objects, and it has an array in a static property which it uses to keep track of what it's created in the past.

 

I still had a bit of a problem because I had object A creating object B in its constructor which in turn created object A in its own constructor. The object factory only added newly created objects to its array after their contstructors had completed so I managed to create a nice infinite loop. I've worked around that temporarily by giving each class a separate method called createChildObjects which is called by the factory after the object has been instantiated and added to the static array (map). This I don't particularly like this, so I'm going to have a look at getting objects make a call back to the object factory to register themselves in the map during their constructor phase but before they try to create child objects. I think this will solve the problem and look a little cleaner.

Link to comment
Share on other sites

Yes, that idea seems to work nicely. Basically your idea, I've just rewritten it myself to solidify my own understanding.

 

Here's what I ended up with. I decided to use a really simple analogy of cars pulling boats for the purposes of this prototype. I'll get back to my servers and clusters later!

 

<?php

$vehicles=array();

$vehicles[]=ObjectFactory::make("Car", 0);

echo("\$vehicles: ".print_r($vehicles, true)."\n\n");
echo("\$objects: ".print_r(ObjectFactory::getRegisteredObjects(), true)."\n");


class ObjectFactory
{
private static $objects=array();

private function __construct() {}

public function make($className, $objectId)
{
	// Search for a pre-existing object matching creation arguments
	foreach(self::$objects as $object)
	{
		$getId="get".strtoupper(substr(get_class($object), 0, 1)).substr(get_class($object), 1)."Id";
		if(is_a($object, $className) && $object->$getId()==$objectId)
		{
			// Found one. Return it
			return($object);
		}
	}

	// No pre-existing object matches. Create a new one
	$object=new $className($objectId);

	// Register it for later use
	self::register($object);

	// Return newly created object
	return($object);
}

public function register($object)
{
	// Check object isn't already registered
	foreach(self::$objects as $existingObject)
	{
		// If it is, don't register it again
		if($object===$existingObject) return;
	}

	// Not already registered. Do so now
	self::$objects[]=$object;
}

public function getRegisteredObjects()
{
	return(self::$objects);
}
}



class Car
{
private $carId=0;
private $pulls=null;

public function __construct($carId=null)
{
	// Register this instance before creating child objects with circular references
	ObjectFactory::register($this);

	// Record our ID for later use
	if(!is_null($carId)) $this->carId=$carId;

	// Create a boat to pull
	$this->pulls=ObjectFactory::make("Boat", $carId);
}

public function getCarId()
{
	return($this->carId);
}
}

class Boat
{
private $boatId=0;
private $pulledBy=null;

public function __construct($boatId=null)
{
	// Register this instance before creating child objects with circular references
	ObjectFactory::register($this);

	// Record our ID for later use
	if(!is_null($boatId)) $this->boatId=$boatId;


	// Create (or fetch existing) car to be pulled by
	$this->pulledBy=ObjectFactory::make("Car", $boatId);
}

public function getBoatId()
{
	return($this->boatId);
}
}

?>

 

When I run that I get:

 

$vehicles: Array
(
    [0] => Car Object
        (
            [carId:Car:private] => 0
            [pulls:Car:private] => Boat Object
                (
                    [boatId:Boat:private] => 0
                    [pulledBy:Boat:private] => Car Object
*RECURSION*
                )

        )

)


$objects: Array
(
    [0] => Car Object
        (
            [carId:Car:private] => 0
            [pulls:Car:private] => Boat Object
                (
                    [boatId:Boat:private] => 0
                    [pulledBy:Boat:private] => Car Object
*RECURSION*
                )

        )

    [1] => Boat Object
        (
            [boatId:Boat:private] => 0
            [pulledBy:Boat:private] => Car Object
                (
                    [carId:Car:private] => 0
                    [pulls:Car:private] => Boat Object
*RECURSION*
                )

        )

)

 

Lots of recursion, but no duplicate objects (and no memory warnings as the code loops forever!). Just what I was after.

 

Thanks for the pointer, ignace!

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.