Location: PHPKode > projects > Anahita Social Engine > dependencies/plg_system_socialengine/socialengine/domain/mapper/mapper.php
<?php
/**
 * @version		1.0.0
 * @category	Anahita Social Engine™
 * @copyright	Copyright (C) 2008 - 2010 rmdStudio Inc. and Peerglobe Technology Inc. All rights reserved.
 * @license		GNU GPLv2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
 * @link     	http://www.anahitapolis.com
 */


define('PolyMorphic', 'PolyMorphic');

class AnDomainMapper extends KObject
{	
	static protected $_mappers   = array();
	/**
	 * AnDomainContext - identity map
	 * 
	 * */
	protected $_context;
	
	/**
	 * Mappers - map properties to fields 
	 */
	protected $_properties		   = array();
	
	protected $_model		  	 = null;
	
	protected $_db				 = null;
	
	protected $_repos		   	 = null;
	
	protected $_dql;
	
	protected $_raw_query;
	
	protected $_tables		  	  = array('primary'=>null, 'secondary'=>array());
	
	protected $_unique_properties = null;
	
	protected $_instance		  = null;
	
	protected $_discimniatings	  = null;	
	
	protected $_default_values;
	
	protected $_attr_key_path	  = null;
	
	protected $_parents			  = array();
	
	protected $_inheritance_tree;
	/**
	 * 
	 * @return 
	 * @param $options Object[optional]
	 */
	public function __construct($options=array())
	{				
		$this->_model	  = $options['model'];

		$options 		  = $this->_initialize($options);
		
		$this->_context   = $options['context'];
		
		$this->_repos	  = $options['repos'];
		
		$this->_dql		  = $options['query'];
		
		$this->_db		  = $options['adapter'];
						
		$this->_instance  = KFactory::tmp($this->_model, array('mapper' => $this));
		
		$this->_inheritance_tree   = $this->_instance->inheritanceTree();

		//reigster this mapper for the instance class
		self::$_mappers[ get_class($this->_instance) ] = $this;
		
		array_unshift($this->_inheritance_tree, get_class($this->_instance));
		
		call_user_func(array(get_class($this->_instance), 'describe'), $this);
		
		$this->_default_values = null;		

		if ( !count($this->getUniqueKeys()) ) {
			throw new Exception("{$this->_model} model must have at least one unique property");
		}
		
		$relationships = array_keys($this->getRelationships());
		//inherit all the parent relationships
		$parents = $this->_inheritance_tree;			
		
		array_shift($parents);						
		//this is for when a relationship has been added dymanically to the parent of this 
		//mapper to include it in the child
		foreach($parents as $parent) {
			if ( isset(self::$_mappers[$parent]) ) {
				$parent_mapper = self::$_mappers[$parent];
				$relationships = array_diff_key($parent_mapper->getRelationships(), $this->getRelationships());
				$this->_properties = array_merge($relationships, $this->_properties);
			}
		}
		
	}
	
	public function getModel()
	{
		return $this->_model;
	}
	
	/**
	 * returns the properties describe for the model
	 * @return 
	 */
	public function getProperties()
	{
		return $this->_properties;
	}
	
	/**
	 * Return a list of properties who are identified as unique
	 * @return 
	 */
	public function getUniqueKeys()
	{
		if ( is_null($this->_unique_properties) ) {
			$this->_unique_properties = array();
			foreach($this->getAttributes() as $name => $property) {
				if ( $property->isUnique() ) {	
					$this->_unique_properties[$name] = $property;
				}
			}
		}
		return $this->_unique_properties;		
	}	
	
	public function setAdapter($adapter)
	{
		$this->_db = $adapter;
	}
	
	public function getAdapter()
	{
		return $this->_db;	
	}
	
	/**
	 * 
	 * @return 
	 * @param $options Object
	 */
	protected function _initialize($options)
	{
		$default = array(
			'repos'	      => "lib.anahita.domain.repository.default"    	   ,
			'context'     => KFactory::get('lib.anahita.domain.context')	   ,
			'query'	 	  => 'lib.anahita.domain.query',
			'adapter'	  => KFactory::get('lib.koowa.database')
		);
		
		return array_merge($default, $options);
	}	
	
	public function setQuery($query)
	{
		$this->_dql = $query;
	}
	
	public function getQuery()
	{
		$query = KFactory::tmp($this->_dql, array('mapper'=>$this));
		/*
		$inheritance = $this->getInheritanceTree();
		if ( count($inheritance) > 1 ) {
			//get all the subclasses mappers
		}*/
		return $query;
	}
	
		
	public function getInheritanceTree()
	{
		return $this->_inheritance_tree;
	}
		
