<?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);
}
}