Location: PHPKode > projects > FreeORM > FreeORM/SessionXML.php
<?php

/**
 * An Implementation of the abstract class Session. The mapping is based on XML file.
 * 
 * @package		com.freeorm
 * @author		Yide Zou
 * @link		http://www.freeorm.com
 * @copyright	Copyright (c) 2010 Yide Zou <hide@address.com>. 
 * 				All Rights Reserved.
 * @license		This software is released under the terms of the GNU Lesser General Public License
 * 				A copy of which is available from http://www.gnu.org/copyleft/lesser.html
 */



require_once 'Session.php';
require_once 'EntityProxy.php';

class SessionXML extends Session
{
	
	// xml orm
	private $cfg = null;
	
	
	/**
	 * parse the xml data,
	 * set the properties, the component object and many-to-one relation,
	 * the column and value will be saved into parameter arrays
	 * @param $objclone, the obj in cache, instead of XX-to-XX object, it use EntityProxy
	 */
	
	private function saveClassProp($classNode, $obj, $objclone, &$arrCol, &$arrVal)
	{
		/* check the basic properties */
		foreach ($classNode->property as $property)
		{
			// skip the property with the formular attribute, 
			// they are only useful with SELECT operation
			if ((string)$property['formular'] != '')
				continue;
				
			// skip the property with the insert='false'
			if ((string)$property['insert'] == 'false')
				continue;
				
			$fieldname = (string)$property['name'];
			$value = $obj->$fieldname;
			$objclone->$fieldname = $value;
			if (is_string($value))
				$value = "'$value'";
			$arrVal[] = $value;
			
			// set default column name to the property's name
			$column = (string)$property['column'] == '' ? $fieldname : (string)$property['column'];
			$arrCol[] = $this->db->sqlquote($column);
		}
		
		/* check the properties from the normal(not collection) component class */
		foreach ($classNode->component as $component)
		{
			$compFieldname = (string)$component['name'];
			$compClassname = (string)$component['class'];
			$objclone->$compFieldname = new $compClassname();
			
			foreach ($component->property as $property)
			{
				// skip the property with the formular attribute, they are only useful with SELECT operation
				if ((string)$property['formular'] != '')
					continue;
				// skip the property with the insert='false'
				if ((string)$property['insert'] == 'false')
					continue;
					
				$fieldname = (string)$property['name'];
				$value = $obj->$compFieldname->$fieldname;
				$objclone->$compFieldname->$fieldname = $value;
				if (is_string($value))
					$value = "'$value'";
				$arrVal[] = $value;
				
				// set default column name to the property's name
				$column = (string)$property['column'] == '' ? $fieldname : (string)$property['column'];
				$arrCol[] = $this->db->sqlquote($column);
			}
		}
		
		/* check the many-to-one relation */
		foreach ($classNode->{'many-to-one'} as $m2o)
		{
			$fieldname = (string)$m2o['name'];
			$fkcolumn = (string)$m2o['column'] == '' ? $fieldname : (string)$m2o['column'];
			$arrCol[] = $this->db->sqlquote($fkcolumn);
		
			$oneClassName = (string)$m2o['class'];
			//TODO: only works for hierarchy per class inherate strategy
			$oneClassNode = $this->getRootClassNode($oneClassName);
			$pkfield = (string)$oneClassNode->id['name'];
			$value = $obj->$fieldname->$pkfield;
			
			$objclone->$fieldname = new EntityProxy($this, $oneClassName, $value);
			
			if (is_string($value))
				$value = "'$value'";
			$arrVal[] = $value;
		}
		
	}
	
	
	/**
	 * Help funtion of saving/reading Component Collection, 
	 * parse the component tag inside collection tags 
	 * and get the fieldnames and columns array
	 * @param $component, the Node object
	 * @param $compFieldnames, the reference of the array of fieldnames
	 * @param $columns, the reference of the array of columns
	 * @param $withquote, boolean, whether the $columns with ``
	 */
	private function parseComponentCollection($component, &$compFieldnames, &$columns, $withquote=true)
	{
		// read the fieldnames and columns of component tag
		foreach ($component->property as $property)
		{
			// skip the property with the formular attribute, they are only useful with SELECT operation
			if ((string)$property['formular'] != '')
				continue;
			// skip the property with the insert='false'
			if ((string)$property['insert'] == 'false')
				continue;
			
			$compFieldname = (string)$property['name'];
			$compFieldnames[] = $compFieldname;
			// set default column name to the property's name
			$column = (string)$property['column'] == '' ? $compFieldname : (string)$property['column'];
			if ($withquote)
				$column = $this->db->sqlquote($column); 
			$columns[] = $column;
		}
	}
	
