Orms and Circular References

In my spare time I have been working on an ORM… not one meant to be used in the real world (well… not yet anyway). I currently have hasOne and hasMany working like so:

<?php
 
/**
 * @property int $id
 * @property string $type
 * @property int $car_id
 */
class wheelBoilerplate extends orm{
	protected function init(){
		$this->addProperty('type', array(
			'type'=>'string',
			'default'=>'monster truck',
		));
		$this->hasOne('car');
	}
}
 
class wheel extends wheelBoilerplate{}
 
/**
 * @property int $id
 * @property int $wheel_count
 * @property array $wheels
 * @property string $type
 */
abstract class carBoilerplate extends orm{
	protected function init(){
		$this->addProperty('wheel_count', array(
			'default'=>4,
			'type'=>'int',
		));
		$this->addProperty('type', array(
			'default'=>'monster truck',
			'type'=>'string',
		));
		$this->hasMany('wheels');
	}
}
 
class car extends carBoilerplate{}
 
$car = new car();
 
echo $car->wheels[0]->car->wheels[2]->car->wheels[3]->car->wheels[0]->type; #=> 'monster truck'

The problem as you can probably guess, is you now have 4 of the same car loaded into memory, and 4 wheels, 2 of which are the same. So, when you do this:

$car->type = 'Ford Pinto';

You are only actually changing one instance of all of the instances of car that you have created in memory. Not so good for keeping things straight, which one do you save? What happens if you save one over top of the other?

$car->type = 'Ford Pinto';
$car->save();
$car->wheels[0]->car->save();

You have just overwritten your changes in the database. I didn’t think that was a good thing to let someone do (even accidentally). That is where this class came from:

<?php
 
class factory {
	/**
	 * Holds all of the items in the factory
	 * @var array
	 */
	protected static $created = array();
	/**
	 * Create/Load an object from the factory
	 * @param string $className
	 * @param int $id
	 * @return orm
	 */
	public static function Create($className, $id=null ) {
		$className = strtolower($className);
		if(!isset(self::$created[$className][$id])) {
			self::$created[$className][$id] = new $className($id);
		}
		return self::$created[$className][$id];
	}
	/**
	 * Load an object from the factory
	 * @param string $className the object's class
	 * @param int $id the id of the object you are loading
	 * @return orm or null
	 */
	public static function Load($className, $id ) {
		$className = strtolower($className);
		return (isset(self::$created[$className][$id]))? self::$created[$className][$id]:null;
	}
	/**
	 * Add an object to the factory, has no effect if the object is already part of the factory
	 * @param string $className
	 * @param orm $class
	 */
	public static function Add(orm &$class) {
		$className = strtolower($class->class_name);
		if(!isset(self::$created[$className][$class->id])) {
			self::$created[$className][$class->id] = &$class;
		}
	}
	/**
	 * Destroy an instance of something in the factory and the object itself
	 * @param orm $class the class being destroyed
	 */
	public static function Destroy(orm &$class){
		$className = strtolower($class->class_name);
		$id = $class->id;
		unset(self::$created[$className][$id]);
		$class = null;
	}
}

I haven’t had the chance to actually merge them together, but this is how it should work:

 
$car = factory::Create('car', 1);
 
$car->wheels[0]; //#=> factory::Create('wheel', 1);
$car->wheels[1]; //#=> factory::Create('wheel', 2);
$car->wheels[2]; //#=> factory::Create('wheel', 3);
$car->wheels[3]; //#=> factory::Create('wheel', 4);
 
$car->wheels[0]->car; //#=> factory::Load('car', 1);
$car->wheels[0]->car->wheels[0]; //#=> factory::Load('wheel', 1);
 
$car->wheels[0]->car->type = 'Ford Pinto';
 
echo $car->type; //#=> 'Ford Pinto'

Now, no matter which of the cars that you load (as long as it has the same ID), it will be the same one instead of just another instance of the same one. Changes are made to all of them, because they are all the same instance of that car.

The factory class and some sample usage code are on github, if’n you want to look at it, or fork it, or whatever.

flattr this!