summaryrefslogtreecommitdiff
path: root/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php
diff options
context:
space:
mode:
Diffstat (limited to 'framework/Data/ActiveRecord/TActiveRecordStateRegistry.php')
-rw-r--r--framework/Data/ActiveRecord/TActiveRecordStateRegistry.php262
1 files changed, 262 insertions, 0 deletions
diff --git a/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php b/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php
new file mode 100644
index 00000000..4493d4cb
--- /dev/null
+++ b/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php
@@ -0,0 +1,262 @@
+<?php
+/**
+ * TActiveRecordStateRegistry class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Id$
+ * @package System.Data.ActiveRecord
+ */
+
+/**
+ * Active record Unit of Work class and Identity Map.
+ *
+ * Maintains a list of objects affected by a business transaction and
+ * coordinates the writing out of changes and the resolution of concurrency problems.
+ *
+ * This registry keeps track of everything you do during a business transaction
+ * that can affect the database. When you're done, it figures out everything that
+ * needs to be done to alter the database as a result of your work.
+ *
+ * The object can only be in one of the four states: "new", "clean", "dirty" or "removed".
+ * A "new" object is one that is created not by loading the record from database.
+ * A "clean" object is one that is created by loading the record from datase.
+ * A "dirty" object is one that is marked as dirty or a "clean" object that has
+ * its internal state altered (done by using == object comparision).
+ * A "removed" object is one that is marked for deletion.
+ *
+ * See the "Active Record Object States.png" in the docs directory for state
+ * transition diagram.
+ *
+ * @author Wei Zhuo <weizho[at]gmail[dot]com>
+ * @version $Id$
+ * @package System.Data.ActiveRecord
+ * @since 3.1
+ */
+class TActiveRecordStateRegistry
+{
+ private $_cleanObjects=array();
+ private $_removedObjects;
+ private $_cachedObjects=array();
+ /**
+ * Initialize the registry.
+ */
+ public function __construct()
+ {
+ $this->_removedObjects = new TList;
+ }
+
+ /**
+ * Get the cached object for given type and row data. The cached object
+ * must also be clean.
+ * @param mixed row data fetched
+ * @return TActiveRecord cached object if found, null otherwise.
+ */
+ public function getCachedInstance($data)
+ {
+ $key = $this->getObjectDataKey($data);
+ if(isset($this->_cachedObjects[$key]))
+ {
+ $obj = $this->_cachedObjects[$key];
+ if($this->getIsCleanObject($obj))
+ return $obj;
+ }
+ }
+
+ /**
+ * Cache the object that corresponding to the fetched row data.
+ * @param mixed row data fetched.
+ * @param TActiveRecord object to be cached.
+ * @return TActiveRecord cached object.
+ */
+ public function addCachedInstance($data,$obj)
+ {
+ $key = $this->getObjectDataKey($data);
+ $this->registerClean($obj);
+ $this->_cachedObjects[$key]=$obj;
+ return $obj;
+ }
+
+ /**
+ * @return string hash of the data.
+ */
+ protected function getObjectDataKey($data)
+ {
+ return sprintf('%x',crc32(serialize($data)));
+ }
+
+ /**
+ * Ensure that object is not null.
+ */
+ protected function assertNotNull($obj)
+ {
+ if(is_null($obj))
+ throw new TActiveRecordException('ar_object_must_not_be_null');
+ }
+
+ /**
+ * Register the object for deletion, when the object invokes its delete() method
+ * the corresponding row in the database is deleted.
+ * @param TActiveRecord existing active record.
+ * @throws TActiveRecordException if object is null.
+ */
+ public function registerRemoved($obj)
+ {
+ $this->assertNotNull($obj);
+ $found=false;
+ foreach($this->_cleanObjects as $i=>$cache)
+ {
+ if($cache[0]===$obj)
+ {
+ unset($this->_cleanObjects[$i]);
+ $found=true;
+ }
+ }
+ if(!$found)
+ throw new TActiveRecordException('ar_object_must_be_retrieved_before_delete');
+ if(!$this->_removedObjects->contains($obj))
+ $this->_removedObjects->add($obj);
+ }
+
+ /**
+ * Register a clean object attached to a specific data that was used to
+ * populate the object. This acts as an object cache.
+ * @param TActiveRecord new clean object.
+ */
+ public function registerClean($obj)
+ {
+ $this->removeCleanOrDirty($obj);
+ if($this->getIsRemovedObject($obj))
+ throw new TActiveRecordException('ar_object_marked_for_removal');
+ $this->_cleanObjects[] = array($obj, clone($obj));
+ }
+
+ /**
+ * Remove the object from dirty state.
+ * @param TActiveRecord object to remove.
+ */
+ protected function removeDirty($obj)
+ {
+ $this->assertNotNull($obj);
+ foreach($this->_cleanObjects as $i=>$cache)
+ if($cache[0]===$obj && $obj != $cache[1])
+ unset($this->_cleanObjects[$i]);
+ }
+
+ /**
+ * Remove object from clean state.
+ * @param TActiveRecord object to remove.
+ */
+ protected function removeClean($obj)
+ {
+ $this->assertNotNull($obj);
+ foreach($this->_cleanObjects as $i=>$cache)
+ if($cache[0]===$obj && $obj == $cache[1])
+ unset($this->_cleanObjects[$i]);
+ }
+
+ /**
+ * Remove object from dirty and clean state.
+ * @param TActiveRecord object to remove.
+ */
+ protected function removeCleanOrDirty($obj)
+ {
+ $this->assertNotNull($obj);
+ foreach($this->_cleanObjects as $i=>$cache)
+ if($cache[0]===$obj)
+ unset($this->_cleanObjects[$i]);
+ }
+
+ /**
+ * Remove object from removed state.
+ * @param TActiveRecord object to remove.
+ */
+ protected function removeRemovedObject($obj)
+ {
+ $this->_removedObjects->remove($obj);
+ }
+
+ /**
+ * Test whether an object is dirty or has been modified.
+ * @param TActiveRecord object to test.
+ * @return boolean true if the object is dirty, false otherwise.
+ */
+ public function getIsDirtyObject($obj)
+ {
+ foreach($this->_cleanObjects as $cache)
+ if($cache[0] === $obj)
+ return $obj != $cache[1];
+ return false;
+ }
+
+ /**
+ * Test whether an object is in the clean state.
+ * @param TActiveRecord object to test.
+ * @return boolean true if object is clean, false otherwise.
+ */
+ public function getIsCleanObject($obj)
+ {
+ foreach($this->_cleanObjects as $cache)
+ if($cache[0] === $obj)
+ return $obj == $cache[1];
+ return false;
+ }
+
+ /**
+ * Test whether an object is a new instance.
+ * @param TActiveRecord object to test.
+ * @return boolean true if object is newly created, false otherwise.
+ */
+ public function getIsNewObject($obj)
+ {
+ if($this->getIsRemovedObject($obj)) return false;
+ foreach($this->_cleanObjects as $cache)
+ if($cache[0] === $obj)
+ return false;
+ return true;
+ }
+
+ /**
+ * Test whether an object is marked for deletion.
+ * @param TActiveRecord object to test.
+ * @return boolean true if object is marked for deletion, false otherwise.
+ */
+ public function getIsRemovedObject($obj)
+ {
+ return $this->_removedObjects->contains($obj);
+ }
+
+ /**
+ * Commit the object to database:
+ * * a new record is inserted if the object is new, object becomes clean.
+ * * the record is updated if the object is dirty, object becomes clean.
+ * * the record is deleted if the object is marked for removal.
+ *
+ * @param TActiveRecord record object.
+ * @param TActiveRecordGateway database gateway
+ * @return boolean true if commit was successful, false otherwise.
+ */
+ public function commit($record,$gateway)
+ {
+ $rowsAffected=0;
+
+ if($this->getIsRemovedObject($record))
+ {
+ $rowsAffected = $gateway->delete($record);
+ if($rowsAffected===1)
+ $this->removeRemovedObject($record);
+ }
+ else
+ {
+ if($this->getIsDirtyObject($record))
+ $rowsAffected = $gateway->update($record);
+ else if($this->getIsNewObject($record))
+ $rowsAffected = $gateway->insert($record);
+
+ if($rowsAffected===1)
+ $this->registerClean($record);
+ }
+ return $rowsAffected===1;
+ }
+}
+
+?> \ No newline at end of file