#native_company# #native_desc#
#native_cta#

Priority based object Mixer class

By Manuj
on December 20, 2007

Version: 1.0

Type: Class

Category: Algorithms

License: GNU General Public License

Description: I was using symfony’s sfMixer class. Thought why not create one’s own

This class mixes different object and u can call methods on the component objects (see demo class). Priority can be also assigned to the objects. All the objects in the Mixer are singletons.

/////////////////////

/*
 * Class MyMixer - Mixes objects and provides priority call functionality.
 * 				   The contents of MyMixer are singletons. 
 *
 * TODO: implement __call method (done)
 *     : destruction of objects in object store and RMO objects in classMethods (done)
 *     : Constructor data incorporated into objects (done)
 *     : Resetting the Mixer (done)
 * 
 * 
 * 
 */

class MyMixer
{
	private $objectStore=array();			//contains Component class => their objects
	private $arrClasses=array();			//an array of classnames to be mixed with their priority value
	private $classConstructorArg=array();	//an array of classname to the constructor arguments (string)
	private $classnameParentPriority;		//an array of
											//class id
											//classname
											//parent class id 
											//priority
	private $classMethods;					//an array of
											//components class id,
											//an array of Method name to corresponding RMO
										
	private $flag_everythingready=false;


	public function resetMixer()
	{
		$this->objectStore=array();
		$this->arrClasses=array();
		$this->classnameParentPriority=array();
		$this->classMethods=array();
		$this->flag_everythingready=false;
	}
	

	//objects getter methods
	private function getObjectStoreEntryByClassId($classid)
	{
			if(isset($this->objectStore[$classid]))
			{
				return $this->objectStore[$classid];
			}
			else
				throw new Exception('Method MyMixer::getObjectStoreEntryByClassId() provided a non existing key',NOK+6);
	}
	
	private function getObjectStoreEntryByClassName($classname)
	{
		$flag=false;
		foreach($this->classnameParentPriority as $key=>$val)
		{
			if($val[0] == $classname)
			{
				$flag=true;
				return $this->getObjectStoreEntryByClassId($key);
			}
		}
		if($flag==false)throw new Exception('Method MyMixer::getObjectStoreEntryByClassName() returned nothing.',NOK+7);
	}
		
	private function createObjectStore()
	{
		foreach($this->classnameParentPriority as $key=>$val)
		{
				$classname=$this->classnameParentPriority[$key][0];
				//make the constructor argument string
				if(isset($this->classConstructorArg[$classname]))
				{
					eval("$this->objectStore[$classid]=new $classname(".$this->classConstructorArg[$classname].");");				
				}
				else
				{
					eval("$this->objectStore[$classid]=new $classname();");
				} 
		}
	}


	private function populateclassMethods()
	{
		foreach($this->classnameParentPriority as $key=>$val)
		{
			$methods=get_class_methods($val[0]);
			foreach($methods as $method)
			{
				$this->classMethods[$key][$method]=new ReflectionMethod($val[0], $method);
			}
		}
	}
	
	private function getParents($classname,$level=0)	//limits to $level or oldest parent whichever
	{													//comes early
		if(!class_exists($classname))
		{
			throw new Exception('Wrong Argument to getParents() method. Argument class does not exists.',NOK+5);
		}
		elseif($level<0)
		{
			throw new Exception('Wrong Argument to getParents() method. Level value is invalid (<0).',NOK+14);
		}
		$retarr=array();
		$iter=$classname;
		$i=0;
		do
		{
			$retarr[]=$iter;
			$i+=1;			
		}
		while(($iter=get_parent_class($iter))  and  (($level==0)?true:(($i==$level)?false:true))  );
		return $retarr;
	}

	private function populateclassnameParentPriorityArray($includeParents=false)
	{	
		//populate
		$id=0;	//initial id
		foreach ($this->arrClasses as $ccname=>$ccpriority)
		{
			$parentage=$this->getParents($ccname,($includeParents==true)?0:1);
			foreach($parentage as $name)
			{
				$this->classnameParentPriority[$id++]=array($name,$id,$ccpriority);
			}
			$this->classnameParentPriority[$id-1][1]=-1;	//set parent to -1 of the last class entered assumingly the super parent
		}
		
		//remove multiple entries of classnames and edit the parent field to correct values
		$cnames=array();
		$lastkey=0;
		$unsetlist=array();
		foreach($this->classnameParentPriority as $key=>$val)
		{
			if(!isset($cnames[$val[0]]))
			{
				$cnames[$val[0]]=$key;
				$lastkey=$key;
			}
			else
			{
				$this->classnameParentPriority[$lastkey][1]=$cnames[$this->classnameParentPriority[$key][0]];
				$unsetlist[]=$key;
			}
		}

		foreach($unsetlist as $unsetit)
		{
			unset($this->classnameParentPriority[$unsetit]);
		}
		
		//populate the methods array
		$this->populateclassMethods();
		
		//create object store
		$this->createObjectStore();
		
		$this->flag_everythingready=true;	//everything ready for method invocation on the mixer chk in __call
	}
	