	/**
	 * 
	 * @return 
	 * @param $table Object
	 */
	public function setTable($table, $alias = null)
	{
		$from = $table;
		if ( $alias ) {
			$from = "$from AS $alias";
		}
		$this->rawQuery()->from($from);
		$table = KFactory::tmp('lib.anahita.domain.mapper.table', array('table'=>$from,'adapter'=>$this->getAdapter()));
		$this->_tables['primary'] = $table;
		return $this;
	}
	
	/**
	 * 
	 * @return 
	 * @param $table Object
	 */
	public function useTable($table)
	{
		$this->rawQuery()->from($table);
		$table = KFactory::tmp('lib.anahita.domain.mapper.table', array('table'=>$table,'adapter'=>$this->getAdapter()));
		$this->_tables['primary'] = $table;
		return $this;
	}
	
	/**
	 * 
	 * @return 
	 * @param $table Object
	 * @param $conditions Object
	 * @param $readonly Object[optional]
	 */
	public function joinTable($table, $conditions, $readonly = false, $type='INNER')
	{
		$sql = AnUtilArrayHelper::toString($conditions, ' = ', ' AND ', false, false);

		$this->rawQuery()->join[] = array(
				'type'  => $type,
				'table' => '#__'.$table,
				'condition' => array($sql)
		);
//		$this->rawQuery()->join($type, $table, $sql);
		$table = KFactory::tmp('lib.anahita.domain.mapper.table', array('table'=>$table,'adapter'=>$this->getAdapter()));
		$table->readonly = $readonly;
		$this->_tables['secondary'][] = $table;
		$table->joins($this->_tables['primary'], $conditions, $type);
		return $this;
	}
			
	/**
	 * persist a model to the database
	 * @return 
	 * @param $model Object
	 * @param $extra Object[optional]
	 */
	public function commit($model, $data = array())
	{	
		
		if ( $model->isDirty() ) {
			$updated_properties = $model->updatedProperties();
			$properties 	    = array_intersect_key($model->_properties, $updated_properties);
		} else {
			$properties 	 = $model->_properties;
		}
		$data   	 	 = array_merge($this->serialize($model->uniqueValues()), $this->serialize($properties), $data);
		$table		  	 = $this->_tables['primary'];
		$table_data 	 = $table->extractData($data);
		$identifiers 	 = $table->extractData($this->serialize($model->uniqueValues()));
		
		//what's the primary key of the table
		//it might not be id
		$pk				 = array_shift($this->getProperty('id')->getAttrTree());
		$condition	 	 = array();
		
		if  ( !$model->isNew() ) {
			$value			 = $data[$pk];
			$condition		 = $table->extractData(array($pk=>$value));
			if ( !count($condition) )
				throw new AnDomainMapperException("No criteria specified to update or delete");
		}
		
		if ( $model->isNew() ) {
			$data[$pk] =  $table->insert($table_data);
		} else if ( $model->isDirty() ) {
			$table->update($table_data, $condition);
		} else if ( $model->isDeleted() )
			$table->delete($condition);

		foreach($this->_tables['secondary'] as $table) {
			$table_data  = $table->extractData($data);
			$conditions  = $table->getCondition($data);
			if ( $model->isNew() ) {
				$insert_data = array_merge($table_data, $conditions);
				$table->insert( $insert_data );
			} else if ( $model->isDirty() )
				$table->update($table_data, $conditions);
			else if ( $model->isDeleted() )	
				$table->delete($conditions);
		}
		
		if ( $model->isNew() ) {
			$model->id = $data[$pk];
			//set all the external associations for the new instance
			foreach($this->getRelationships('onetomany') as $relationship) {
					$model->_properties[$relationship->getName()] = $relationship->materialize($model, $data);
			}
			$model->getDomainContext()->add($model);
		}
		//clean up the model 
		$model->markClean();
	}
	
	/**
	 * 
	 * @return 
	 * @param $repos Object
	 */
	public function setRepository($repos)
	{
		$this->_repos = $repos;
	}
	
	/**
	 * 
	 * @return 
	 */
	public function getRepository()
	{
		if ( !$this->_repos instanceof AnDomainRepositoryAbstract && !is_null($this->_repos) )
			$this->_repos = KFactory::tmp($this->_repos, array('mapper'=>$this) );
			
		return $this->_repos;
	}
		
	/**
	 * KDatabse Query Object
	 * @return 
	 */
	public function useQuery()
	{
		return $this->rawQuery();
	}
	
	public function filter()
	{
		return $this->rawQuery();	
	}
	