	/**
	 * Parse the xml and save the collections into own table
	 * It won't change the owner's table, but it need the id of owner, 
	 * so it should be called after the mainly insert operation of owner.
	 * 
	 * For the one-to-many relation, if the many side objects are not yet saved,
	 * It just do nothing, won't throw exceptions. 
	 * Because it just update the foreign key of the many side table.
	 * @param $classNode
	 * @param $obj
	 * @param $objclone the $cacheobj, instead of xx-to-xx relation object, the linked Entities will be replaced by EntityProxy
	 * @param $ownerid the id of owner object
	 * @param $caller
	 * @param $callerid they are useful for the many-to-many relation saving
	 */
	private function saveClassCollection($classNode, $obj, $objclone, $ownerid)
	{
		// set
		foreach ($classNode->set as $set)
		{
			$fieldname = (string)$set['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$set->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$set->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $set->component;
			$o2m = $set->{'one-to-many'};
			$m2m = $set->{'many-to-many'};
			
			/* save the elements */
			if ($elementname) 
			{
				$arrclone = array();
				foreach ($obj->$fieldname as $value)
				{
					$arrclone[] = $value;
					if (is_string($value))
						$value = "'$value'";
					$sql = "INSERT INTO $tablename ($keyname, $elementname) VALUES ($ownerid, $value)";
					$this->db->query($sql);
				}
				$objclone->$fieldname = $arrclone;
			} 
			/* save the components */
			elseif ($component) 
			{
				$compClassName = (string)$component['class'];
				$arrclone = array();
				
				$compFieldnames = array();
				$columns = array();
				$this->parseComponentCollection($component, $compFieldnames, $columns);
				$columns = implode(',', $columns);
				
				// read the value from object and save them into database
				foreach ($obj->$fieldname as $comp)
				{
					$compClone = new $compClassName();
					$values = array();
					foreach ($compFieldnames as $compFieldname)
					{
						$value = $comp->$compFieldname;
						$compClone->$compFieldname = $value;
						if (is_string($value))
							$value = "'$value'";
						$values[] = $value;
					}
					$values = implode(',', $values);
					
					$sql = "INSERT INTO $tablename ($keyname, $columns) VALUES ($ownerid, $values)";
					$this->db->query($sql);
					$arrclone[] = $compClone;
				}
				$objclone->$fieldname = $arrclone;
			}
			/* save the one-to-many relation */
			/* if the many side objects not yet be saved, nothing happens */
			elseif ($o2m)
			{
				$manyClassName = (string)$o2m['class'];
				//TODO: only works for hierarchy per class inherate strategy
				$manyClassNode = $this->getRootClassNode($manyClassName);
				$tablename = (string)$manyClassNode['table'] == '' ? (string)$manyClassNode['name'] : (string)$manyClassNode['table'];
				$tablename = $this->db->sqlquote($tablename);
				$pkfield = (string)$manyClassNode->id['name'];
				$pkname = (string)$manyClassNode->id['column'] == '' ? $pkfield : (string)$manyClassNode->id['column'];
				$pkname = $this->db->sqlquote($pkname);
				
				$ids = array();
				$arrclone = array();
				foreach ($obj->$fieldname as $mObj)
				{
					$id = $mObj->$pkfield;
					// the many side object is new
					if ($id === null)
					{
						// cascade save
						if ( strpos( (string)$set['cascade'], 'save-update') !== false )
							$id = $this->save($mObj);
						else
							continue;
					}
					else
					{
						// deattached object, then make it attached
						if (! $this->inCache((string)$manyClassNode['name'], $id, true) )
							$this->update($mObj, $id);
					}
					
					$arrclone[] = new EntityProxy($this, $manyClassName, $id);
					
					if (is_string($id))
						$id = "'$id'";
					$ids[] = $id;
					
				}
				if (count($ids)>0)
				{
					$ids = implode(',', $ids);
					$sql = "UPDATE $tablename SET $keyname=$ownerid WHERE $pkname in ($ids)";
					$this->db->query($sql);
				}
				$objclone->$fieldname = $arrclone;
			}
			/* save the many-to-many relation */
			elseif ($m2m)
			{
				$manyClassName = (string)$m2m['class'];
				//TODO: only works for hierarchy per class inherate strategy
				$manyClassNode = $this->getRootClassNode($manyClassName);
				$pkname = (string)$m2m['column'];
				$pkname = $this->db->sqlquote($pkname);
				$pkfield = (string)$manyClassNode->id['name'];
				
				$arrclone = array();
				foreach ($obj->$fieldname as $mObj)
				{
					$id = $mObj->$pkfield;
					// the many side object is new
					if ($id === null)
					{
						// cascade save
						if ( strpos( (string)$set['cascade'], 'save-update') !== false )
							$id = $this->save($mObj);
						else
							continue;
					}
					else
					{
						// if it's the inverse side of many-to-many relation, 
						// then the $id is its party object id, we dont need more check
						
						// if it's not the inverse side, then check the deattached state
						if ( (string)$set['inverse'] !== 'true' )
						{
							// deattached object, then make it attached
							if (! $this->inCache((string)$manyClassNode['name'], $id, true) )
								$this->update($mObj, $id);
						}
					}
					
					$arrclone[] = new EntityProxy($this, $manyClassName, $id);
					
					// prevent duplicated saving for the inverse side
					if ( (string)$set['inverse'] !== 'true' )
					{
						if (is_string($id))
							$id = "'$id'";
						$sql = "INSERT INTO $tablename ($keyname, $pkname) VALUES($ownerid, $id)";
						$this->db->query($sql);
					}
					
				}
				$objclone->$fieldname = $arrclone;	
			}
		}
		
		// bag
		foreach ($classNode->bag as $bag)
		{
			$fieldname = (string)$bag['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
			$tablename = $this->db->sqlquote($tablename);
			$pkname = (string)$bag->id['column'];
			$pkname = $this->db->sqlquote($pkname);
			$keyname = (string)$bag->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$bag->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $bag->component;
			
			/* save the elements */
			if ($elementname)
			{
				$arrclone = array();
				foreach ($obj->$fieldname as $value)
				{
					$arrclone[] = $value;
					if (is_string($value))
						$value = "'$value'";
					$sql = "INSERT INTO $tablename ($pkname, $keyname, $elementname) VALUES (NULL, $ownerid, $value)";
					$this->db->query($sql);
				}
				$objclone->$fieldname = $arrclone;
			}
			/* save the components */
			elseif ($component)
			{
				$compClassName = (string)$component['class'];
				$arrclone = array();
				
				$compFieldnames = array();
				$columns = array();
				$this->parseComponentCollection($component, $compFieldnames, $columns);
				$columns = implode(',', $columns);
				
				// read the value from object and save them into database
				foreach ($obj->$fieldname as $comp)
				{
					$compClone = new $compClassName();
					$values = array();
					foreach ($compFieldnames as $compFieldname)
					{
						$value = $comp->$compFieldname;
						$compClone->$compFieldname = $value;
						if (is_string($value))
							$value = "'$value'";
						$values[] = $value;
					}
					$values = implode(',', $values);
					
					$sql = "INSERT INTO $tablename ($pkname, $keyname, $columns) VALUES (NULL, $ownerid, $values)";
					$this->db->query($sql);
					$arrclone[] = $compClone;
				}
				$objclone->$fieldname = $arrclone;
			}
		}
		
		// map, the key of the map must be type string
		foreach ($classNode->map as $map)
		{
			$fieldname = (string)$map['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$map->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$mapkeyname = (string)$map->{'map-key'}['column'];
			$mapkeyname = $this->db->sqlquote($mapkeyname);
			$elementname = (string)$map->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $map->component;
			
			/* save the elements */
			if ($elementname)
			{
				$arrclone = array();
				foreach ($obj->$fieldname as $key=>$value)
				{
					$arrclone[$key] = $value;
					if (is_string($value))
						$value = "'$value'";
					$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $elementname) VALUES ($ownerid, '$key', $value)";
					$this->db->query($sql);
				}
				$objclone->$fieldname = $arrclone;
			}
			/* save the components */
			elseif ($component)
			{
				$compClassName = (string)$component['class'];
				$arrclone = array();
				
				$compFieldnames = array();
				$columns = array();
				$this->parseComponentCollection($component, $compFieldnames, $columns);
				$columns = implode(',', $columns);
				
				// read the value from object and save them into database
				foreach ($obj->$fieldname as $key=>$comp)
				{
					$compClone = new $compClassName();
					$values = array();
					foreach ($compFieldnames as $compFieldname)
					{
						$value = $comp->$compFieldname;
						$compClone->$compFieldname = $value;
						if (is_string($value))
							$value = "'$value'";
						$values[] = $value;
					}
					$values = implode(',', $values);
					
					$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $columns) VALUES ($ownerid, '$key', $values)";
					$this->db->query($sql);
					$arrclone[$key] = $compClone;
				}
				$objclone->$fieldname = $arrclone;
			}
		}
		
	}
	
	
	private function deleteClassCollection($classNode, $ownerid, $obj)
	{
		// set
		foreach ($classNode->set as $set)
		{
			$fieldname = (string)$set['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$set->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$set->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $set->component;
			$o2m = $set->{'one-to-many'};
			$m2m = $set->{'many-to-many'};
			
			/* delete the elements or components */
			if ($elementname || $component) 
			{
				$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
				$this->db->query($sql);
			} 
			/* delete the one-to-many relation */
			elseif ($o2m && strpos((string)$set['cascade'], 'delete')!==false)
			{
				foreach ($obj->$fieldname as $mObj)
					$this->delete($mObj);
			}
			/* delete the many-to-many relation */
			elseif ($m2m)
			{
				if ( strpos((string)$set['cascade'], 'delete')!==false && (string)$set['inverse']!=='true' )
				{
					foreach ($obj->$fieldname as $mObj)
						$this->delete($mObj);
				}
				
				// delete the relation link table
				$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
				$this->db->query($sql);
			}
		}
		
		// bag
		foreach ($classNode->bag as $bag)
		{
			$fieldname = (string)$bag['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$bag->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$bag->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $bag->component;
			
			if ($elementname || $component)
			{
				$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
				$this->db->query($sql);
			}
		}
		
		// map, the key of the map must be type string
		foreach ($classNode->map as $map)
		{
			$fieldname = (string)$map['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$map->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$map->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $map->component;
			
			if ($elementname || $component)
			{
				$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
				$this->db->query($sql);
			}
		}
		
	}
	
	
	/**
	 * Check the xml tag, if it's the root class
	 * @param object, a reference of the xml tag object
	 * @return boolean, true if it's root
	 */
	private function isRootClassNode($classNode)
	{
		//root has no joined and discriminator-value attribute.
		return (string)$classNode['joined']=='' && (string)$classNode['discriminator-value']==''; 
	}
	
	
	/**
	 * Parse the xml, read the date from database and save them into object
	 * include normal properties, collections, relations
	 * @param $obj, has no linked object, they will be replaced with DummyLinkedEntity. (objcache) 
	 * @param $obj2, is the real object with full links (objwatcher)  
	 * @param $id, int or string, if it's a string, it includes the '' quotation
	 * @return false, if no record found, (wrong id), otherweise return true
	 */
	private function readClassProp($classNode, $obj, $obj2, $tablename, $pkname, $id)
	{
		/* read the properties */
		$columns = array();
		$properties = array();
		foreach ($classNode->property as $property)
		{
			$propName = (string)$property['name'];
			$properties[] = $propName;
			
			// property with the formular attribute
			if ((string)$property['formular'] != '')
				$columns[] = (string)$property['formular'];
			else
			{
				// set default column name to the property's name
				$column = (string)$property['column'] == '' ? $propName : (string)$property['column'];
				$columns[] = $this->db->sqlquote($column);
			}
		}
		if ( count($columns)>0 )
		{
			$sql = "SELECT {$columns[0]} AS {$properties[0]}";
			for ($i=1; $i<count($columns); $i++)
			{
				$sql .= ",{$columns[$i]} AS {$properties[$i]}";
			}
			$sql .= " FROM $tablename WHERE $pkname=$id";
			
			$result = $this->db->loadObject($sql);
			// not found, wrong id
			if ($result === null)
				return false;
			foreach ($properties as $prop)
			{
				$obj->$prop = $result->$prop;
				$obj2->$prop = $result->$prop;
			}
		}
		
		
		
		/* read the many-to-one relation */
		$columns = array();
		$properties = array();
		$classnames = array();
		foreach ($classNode->{'many-to-one'} as $m2o)
		{
			$fieldname = (string)$m2o['name'];
			$properties[] = $fieldname;
			$fkcolumn = (string)$m2o['column'] == '' ? $fieldname : (string)$m2o['column'];
			// without sql quote, we need them later.
			$columns[] = $fkcolumn;
			$classname = (string)$m2o['class'];
			$classnames[] = $classname;
		}
		if ( count($columns)>0 )
		{
			$sql = "SELECT `{$columns[0]}`";
			for ($i=1; $i<count($columns); $i++)
			{
				$sql .= ",`{$columns[$i]}`";
			}
			$sql .= " FROM $tablename WHERE $pkname=$id";
			
			$result = $this->db->loadObject($sql);
			// not found, wrong id
			if ($result === null)
				return false;
			for ($i=0; $i<count($columns); $i++)
			{
				$pkvalue = $result->$columns[$i];
				// FIXME: is_numeric() return true for string like '3.12' and '2e12'		
				if (is_numeric($pkvalue))
					$pkvalue = (int)$pkvalue;

				$ep1 = new EntityProxy($this, $classnames[$i], $pkvalue);
				$ep2 = new EntityProxy($this, $classnames[$i], $pkvalue);
				$obj->$properties[$i] = $ep1;
				$obj2->$properties[$i] = $ep2;
				// save the EntityProxy into cache
				$this->insertCache($ep1, $ep2, $classnames[$i], $pkvalue, false);
			}
		}
		
		
		
		/* read collections */
		
		// set
		foreach ($classNode->set as $set)
		{
			$fieldname = (string)$set['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$set->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$set->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $set->component;
			$o2m = $set->{'one-to-many'};
			$m2m = $set->{'many-to-many'};
			$orderby = (string)$set['order-by'];
			if ($orderby)
				$orderby = "ORDER BY $orderby";
			
			$resultArr = array();
			$resultArr2 = array();
			
			/* elements */	
			if ($elementname)	
			{
				$sql = "SELECT $elementname AS element FROM $tablename WHERE $keyname=$id $orderby";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$resultArr[] = $result->element;
					$resultArr2[] = $result->element;
	    		}
			}
			/* components */
			elseif ($component)
			{
				$classname = (string)$component['class'];
				$compFieldnames = array();
				$columns = array();
				$this->parseComponentCollection($component, $compFieldnames, $columns, false);
				
				$columnsStr = "`{$columns[0]}`";
				for ($i=1; $i<count($columns); $i++)
					$columnsStr .= ",`{$columns[$i]}`";
				
				$sql = "SELECT $columnsStr FROM $tablename WHERE $keyname=$id $orderby";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$compObj = new $classname();
					$compObj2 = new $classname();
					for ($i = 0; $i<count($compFieldnames); $i++)
					{
						$compFieldname = $compFieldnames[$i];
						$column = $columns[$i];
						$compObj->$compFieldname = $result->$column;
						$compObj2->$compFieldname = $result->$column;
					}
					$resultArr[] = $compObj;
					$resultArr2[] = $compObj2;
	    		}
			}
			/* read the one-to-many relation */
			elseif ($o2m)
			{
				$manyClassName = (string)$o2m['class'];
				$manyClassNode = $this->getRootClassNode($manyClassName);
				$tablename = (string)$manyClassNode['table'] == '' ? (string)$manyClassNode['name'] : (string)$manyClassNode['table'];
				$tablename = $this->db->sqlquote($tablename);
				$pkfield = (string)$manyClassNode->id['name'];
				$pkname = (string)$manyClassNode->id['column'] == '' ? $pkfield : (string)$manyClassNode->id['column'];
				
				$sql = "SELECT `$pkname` FROM $tablename WHERE $keyname=$id";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$pkvalue = $result->$pkname;
					// FIXME: is_numeric() return true for string like '3.12' and '2e12'
					if (is_numeric($pkvalue))
						$pkvalue = (int)$pkvalue;

					$ep1 = new EntityProxy($this, $manyClassName, $pkvalue);
					$ep2 = new EntityProxy($this, $manyClassName, $pkvalue);
					$resultArr[] = $ep1;
					$resultArr2[] = $ep2;
					// save the EntityProxy into cache
					$this->insertCache($ep1, $ep2, (string)$manyClassNode['name'], $pkvalue, true);
	    		}
			}
			/* read the many-to-many relation */
			elseif ($m2m)
			{
				$manyClassName = (string)$m2m['class'];
				$manyClassNode = $this->getRootClassNode($manyClassName);
				$tablename = (string)$set['table'];
				$tablename = $this->db->sqlquote($tablename);
				$pkfield = (string)$manyClassNode->id['name'];
				$pkname = (string)$m2m['column'];
				
				$sql = "SELECT `$pkname` FROM $tablename WHERE $keyname=$id";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$pkvalue = $result->$pkname;
					// FIXME: is_numeric() return true for string like '3.12' and '2e12'
					if (is_numeric($pkvalue))
						$pkvalue = (int)$pkvalue;

					$ep1 = new EntityProxy($this, $manyClassName, $pkvalue);
					$ep2 = new EntityProxy($this, $manyClassName, $pkvalue);
					$resultArr[] = $ep1;
					$resultArr2[] = $ep2;
					// save the EntityProxy into cache
					$this->insertCache($ep1, $ep2, (string)$manyClassNode['name'], $pkvalue, true);
	    		}
			}
			
			$obj->$fieldname = $resultArr;
			$obj2->$fieldname = $resultArr2;
		}
		
		// bag
		foreach ($classNode->bag as $bag)
		{
			$fieldname = (string)$bag['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
			$tablename = $this->db->sqlquote($tablename);
			//$pkname = (string)$bag->id['column'];
			$keyname = (string)$bag->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$bag->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $bag->component;
			$orderby = (string)$bag['order-by'];
			if ($orderby)
				$orderby = "ORDER BY $orderby";
			
			$resultArr = array();
			$resultArr2 = array();
				
			/* elements */	
			if ($elementname)	
			{
				$sql = "SELECT $elementname AS element FROM $tablename WHERE $keyname=$id $orderby";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$resultArr[] = $result->element;
					$resultArr2[] = $result->element;
	    		}
			}
			/* components */
			elseif ($component)
			{
				$className = (string)$component['class'];
				$compFieldnames = array();
				$columns = array();
				$this->parseComponentCollection($component, $compFieldnames, $columns, false);
				
				$columnsStr = "`{$columns[0]}`";
				for ($i=1; $i<count($columns); $i++)
					$columnsStr .= ",`{$columns[$i]}`";
				
				$sql = "SELECT $columnsStr FROM $tablename WHERE $keyname=$id $orderby";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$compObj = new $className();
					$compObj2 = new $className();
					for ($i = 0; $i<count($compFieldnames); $i++)
					{
						$compFieldname = $compFieldnames[$i];
						$column = $columns[$i];
						$compObj->$compFieldname = $result->$column;
						$compObj2->$compFieldname = $result->$column;
					}
					$resultArr[] = $compObj;
					$resultArr2[] = $compObj2;
	    		}
			}
			
			$obj->$fieldname = $resultArr;
			$obj2->$fieldname = $resultArr2;
		}
		
		// map, the key of the map must be type string
		foreach ($classNode->map as $map)
		{
			$fieldname = (string)$map['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$map->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$mapkeyname = (string)$map->{'map-key'}['column'];
			$mapkeyname = $this->db->sqlquote($mapkeyname);
			$elementname = (string)$map->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $map->component;
			$orderby = (string)$map['order-by'];
			if ($orderby)
				$orderby = "ORDER BY $orderby";
			
			$resultArr = array();
			$resultArr2 = array();
				
			/* elements */	
			if ($elementname)	
			{	
				// becareful, key and value are keyword of MySQL, so here use res_ prefix
				$sql = "SELECT $mapkeyname AS res_key, $elementname AS res_value FROM $tablename WHERE $keyname=$id $orderby";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$resultArr[$result->res_key] = $result->res_value;
					$resultArr2[$result->res_key] = $result->res_value;
	    		}
			}
			/* components */
			elseif ($component)
			{
				$className = (string)$component['class'];
				$compFieldnames = array();
				$columns = array();
				$this->parseComponentCollection($component, $compFieldnames, $columns, false);
				
				$columnsStr = "`{$columns[0]}`";
				for ($i=1; $i<count($columns); $i++)
					$columnsStr .= ",`{$columns[$i]}`";
				
				$sql = "SELECT $mapkeyname AS res_key, $columnsStr FROM $tablename WHERE $keyname=$id $orderby";
				$results = $this->db->loadObjectList($sql);
				foreach ($results as $result) 
				{
					$compObj = new $className();
					$compObj2 = new $className();
					for ($i = 0; $i<count($compFieldnames); $i++)
					{
						$compFieldname = $compFieldnames[$i];
						$column = $columns[$i];
						$compObj->$compFieldname = $result->$column;
						$compObj2->$compFieldname = $result->$column;
					}
					$resultArr[$result->res_key] = $compObj;
					$resultArr2[$result->res_key] = $compObj2;
	    		}
			}
			$obj->$fieldname = $resultArr;
			$obj2->$fieldname = $resultArr2;
		}
		
		return true;
	}
	
	
	