	public function setComponentClasses($argarrClasses,$constructorarg,$setDefaultPriority=false)
	{
		$this->classConstructorArg=$constructorarg;

		$defpriority=1;
		$argcount=count($argarrClasses);

		if($argcount == 0)	//no entry in arrClasses - its not ok whats the point of mixing
		{
			throw new Exception('Argument classes array to MyMixer::setComponentClasses() is empty.',NOK+1);
		}

		foreach($argarrClasses as $name=>$priority)
		{
			if($priority>=1 and $priority<=$argcount)	// 1 <= priority <= count of component classes
			{
				if(!in_array($priority,array_values($this->arrClasses)))	//no repeated priorities
				{
					if(class_exists($name))
					{
						$this->arrClasses[$name]=($setDefaultPriority==false)?$priority:$defpriority++; 	//put $name=>$priority in $arrClasses
					}
					else
						throw new Exception('Class '.$name.' does not exist in argument to MyMixer::setComponentClasses() method',NOK+2);
				}
				else
					throw new Exception('In argument to MyMixer::setComponentClasses() method you cannot assign same priority ('.$priority.') to two or more than two classes',NOK+3);
			}
			else
				throw new Exception('Invalid priority value ('.$name.' => '.$priority.') passed to MyMixer::setComponentClasses() method',NOK+4);
		}
		//now $this->arrClasses contains the component class names and their priority

		//sort the arrClasses array based on priority only if setDefaultPriority is false
		//priority values will be unique
		if($setDefaultPriority == false)
		{
			$this->arrClasses=array_flip($this->arrClasses);
			ksort($this->arrClasses);
			$this->arrClasses=array_flip($this->arrClasses);
		}

		//call private member method
		$this->populateclassnameParentPriorityArray();
		
	}

	public function __call($methodname,$arguments)
	{
		if($this->flag_everythingready==false)
		{
			throw new Exception('Something is missing. Not ready to call methods on the mixer object.',NOK+8);
		}
		$narguments=count($arguments);
		$temparr=explode('____',$methodname);
		
		if(strlen($methodname)==strlen($temparr[0])) //if methodname does not contain ____ (4 underscores)
		{											 //priority based invocation
			$namematches=false;$parammatches=false;$called=false;
			foreach($this->classMethods as $key=>$val)
			{
				foreach($val as $mname=>$rmo)
				{
					if($mname == $methodname)	//name matches
					{
						$namematches=true;
						if($rmo->getNumberOfRequiredParameters() <= $narguments and $narguments <= $rmo->getNumberOfParameters())
						{
							//number of params matches this seems to be the right method to call now
							$parammatches=true;

							//gets its object
							$object=$this->getObjectStoreEntryByClassId($key);
							$result=null;
							eval("$result=$object->$methodname(".implode(',',$arguments).");");
							$called=true;
							break;
						}
					}
				}
				if($called==true)
				{
					return $result;
				}
			}	
			if($namematches==false)
			{
				throw new Exception('There is no method named '.$methodname.'() in the mixer. Call failed.',NOK+9);
			}
			elseif($parammatches==false)
			{
				throw new Exception('Number of parameters of method '.$methodname.'() in the mixer do not match with the actual call. Call failed.',NOK+10);
			}
		}
		else										 //classname specific invocation
		{
			$classname=$temparr[0];$methodname=$temparr[1];$classexists=false;$methodexists=false;$parammatches=false;
			foreach($this->classnameParentPriority as $key=>$val)
			{
				if($val[0]==$classname)
				{
					$classexists=true;
					$classmethods=$this->classMethods[$key];
					if(isset($classmethods[$methodname]))
					{
						$methodexists=true;
						$rmo=$classmethods[$methodname];
						if($rmo->getNumberOfRequiredParameters() <= $narguments and $narguments <= $rmo->getNumberOfParameters())
						{
							$parammatches=true;
							$object=$this->getObjectStoreEntryByClassId($key);
							$result=null;
							eval("$result=$object->$methodname(".implode(',',$arguments).");");
							$called=true;
							break;
						}					
					}
				}
			}
			if($classexists==false)
			{
				throw new Exception('The class '.$classname.' does not exists in the mixer',NOK+11);
			}
			elseif($methodexists==false)
			{
				throw new Exception('The method '.$classname.'::'.$methodname.'() does not exists in the mixer',NOK+12);
			}
			elseif($parammatches==false)
			{
				throw new Exception('Number of parameters of method '.$classname.'::'.$methodname.'() in the mixer do not match with the actual call. Call failed.',NOK+13);				
			}
			else
			{
				return $result;
			}
		}
	}

	//destructor method
	public function __destruct()
	{
		$this->resetMixer();
	}


}









//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////



/**
 * TestingModule actions.
 */



class Story
{
  protected $title = '';
  protected $topic = '';
  protected $characters = array();
 
  public function __construct($title = '', $topic = '', $characters = array())
  {
    $this->title = $title;
    $this->topic = $topic;
    $this->characters = $characters;
  }
 
  public function getSummary()
  {
    return $this->title.', a story about '.$this->topic.' having '.implode(',',$this->characters);
  }
}

 

class Novel extends Story
{
	private $isbn=10;
	
	public function __construct($title,$topic,$chars)
	{
		parent::__construct($title,$topic,$chars);
	}
	
	public function setISBN($isbn=0)
	{
		$this->isbn=$isbn;
	}
	
	public function getISBN()
	{
		return $this->isbn;
	}

	public function getSummary()
	{
		echo parent::getSummary();
	}
}


class Book
{
  protected $isbn = 1000;
 
  public function setISBN($isbn = 0)
  {
    $this->isbn = $isbn;
  }
 
  public function getISBN()
  {
    return $this->isbn;
  }
}


class TestingModule
{
  /**
   * Executes index action
   *
   */
  public function executeTestAction()
  {
	
		try {

			$mixer=new MyMixer();
			
			$mixer->setComponentClasses(array("Novel"=>1),array(
																			"Novel" => ""alladin","alladin's escape",array("alladin","genie","jasmine")"    
			
																			));

			echo $mixer->getSummary();

		}
		catch(Exception $e) {
			echo "<br>Caught exception ".$e->getMessage()." <br>";
		}
	
  }
}