	public function rawQuery()
	{
		if ( is_null($this->_raw_query) ) {
			$this->_raw_query		  = $this->getAdapter()->getQuery();
		}
		return $this->_raw_query;
	}
	
	/**
	 * 
	 * @return 
	 * @param $property Object
	 */
	public function getProperty($property)
	{
		return isset($this->_properties[$property]) ? $this->_properties[$property] : null;
	}
	
	/**
	 * 
	 * @return 
	 * @param $type Object[optional]
	 */
	public function getRelationships($type = null)
	{
		$relationships = array();
		//create a relationship type based on the passed parameter
		$type 		   = $type ? 'AnDomainDescriptionRelationship'.ucfirst($type) : 'AnDomainDescriptionRelationshipAbstract';		
		foreach($this->_properties as $name=>$property) 
			if ( $property instanceof $type )
				$relationships[$name] = $property;
				
		return $relationships;		
	}
	
	public function getAttributes()
	{
		$attributes = array();
		
		foreach($this->_properties as $name=>$property) 
			if ( $property instanceof AnDomainDescriptionAttributeAbstract )
				$attributes[$name] = $property;
				
		return $attributes;
	}
	
	/**
	 * 
	 * @return 
	 * @param $property Object
	 * @param $callback Object[optional]
	 */
	public function discriminate($property, $callback = null)
	{
		$this->_discimniatings = array('property'=>$property, 'callback'=>$callback);
	}
	
	/**
	 * 
	 * @return 
	 */
	public function getDefaultValues()
	{
		if ( !isset($this->_default_values) ) {
			$this->_default_values = array();			
			foreach($this->getAttributes() as $name => $property) { 
				$this->_default_values[$name] = $property->getDefaultValue();
			}
		}
		return $this->_default_values;
	}
	
	/**
	 * 
	 * @return 
	 * @param $data Object
	 * @param $clone Object[optional]
	 */
	public function create($data, $clone = false)
	{
				
		//create an instance with its unique keys first
		$unique_properties = array();
		
		foreach($this->getUniqueKeys() as $property) { 
			$unique_properties[$property->getName()] = $property->materialize($data);
		}
		
		if ( $instance = $this->_context->exists($this, $unique_properties) ) {
			return $instance;
		}

		$attributes = array_diff_key($this->getAttributes(), $unique_properties);
				
		if ( $this->_discimniatings ) {
			
			$disc_property 		 = $this->_discimniatings['property'];
			$callback	   		 = $this->_discimniatings['callback'];
			//look for the value of the discriminating property in the 
			//remaining properies or the unique properties
			if ( isset($unique_properties[$disc_property]) )
				$disc_property_value = $unique_properties[$disc_property];
			else 
				$disc_property_value = $attributes[$disc_property]->materialize($data);
			
			if ( is_array($callback) || is_string($callback) )  {
				
				$model = call_user_func($callback, $disc_property_value);
									
			} else {
				
				$thismodel = new KIdentifier($this->_model);
				
				$model = clone $thismodel;
				
				$model->name = $disc_property_value;
			}
			
			$instance = KFactory::get('lib.anahita.domain.factory.model')->get($model);
			
		} else {
			
			$instance    = $this->getInstance($clone);

		}
		
		$instance->_properties = $unique_properties;
		
		foreach($attributes as $property) {			
			$instance->_properties[ $property->getName() ] = $property->materialize($data);			
		}

		foreach($this->getRelationships() as $relationship) {
		
			$instance->_properties[$relationship->getName()]   = $relationship->materialize($instance, $data);			
		}
										
		$this->_context->add($instance);
			
		$instance->awakeFromFetch();
		
		$instance->markClean();
				
		return $instance;				
	}
	
	public function getInstance($clone = false)
	{
		$instance = null;

		if ( !$instance ) {
			
			$instance = $clone ? clone $this->_instance : KFactory::tmp($this->_model, array('mapper' => $this));
		}
		
		$instance->markNew();
		
		return $instance;
	}
		
	/**
	 * add a property 
	 * @return 
	 * @param $name Object
	 * @param $options Object[optional]
	 */
	public function property($name, array $options = array())
	{				
		$options['name']   = $name;
		
		$options['mapper'] = $this;
										
		if ( !isset($options['class']) ) {
			$property = KFactory::tmp('lib.anahita.domain.description.attribute', $options);
			if ( isset($options['discriminate']) && $options['discriminate'] ) {
				$this->discriminate($name, $options['discriminate']);
			}
		} else {
			$property = KFactory::tmp('lib.anahita.domain.description.attribute.composite', $options);			
			/*
			if ( count($options['initialize_with']) > 1 )
				$property = KFactory::tmp('lib.anahita.domain.property.compound', $options);
			else
				$property = KFactory::tmp('lib.anahita.domain.property.object',   $options);
			*/
		}
		
		$this->_properties[$name] = $property;		
		
		return $property;
	}
	