	/**
	 *	Get the root class node
	 *  @param String, the current class name
	 *  @return Object, the root class node object 
	 */
	private function getRootClassNode($classname)
	{
		$rootClassNode = $this->cfg->xpath("//class[@name='$classname']/ancestor-or-self::*[parent::mappings]");
		return $rootClassNode[0];
	}
	
	
	
	public function __construct($db, $logger, $cfg)
	{
		parent::__construct($db, $logger);
		$this->cfg = $cfg;
	}
	
	
	/**
	 * Delete the object from database
	 * @param $obj
	 * @return the id of the object
	 */
	protected function doDelete($obj)
	{
		$classname = $this->getObjClassname($obj);
		$id = $this->getObjId($obj);
		
		$classNode = $this->cfg->xpath("//class[@name='$classname']");
		$classNode = $classNode[0];
		
		// found the root? if not then delete the collection properties from bottom to up
		while (!$this->isRootClassNode($classNode))
		{
			$this->deleteClassCollection($classNode, $id, $obj);
			
			// look other level of the class hierarchy
			// default: joined=false, use discriminator inherit strategy
			if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
			{
				$parentClassname = get_parent_class($classname);
				$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
				$parentClassNode = $parentClassNode[0];
				
				// recursiv lookup
				$classNode = $parentClassNode;
				$classname = $parentClassname;
			}
		}
		// the root class
		$this->deleteClassCollection($classNode, $id, $obj);
		
		/*
		 * Delete the basic class table,
		 * we just skip the many-to-one relation
		 */
		
		$tablename = (string)$classNode['table'] == '' ? $classname : (string)$classNode['table'];
		$tablename = $this->db->sqlquote($tablename);
		$pkfield = (string)$classNode->id['name'];
		$pkname = (string)$classNode->id['column'] == '' ? $pkfield : (string)$classNode->id['column'];
		$pkname = $this->db->sqlquote($pkname);
		
		$sql = "DELETE FROM $tablename WHERE $pkname=$id";
		$this->db->query($sql);
		
		// make the delete real happen
		$this->flush();
		
		return $id;	
	}
	
	
	
