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

/**
 * The abstract class of Session. There are definitions of user API 
 * and the most part of implementations about cache
 * 
 * @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 'exceptions/ClassNotFoundException.php';
require_once 'exceptions/ObjectNotFoundException.php';
require_once 'exceptions/NonUniqueObjectException.php';


abstract class Session 
{
	// Database object
	protected $db = null;
	protected $logger = null;

	/*
	 * the cache of attached objects
	 * the key is the rootclassname
	 * the value is a map, which key is the id and the value is the obj
	 * All Entities in the object will be replaced by EntityProxy 
	 * They should not be modified by user 
	 */
	protected $objcache = array();
	/*
	 * same as $objcache but they are references of objects
	 * they can be modified by user (they are the actual state of object)
	 * It will be used in auto-dirty-check
	 * The Entities in the object can be real object or an EntityProxy
	 */
	protected $objwatcher = array();
	
	public function __construct(Database $db, Logger $logger)
	{
		$this->db = $db;
		$this->logger = $logger;
	}
	
	
	/*
	 * Main logic functions
	 */
	
	/**
	 * Save the object into database.
	 * It will automatisch call flush(), make the insert really happen.
	 * The object will be also saved in cache and watcher, 
	 * that means its state is now "attached".
	 * @param $obj
	 * @return the last auto generated id
	 */
	public function save($obj)
	{
		$classname = get_class($obj);
		$rootclassname = $this->getRootClassname($classname);
		// class not found
		if ($rootclassname === null)
			throw new ClassNotFoundException($classname);
		
		$this->logger->debug("Saving the object into database");
		return $this->doSave($obj);
	}
	
	/**
	 * Delete the object from database and cache/watcher
	 * Delete a in database not exist object, nothing bad happen. 
	 * (So we can do many-to-many cascade delete)
	 * @param $obj
	 */
	public function delete($obj)
	{
		$this->logger->debug("Deleting the object from database");
		$id = $this->doDelete($obj);
		$this->evict($obj, $id);
	}
	
	
	/**
	 * Commit all transactions
	 */
	public function flush()
	{
		$this->logger->debug("Commit the transaction");
		if (!$this->db->commit())
			throw new DBTransactionException();
	}
	
	
	/**
	 * Check if an object with the classname and id in cache
	 * @param $classname
	 * @param $id
	 * @return true if found
	 */
	protected function inCache($classname, $id, $isRootClassname=false)
	{
		if (!$isRootClassname)
			$classname = $this->getRootClassname($classname);
		// class not found
		if ($classname === null)
			return false;
		return array_key_exists($classname, $this->objcache)
			&& array_key_exists($id, $this->objcache[$classname]);
	}
	
	/**
	 * Insert the objects into cache and watcher
	 * @param $objcache
	 * @param $objwatcher
	 * @param $classname
	 * @param $id
	 * @param $isRootClassname true, if the parameter $classname is the root class name
	 * @return unknown_type
	 */
	protected function insertCache($objcache, $objwatcher, $classname, $id, $isRootClassname=true)
	{
		if (!$isRootClassname)
			$classname = $this->getRootClassname($classname);
		// the old objects should never be overrided.
		if (! $this->inCache($classname, $id, true) )
		{
			$this->logger->debug("Saving the object into cache and watcher [$classname][$id]");
			if ($objcache !== null)
				$this->objcache[$classname][$id] = $objcache;
			if ($objwatcher !== null)
				$this->objwatcher[$classname][$id] = $objwatcher;
		}
	}
	
	
	/**
	 * Deattach an object, remove it from cache and watcher
	 * @param $obj, a real object or an EntityProxy
	 * @param $id
	 * @return false if the obj is not found in cache
	 */
	public function evict($obj, $id)
	{
		$classname = $this->getObjClassname($obj);
		if (!$this->inCache($classname, $id, false))
			return false;
		else
		{
			$rootClassname = $this->getRootClassname($classname);
			$this->logger->debug("Removing the object from cache and watcher [$rootClassname][$id]");
			unset($this->objcache[$rootClassname][$id]);
			unset($this->objwatcher[$rootClassname][$id]);
			return true;
		}
	}
	
	/**
	 * Clear the whole cache and watcher, all attached objects become deattached
	 * @return unknown_type
	 */
	public function clear()
	{
		$this->objcache = array();
		$this->objwatcher = array();
	}
	
	
	
	/**
	 * Load an object with the class and id from database. And save them into cache. 
	 * The key of the cache map is the root classname, because the object id is always
	 * unique in whole class hierarchy 
	 * @param $classname
	 * @param $id
	 * @return the proxy object (with EntityProxy), if not found return null
	 * @throws ClassNotFoundException if classname undefined in ORM
	 * @throws ObjectNotFoundException if no record with the id can be found in database
	 */
	public function get($classname, $id)
	{
		$rootclassname = $this->getRootClassname($classname);
		// class not found
		if ($rootclassname === null)
			throw new ClassNotFoundException($classname);
		
		// check the cache, if found don't need hit database any more.
		if ($this->inCache($rootclassname, $id, true))
		{
			$obj = $this->objcache[$rootclassname][$id];
			// if the $obj is EntityProxy, then its empty, we should also hit database
			if (! $obj instanceof EntityProxy)
			{
				$this->logger->debug("Read the object from cache [$rootclassname][$id]");
				return $obj;
			}
		}
		
		$this->logger->debug("Read the object from database [$classname][$id]");
		$objs = $this->doGet($classname, $id);
		if ($objs !== null)
		{
			// save new read obj into cache and watcher
			$this->insertCache($objs[0], $objs[1], $rootclassname, $id, true);
			return $objs[1];
		}
		else
			throw new ObjectNotFoundException($id);
	}
	
	/**
	 * Close session and commit all database operations
	 * @return unknown_type
	 */
	public function close()
	{
		$this->dirtycheck();
		$this->flush();
		$this->db->close();
	}
	
	/**
	 * make a deattached object to attached
	 * save it into cache and watcher
	 * @param $obj
	 * @param $merge, true if wont do the id same check of cache attached object
	 * @return the $obj self
	 */
	public function update($obj, $merge=false)
	{
		$classname = $this->getObjClassname($obj);
		$id = $this->getObjId($obj);
		// there is same entity object in the cache with the same id
		if (!$merge && $this->inCache($classname, $id, false))
			throw new NonUniqueObjectException($classname, $id);
		
		$this->logger->debug("Update the detached object");
		// read it from database and save it into cache 
		$this->get($classname, $id);
		// override the watcher with actual $obj 
		$rootclassname = $this->getRootClassname($classname);
		$this->objwatcher[$rootclassname][$id] = $obj;
		
		return $obj;
	}
	
	/**
	 * Compare the objcache and objwatcher
	 * update the changes to database
	 */
	private function dirtycheck()
	{
		$this->logger->debug("Doing Dirty-Check");
		// warning: during the dirty check, if the size of objcache be changed
		// for example update a linked deattached entity, the new element will not be check here
		// so it must be checked at once in doDirtyCheck().
		foreach ($this->objcache as $rootclassname => $objcachemap)
			foreach ($objcachemap as $id => $objcache)
			{
				$this->logger->debug("Checking [$rootclassname][$id]");
				$this->doDirtyCheck($objcache, $this->objwatcher[$rootclassname][$id]);
			}
	}
	
	protected abstract function doDirtyCheck($objcache, $objwatcher);
	
	
	/**
	 * The real "get" function. Without the cache logic.
	 * @param $classname
	 * @param $id
	 * @return array of objects
	 * if not found return null
	 */
	protected abstract function doGet($classname, $id);
	
	/**
	 * The real "save" function. Without the cache logic.
	 * @param $obj
	 * @return auto generated id
	 */
	protected abstract function doSave($obj);
	
	/**
	 * Delete the object from database. Without the cache logic
	 * @param $obj
	 * @return the id of the object
	 */
	protected abstract function doDelete($obj);
	
	
	/**
	 * Get the root classname in the class hierarchy
	 * This will be used for the key of cache map, because the object id is always
	 * unique in whole class hierarchy
	 * @return null if not found 
	 */
	protected abstract function getRootClassname($classname);
	
	/**
	 * 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 abstract function getObjId($obj);
	
	/**
	 * Get the classname of the object, the object can be an EntityProxy or a real object
	 * @param $obj
	 */
	public function getObjClassname($obj)
	{
		if ($obj instanceof EntityProxy)
			return $obj->classname;
		else
			return get_class($obj);
	}
	
	
	
	/**
	 * Compare the collection of entities
	 * @param $cacheCollection, the elements with EntityProxy
	 * @param $watcherCollection, the elements with EntityProxy or normal object
	 * @return true if they are same
	 */
	protected function areSameEntityCollection($cacheCollection, $watcherCollection)
	{
		$count = count($cacheCollection);
		if ($count != count($watcherCollection))
			return false;
		for ($i=0; $i<$count; $i++)
			if (! $cacheCollection[$i]->equals($watcherCollection[$i]) )
				return false;
		return true;
	}
	
	
}
Return current item: FreeORM