	/**
	 * add many-to-many or one-to-many or one-to-one relationship
	 * @return 
	 * @param $cardinality Object
	 * @param $name Object
	 * @param $options Object[optional]
	 */
	public function has($cardinality, $name, $options = array())
	{
		$options['cardinality'] = $cardinality;
		$options['name']		= $name;
		$options['mapper']      = $this;		
		if ( !isset($options['through']) ) {
			$relationship = KFactory::tmp('lib.anahita.domain.description.relationship.onetomany',  $options);
		} else {
			$relationship = KFactory::tmp('lib.anahita.domain.description.relationship.manytomany', $options);
		}
		
		//add this relationship to the existing entities
		$identifier  =  array_shift($this->getInheritanceTree());
		
		$entities    = $this->_context->getEntities($identifier);
		
		$this->_properties[$name] = $relationship;
		
		foreach($entities as $entity) {
			
			$entity->_properties[$name] = new AnDomainProxyRelationship(array('relationship'=>$relationship, 'entity'=>$entity));
		}	
		
		return $relationship;
	}
	
	/**
	 * add a many-to-one relationship
	 * @return 
	 * @param $name Object
	 * @param $options Object[optional]
	 */
	public function belongsTo($name, $options = array())
	{
		$options['name']   = $name;
		$options['mapper'] = $this;
		$relationship = KFactory::tmp('lib.anahita.domain.description.relationship.manytoone', $options);
		$this->_properties[$name] = $relationship;
		return $relationship;
	}
		
	/**
	 * creates a key path mapping to a db field
	 * @return 
	 */
	public function attrKeyPathsToField()
	{
		if ( !isset($this->_attr_key_path) ) {
			$fields = array();
			foreach($this->_properties as $name => $mapper) {
				$tree   = $mapper->getAttrTree();
				$fields = array_merge($fields, $this->_buildKeyPath($tree));
			}
			$this->_attr_key_path = $fields;
		}
		return $this->_attr_key_path;
	}
		
	public function getAttribute($path)
	{	
		$attrs = $this->attrKeyPathsToField();
		return isset($attrs[$path]) ? $attrs[$path] : null;
	}
	
	/**
	 * creates a tree from a tree
	 * @return 
	 * @param $tree Object
	 */
	protected function _buildKeyPath($tree)
	{
		$paths = array();
		foreach($tree as $root=>$children) {
			if ( is_array($children) ) {
				$sub_paths = $this->_buildKeyPath($children);
				foreach($sub_paths as $sub_path => $value) {
					$paths[$root.'.'.$sub_path] = $value;
				}
			} else 
				$paths[$root] = $children;
		}	
		return $paths;
	}
		
	/**
	 * Serialize an entity into storaable database value
	 * @return 
	 * @param $entity Object
	 */	
	public function serialize($properties)
	{
		$data = array();
		
		foreach($properties as $name => $value) {
			
			if ( !$this->getProperty($name) ) 
				continue;
				
			$property   = $this->getProperty($name);
			
			$serialized = $property->serialize($value);
			
			if ( is_array($serialized) )
				$data = array_merge($data, $serialized);
		}
		
		return $data;
		
	}
	
	/**
	 * materialize raw database data into meaningful properties and relationships
	 * @return 
	 * @param $data Object
	 */
	public function materialize($data)
	{
		$properties = array();
		foreach($this->_properties as $property) {
			$properties[$property->getName()] = $property->materialize($data);
		}
		return $properties;
	}
		
	/**
	 * Fetch a signle entity from database
	 * @return 
	 * @param $query Object
	 */	
	public function fetch($query)
	{
		$row   = $this->_db->fetchAssoc((string) $query);
		
		if ( !$row ) return null;
		
		return $this->create($row, false);
	}
	
	
	/**
	 * Fetch a list of entities from database
	 * @return 
	 * @param $query Object
	 */
	public function fetchAll($query)
	{
		$rowset = $this->_db->fetchAssocList((string) $query);		
	
		$models = array();
		
		foreach($rowset as $row) {
			$models[] = $this->create($row, true);
		}
		
		return $models;
	}
	
	public function getDomainContext()
	{
		return $this->_context;	
	}
	
	public function getModelIdentifier()
	{			
		return new KIdentifier($this->_model);
	}
	
}
Return current item: Anahita Social Engine