	/**
	 * Save the object into database and in cache
	 * @param $obj
	 * @return last inserted id
	 */
	protected function doSave($obj)
	{
		$columns = array();
		$values = array();
		
		$classname = get_class($obj);
		// make a copy of it, we need it later.
		$_classname = $classname;
		
		// the cache object, which use EntityProxy
		$objcache = new $classname();
		
		$classNode = $this->cfg->xpath("//class[@name='$classname']");
		$classNode = $classNode[0];
		$_classNode = clone $classNode;
		
		/*
		 * Saving basic properties
		 */
		
		$this->logger->log('saving basic properties');
		
		// found the root? if not then save the basic properties from bottom to up
		while (!$this->isRootClassNode($classNode))
		{
			$this->saveClassProp($classNode, $obj, $objcache, $columns, $values);
			
			// save other level of the class hierarchy
			// default: joined=false, use discriminator inherit strategy
			if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
			{
				$parentClassname = get_parent_class($classname);
				$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
				$parentClassNode = $parentClassNode[0];
				
				// if the discriminator tag with a formular attribute, 
				// then wont save the discriminator-value into database
				if ((string)$parentClassNode->discriminator['formular'] == '')
				{
					$columns[] = $this->db->sqlquote( (string)$parentClassNode->discriminator['column'] );
					$values[] = "'".(string)$classNode['discriminator-value']."'";
				}
				
				// recursiv lookup
				$classNode = $parentClassNode;
				$classname = $parentClassname;
			}
		}
		// the root class
		$this->saveClassProp($classNode, $obj, $objcache, $columns, $values);
		
		
		$colums_str = implode(",", $columns);
		$values_str = implode(",", $values);
		
		// set table name same to the classname by default
		$tablename = (string)$classNode['table'] == '' ? $classname : (string)$classNode['table'];
		$tablename = $this->db->sqlquote($tablename);
		// set default primary key column name to the class identify property's name
		$pkfield = (string)$classNode->id['name'];
		$pkname = (string)$classNode->id['column'] == '' ? $pkfield : (string)$classNode->id['column'];
		$pkname = $this->db->sqlquote($pkname);
		
		$sql = "INSERT INTO $tablename ($pkname, $colums_str) VALUES (NULL, $values_str)";
		$this->db->query($sql);
		
		// make the insert real happen
		$this->flush();
		
		// set the lacked id  of $obj to the auto generated primary key
		$objid = $this->db->getInsertId();
		$obj->$pkfield = $objid;
		$objcache->$pkfield = $objid;
		
		
		
		/* 
		 * saving the collections like set/bag/map of classes 
		 */
		$this->logger->log('saving the collections');
		
		$classname = $_classname;
		$classNode = clone $_classNode;
		
		// found the root? if not then save the collections from bottom to up
		while (!$this->isRootClassNode($classNode))
		{
			$this->saveClassCollection($classNode, $obj, $objcache, $objid);
			
			// save other level of the class hierarchy
			// default: joined=false, use discriminator inherit strategy
			if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
			{
				$parentClassname = get_parent_class($classname);
				$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
				$parentClassNode = $parentClassNode[0];
				
				// recursiv lookup
				$classNode = $parentClassNode;
				$classname = $parentClassname;
			}
		}
		// the root class
		$this->saveClassCollection($classNode, $obj, $objcache, $objid);
		
		//TODO: maybe we should not make this flush? we just need a insert_id
		$this->flush();
		
		// save it into cache
		$this->insertCache($objcache, $obj, $classname, $objid, true);
		
		return $objid;	
	}
	
	
	/*
	 * Override
	 */
	protected function getRootClassname($classname)
	{
		$classNode = $this->getRootClassNode($classname);
		if ($classNode !== null)
			return (string)$classNode['name'];
		else
			return null;
	}
	
	
	/**
	 * Read an object from database.
	 * @param $classname: the assumed return class type, the actual class type depends on the saved object
	 * @param $id: the id of the object
	 * @return an array of two object, 
	 * 	they have no real linked object, it will be replaced by DummyLinkedEntity.
	 * it's polymorph, get_class($obj) will return the true class name, the subclass.
	 */
	protected function doGet($classname, $id)
	{
		// add quote if $id is a string
		if (is_string($id))
			$id = "'$id'";
		
		// find the root classNode, 
		// we need it, because for the "hierarchy per table" inherate strategy, the database table information is saved there. 
		$rootClassNode = $this->getRootClassNode($classname);
		
		// set table name, pkname and idprop from the xml root class
		$tablename = (string)$rootClassNode['table'] == '' ? (string)$rootClassNode['name'] : (string)$rootClassNode['table'];
		$tablename = $this->db->sqlquote($tablename);
		$pkname = (string)$rootClassNode->id['column'] == '' ? (string)$rootClassNode->id['name'] : (string)$rootClassNode->id['column'];
		$pkname = $this->db->sqlquote($pkname);
		$idprop = (string)$rootClassNode->id['name'];
		
		
		/* 
		 * From top to bottom search the xml, set the $classNode and $classname to the actual object class
		 * We cannot direct use the classname from the parameter of this function, because it can be the superclass. (Polymorph)
		 */
		$classNode = $rootClassNode;
		// has subclass?
		while ((string)$classNode->class != '')
		{
			$discriminator = $classNode->discriminator;
			// property with the formular attribute
			if ((string)$discriminator['formular'] != '')
				$column = (string)$discriminator['formular'];
			else
				$column = $this->db->sqlquote( (string)$discriminator['column'] );
			
			$sql = "SELECT $column AS discriminatorVar FROM $tablename WHERE $pkname=$id";
			
			$discriminatorVar = $this->db->loadObject($sql)->discriminatorVar;
			// discriminatorVar==null means, there is subclass xml defination, 
			// but the object is the instance of the superclass
			// we found the object, break
			if ($discriminatorVar == null)
				break;
			
			// recursive
			$classNode = $classNode->xpath("class[@discriminator-value='$discriminatorVar']");
			$classNode = $classNode[0];
		}
		// the sub object which we expect
		$classname = (string)$classNode['name'];
		$obj = new $classname();
		$obj2 = new $classname();
		$obj->$idprop = $id;
		$obj2->$idprop = $id;
		
		
		// from bottom to to search the xml, set the values to object 
		// found the root?
		while (!$this->isRootClassNode($classNode))
		{
			// set the properties of the entity class
			// if no record found, that means wrong id
			if (!$this->readClassProp($classNode, $obj, $obj2, $tablename, $pkname, $id))
				return null;
			// set the properties of the component class
			foreach ($classNode->component as $component)
			{
				$compFieldname = (string)$component['name'];
				$compClassname = (string)$component['class'];
				$compObj = new $compClassname();
				$compObj2 = new $compClassname();
				
				$this->readClassProp($component, $compObj, $compObj2, $tablename, $pkname, $id);
				$obj->$compFieldname = $compObj;
				$obj2->$compFieldname = $compObj2;
			}
			
			// read other level of the class hierarchy
			// default: joined=false, use discriminator inherit strategy
			if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
			{
				$parentClassname = get_parent_class($classname);
				$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
				$parentClassNode = $parentClassNode[0];
				
				// recursiv lookup
				$classNode = $parentClassNode;
				$classname = $parentClassname;
			}
			
		}
		// the properties of the root class
		// if no record found, that means wrong id
		if (!$this->readClassProp($classNode, $obj, $obj2, $tablename, $pkname, $id))
			return null;
		// set the properties of the component class
		foreach ($classNode->component as $component)
		{
			$compFieldname = (string)$component['name'];
			$compClassname = (string)$component['class'];
			$compObj = new $compClassname();
			$compObj2 = new $compClassname();
			
			$this->readClassProp($component, $compObj, $compObj2, $tablename, $pkname, $id);
			$obj->$compFieldname = $compObj;
			$obj2->$compFieldname = $compObj2;
		}
		
		return array($obj, $obj2);
	}
	
	
	/**
	 * Get the id of the object, the object can be an EntityProxy or a real object
	 * @param $obj
	 * @return null if the object cannot be found in database
	 */
	public function getObjId($obj)
	{
		if ($obj instanceof EntityProxy)
			return $obj->id;
		$rootClassNode = $this->getRootClassNode(get_class($obj));
		// not found
		if ($rootClassNode == null)
			return null;
		$idprop = (string)$rootClassNode->id['name'];
		return $obj->$idprop;
	}
	
	
	
	/**
	 * Compare the objcache and objwatcher
	 * update the changes to database
	 */
	protected function doDirtyCheck($objcache, $objwatcher)
	{
		// the get_class() always return the actual class name, 
		// so we dont need to care about polymorph
		$classname = $this->getObjClassname($objcache);
		
		// find the root classNode, 
		// we need it, because for the "hierarchy per table" inherate strategy, the database table information is saved there. 
		$rootClassNode = $this->getRootClassNode($classname);
		
		// set table name, pkname and idprop from the xml root class
		$tablename = (string)$rootClassNode['table'] == '' ? (string)$rootClassNode['name'] : (string)$rootClassNode['table'];
		$tablename = $this->db->sqlquote($tablename);
		$pkname = (string)$rootClassNode->id['column'] == '' ? (string)$rootClassNode->id['name'] : (string)$rootClassNode->id['column'];
		$pkname = $this->db->sqlquote($pkname);
		$idprop = (string)$rootClassNode->id['name'];
		
		$id = $objcache->$idprop;
		// add quote if $id is a string
		if (is_string($id))
			$id = "'$id'";
		
		// the class node of the actual class
		$classNode = $this->cfg->xpath("//class[@name='$classname']");
		$classNode = $classNode[0];
		
		
		// from bottom to up to search the xml, set the values to object 
		// found the root?
		while (!$this->isRootClassNode($classNode))
		{
			// check the properties of the entity class
			$this->checkClassProp($classNode, $objcache, $objwatcher, $tablename, $pkname, $id);
			// check the properties of the component class
			foreach ($classNode->component as $component)
			{
				$compFieldname = (string)$component['name'];
				$this->checkClassProp($component, $objcache->$compFieldname, $objwatcher->$compFieldname, $tablename, $pkname, $id);
			}
			
			// read other level of the class hierarchy
			// default: joined=false, use discriminator inherit strategy
			if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
			{
				$parentClassname = get_parent_class($classname);
				$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
				$parentClassNode = $parentClassNode[0];
				
				// recursiv lookup
				$classNode = $parentClassNode;
				$classname = $parentClassname;
			}
			
		}
		// the properties of the root class
		$this->checkClassProp($classNode, $objcache, $objwatcher, $tablename, $pkname, $id);
		// check the properties of the component class
		foreach ($classNode->component as $component)
		{
			$compFieldname = (string)$component['name'];
			$this->checkClassProp($component, $objcache->$compFieldname, $objwatcher->$compFieldname, $tablename, $pkname, $id);
		}
		
	}
	
	
	private function checkClassProp($classNode, $objcache, $objwatcher, $tablename, $pkname, $id)
	{
		// both are same EntityProxy and still not be "touched", no need more check.
		if ($objcache instanceof EntityProxy && $objcache->equalsAndUnloaded($objwatcher))
			return true;
		
		/* check the properties */
		$columns = array();
		$properties = array();
		foreach ($classNode->property as $property)
		{
			// property with the formular attribute, it's read only
			if ((string)$property['formular'] != '')
				continue;
			else
			{
				$propName = (string)$property['name'];
				// record the different columns
				if ($objcache->$propName != $objwatcher->$propName)
				{
					$value = $objwatcher->$propName;
					if (is_string($value))
						$value = "'$value'";
					$properties[] = $value;
					// set default column name to the property's name
					$column = (string)$property['column'] == '' ? $propName : (string)$property['column'];
					$columns[] = $this->db->sqlquote($column);
				}
			}
		}
		if ( count($columns)>0 )
		{
			$sql = "UPDATE $tablename SET {$columns[0]} = {$properties[0]}";
			for ($i=1; $i<count($columns); $i++)
			{
				$sql .= ",{$columns[$i]} = {$properties[$i]}";
			}
			$sql .= " WHERE $pkname=$id";
			$this->db->query($sql);
		}
		
		
		
		/* check the many-to-one relation */
		$columns = array();
		$properties = array();
		foreach ($classNode->{'many-to-one'} as $m2o)
		{
			$fieldname = (string)$m2o['name'];
			if (! $objcache->$fieldname->equals($objwatcher->$fieldname) )
			{
				$fkcolumn = (string)$m2o['column'] == '' ? $fieldname : (string)$m2o['column'];
				$columns[] = $this->db->sqlquote($fkcolumn);
				
				$watcher = $objwatcher->$fieldname;
				$watcherId = $this->getObjId($watcher);
				$watcherClassname = $this->getObjClassname($watcher);
				
				// id == null, that means it's a new entity
				if ($watcherId === null)
					$watcherId = $this->save($watcher);
				// deattached object, then make it attached, in order to dirty check it later.
				if (! $this->inCache($watcherClassname, $watcherId, false) )
				{
					$this->update($watcher, $watcherId);
					// the new attached object changes the cache, but the foreach in Session.dirtycheck() will not check it at all!
					// so we should do the dirty check at once  
					$this->doDirtyCheck($this->objcache[$this->getRootClassname($watcherClassname)][$watcherId], $watcher);
				}

				$properties[] = $watcherId;
			}
		}
		if ( count($columns)>0 )
		{
			$sql = "UPDATE $tablename SET {$columns[0]} = {$properties[0]}";
			for ($i=1; $i<count($columns); $i++)
			{
				$sql .= ",{$columns[$i]} = {$properties[$i]}";
			}
			$sql .= " WHERE $pkname=$id";
			$this->db->query($sql);
		}
		
		
		
		/* check collections */
		
		// set
		foreach ($classNode->set as $set)
		{
			$fieldname = (string)$set['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$set->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$set->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $set->component;
			$o2m = $set->{'one-to-many'};
			$m2m = $set->{'many-to-many'};
			

			/* elements */	
			if ($elementname)	
			{
				// array compare (not matter the order)
				if ($objwatcher->$fieldname != $objcache->$fieldname)
				{
					// wont update, because no primary key information, just delete and insert again.
					$sql = "DELETE FROM $tablename WHERE $keyname=$id";
					$this->db->query($sql);
					
					foreach ($objwatcher->$fieldname as $value)
					{
						if (is_string($value))
							$value = "'$value'";
						$sql = "INSERT INTO $tablename ($keyname, $elementname) VALUES ($id, $value)";
						$this->db->query($sql);
					}
				}
			}
			/* components */
			elseif ($component)
			{
				// array compare (not matter the order)
				if ($objwatcher->$fieldname != $objcache->$fieldname)
				{
					// wont update, but delete and insert again
					$sql = "DELETE FROM $tablename WHERE $keyname=$id";
					$this->db->query($sql);
					
					$compFieldnames = array();
					$columns = array();
					$this->parseComponentCollection($component, $compFieldnames, $columns);
					$columns = implode(',', $columns);
					// read the value from object and save them into database
					foreach ($objwatcher->$fieldname as $comp)
					{
						$values = array();
						foreach ($compFieldnames as $compFieldname)
						{
							$value = $comp->$compFieldname;
							if (is_string($value))
								$value = "'$value'";
							$values[] = $value;
						}
						$values = implode(',', $values);
						$sql = "INSERT INTO $tablename ($keyname, $columns) VALUES ($id, $values)";
						$this->db->query($sql);
					}
				}
			}
			/* check the one-to-many relation */
			elseif ($o2m)
			{
				if (! $this->areSameEntityCollection($objcache->$fieldname, $objwatcher->$fieldname) )
				{
					$manyClassName = (string)$o2m['class'];
					$manyClassNode = $this->getRootClassNode($manyClassName);
					$manyClassRootName = (string)$manyClassNode['name'];
					$tablename = (string)$manyClassNode['table'] == '' ? (string)$manyClassNode['name'] : (string)$manyClassNode['table'];
					$tablename = $this->db->sqlquote($tablename);
					$pkfield = (string)$manyClassNode->id['name'];
					$pkname = (string)$manyClassNode->id['column'] == '' ? $pkfield : (string)$manyClassNode->id['column'];
					$pkname = $this->db->sqlquote($pkname);
					
					// update all
					$ids = array();
					foreach ($objwatcher->$fieldname as $mObj)
					{
						$watcherId = $this->getObjId($mObj);
						
						// the many side object is new
						if ($watcherId === null)
						{
							// cascade save
							if ( strpos( (string)$set['cascade'], 'save-update') !== false )
								$watcherId = $this->save($mObj);
							else
								continue;
						}
						else
						{
							// deattached object, then make it attached
							if (! $this->inCache($manyClassRootName, $watcherId, true) )
							{
								$this->update($mObj, $watcherId);
								// the new attached object changes the cache, but the foreach in Session.dirtycheck() will not check it at all!
								// so we should do the dirty check at once  
								$this->doDirtyCheck($this->objcache[$manyClassRootName][$watcherId], $mObj);
							}
						}
						if (is_string($watcherId))
							$watcherId = "'$watcherId'";
							
						$ids[] = $watcherId;
					}		
							
					$ids = implode(',', $ids);
					$sql = "UPDATE $tablename SET $keyname=$id WHERE $pkname in ($ids)";
					$this->db->query($sql);
					//TODO: the old unlinked entity will not be deleted? (It's a work of user)
				}
			}
			/* check the many-to-many relation */
			elseif ($m2m)
			{
				if (! $this->areSameEntityCollection($objcache->$fieldname, $objwatcher->$fieldname) )
				{
					$manyClassName = (string)$m2m['class'];
					$manyClassNode = $this->getRootClassNode($manyClassName);
					$manyClassRootName = (string)$manyClassNode['name'];
					$tablename = (string)$set['table'];
					$tablename = $this->db->sqlquote($tablename);
					$pkfield = (string)$manyClassNode->id['name'];
					$pkname = (string)$m2m['column'];
					$pkname = $this->db->sqlquote($pkname);
					
					// delete and then insert
					$sql = "DELETE FROM $tablename WHERE $keyname=$id";
					$this->db->query($sql);
					
					
					foreach ($objwatcher->$fieldname as $mObj)
					{
						$watcherId = $this->getObjId($mObj);
						// the many side object is new
						if ($watcherId === null)
						{
							// cascade save
							if ( strpos( (string)$set['cascade'], 'save-update') !== false )
								$watcherId = $this->save($mObj);
							else
								continue;
						}
						
						if (is_string($watcherId))
							$watcherId = "'$watcherId'";
						$sql = "INSERT INTO $tablename ($keyname, $pkname) VALUES($id, $watcherId)";
						$this->db->query($sql);
						
						// deattached object, then make it attached
						if (! $this->inCache($manyClassRootName, $watcherId, true) )
						{
							$this->update($mObj, $watcherId);
							// the new attached object changes the cache, but the foreach in Session.dirtycheck() will not check it at all!
							// so we should do the dirty check at once  
							$this->doDirtyCheck($this->objcache[$manyClassRootName][$watcherId], $mObj);
						}
						
					}
				}
			}
			
		}
		
		// bag
		foreach ($classNode->bag as $bag)
		{
			$fieldname = (string)$bag['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
			$tablename = $this->db->sqlquote($tablename);
			//$pkname = (string)$bag->id['column'];
			$keyname = (string)$bag->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$elementname = (string)$bag->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $bag->component;
			

			/* elements */	
			if ($elementname)	
			{
				// array compare (not matter the order)
				if ($objwatcher->$fieldname != $objcache->$fieldname)
				{
					// wont update, because no primary key information, just delete and insert again.
					$sql = "DELETE FROM $tablename WHERE $keyname=$id";
					$this->db->query($sql);
					
					foreach ($objwatcher->$fieldname as $value)
					{
						if (is_string($value))
							$value = "'$value'";
						$sql = "INSERT INTO $tablename ($keyname, $elementname) VALUES ($id, $value)";
						$this->db->query($sql);
					}
				}
			}
			/* components */
			elseif ($component)
			{
				// array compare (not matter the order)
				if ($objwatcher->$fieldname != $objcache->$fieldname)
				{
					// wont update, but delete and insert again
					$sql = "DELETE FROM $tablename WHERE $keyname=$id";
					$this->db->query($sql);
					
					$compFieldnames = array();
					$columns = array();
					$this->parseComponentCollection($component, $compFieldnames, $columns);
					$columns = implode(',', $columns);
					// read the value from object and save them into database
					foreach ($objwatcher->$fieldname as $comp)
					{
						$values = array();
						foreach ($compFieldnames as $compFieldname)
						{
							$value = $comp->$compFieldname;
							if (is_string($value))
								$value = "'$value'";
							$values[] = $value;
						}
						$values = implode(',', $values);
						$sql = "INSERT INTO $tablename ($keyname, $columns) VALUES ($id, $values)";
						$this->db->query($sql);
					}
				}
			}
		}
		
		// map, the key of the map must be type string
		foreach ($classNode->map as $map)
		{
			$fieldname = (string)$map['name'];
			// set table name same to the fieldname by default
			$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
			$tablename = $this->db->sqlquote($tablename);
			$keyname = (string)$map->key['column'];
			$keyname = $this->db->sqlquote($keyname);
			$mapkeyname = (string)$map->{'map-key'}['column'];
			$mapkeyname = $this->db->sqlquote($mapkeyname);
			$elementname = (string)$map->element['column'];
			$elementname = $this->db->sqlquote($elementname);
			$component = $map->component;
			

			/* elements */	
			if ($elementname)	
			{	
				// array compare (not matter the order)
				if ($objwatcher->$fieldname != $objcache->$fieldname)
				{
					// wont update, because no primary key information, just delete and insert again.
					$sql = "DELETE FROM $tablename WHERE $keyname=$id";
					$this->db->query($sql);
					
					foreach ($objwatcher->$fieldname as $key=>$value)
					{
						if (is_string($value))
							$value = "'$value'";
						$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $elementname) VALUES ($id, '$key', $value)";
						$this->db->query($sql);
					}
				}
			}
			/* components */
			elseif ($component)
			{
				// array compare (not matter the order)
				if ($objwatcher->$fieldname != $objcache->$fieldname)
				{
					// wont update, but delete and insert again
					$sql = "DELETE FROM $tablename WHERE $keyname=$id";
					$this->db->query($sql);
					
					$compFieldnames = array();
					$columns = array();
					$this->parseComponentCollection($component, $compFieldnames, $columns);
					$columns = implode(',', $columns);
					// read the value from object and save them into database
					foreach ($objwatcher->$fieldname as $key=>$comp)
					{
						$values = array();
						foreach ($compFieldnames as $compFieldname)
						{
							$value = $comp->$compFieldname;
							if (is_string($value))
								$value = "'$value'";
							$values[] = $value;
						}
						$values = implode(',', $values);
						$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $columns) VALUES ($id, '$key', $values)";
						$this->db->query($sql);
					}
				}
			}
		}
	}
	
}
Return current item: FreeORM