diff --git a/Doctrine/Access.php b/Doctrine/Access.php
new file mode 100644
index 000000000..bf59f423c
--- /dev/null
+++ b/Doctrine/Access.php
@@ -0,0 +1,82 @@
+ value pairs
+ */
+ public function setArray(array $array) {
+ foreach($array as $k=>$v):
+ $this->set($k,$v);
+ endforeach;
+ }
+ /**
+ * __set -- an alias of set()
+ * @see set, offsetSet
+ * @param $name
+ * @param $value
+ */
+ public function __set($name,$value) {
+ $this->set($name,$value);
+ }
+ /**
+ * __get -- an alias of get()
+ * @see get, offsetGet
+ * @param mixed $name
+ * @return mixed
+ */
+ public function __get($name) {
+ return $this->get($name);
+ }
+ /**
+ * @param mixed $offset
+ * @return boolean -- whether or not the data has a field $offset
+ */
+ public function offsetExists($offset) {
+ return (bool) isset($this->data[$offset]);
+ }
+ /**
+ * offsetGet -- an alias of get()
+ * @see get, __get
+ * @param mixed $offset
+ * @return mixed
+ */
+ public function offsetGet($offset) {
+ return $this->get($offset);
+ }
+ /**
+ * sets $offset to $value
+ * @see set, __set
+ * @param mixed $offset
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($offset, $value) {
+ if( ! isset($offset)) {
+ $this->add($value);
+ } else
+ $this->set($offset,$value);
+ }
+ /**
+ * unset a given offset
+ * @see set, offsetSet, __set
+ * @param mixed $offset
+ */
+ public function offsetUnset($offset) {
+ if($this instanceof Doctrine_Collection) {
+ return $this->remove($offset);
+ } else {
+ $this->set($offset,null);
+ }
+ }
+}
+?>
diff --git a/Doctrine/Association.php b/Doctrine/Association.php
new file mode 100644
index 000000000..567535a40
--- /dev/null
+++ b/Doctrine/Association.php
@@ -0,0 +1,38 @@
+associationTable = $associationTable;
+ }
+ /**
+ * @return Doctrine_Table
+ */
+ public function getAssociationFactory() {
+ return $this->associationTable;
+ }
+}
+?>
diff --git a/Doctrine/Cache.php b/Doctrine/Cache.php
new file mode 100644
index 000000000..d11d8626c
--- /dev/null
+++ b/Doctrine/Cache.php
@@ -0,0 +1,71 @@
+
diff --git a/Doctrine/Collection.php b/Doctrine/Collection.php
new file mode 100644
index 000000000..838d35a5f
--- /dev/null
+++ b/Doctrine/Collection.php
@@ -0,0 +1,446 @@
+table = $table;
+
+ $name = $table->getAttribute(Doctrine::ATTR_COLL_KEY);
+ if($name !== null) {
+ $this->generator = new Doctrine_IndexGenerator($name);
+ }
+ }
+ /**
+ * initNullObject
+ */
+ public static function initNullObject(Doctrine_Null $null) {
+ self::$null = $null;
+ }
+ /**
+ * @return object Doctrine_Table
+ */
+ public function getTable() {
+ return $this->table;
+ }
+ /**
+ * whether or not an offset batch has been expanded
+ * @return boolean
+ */
+ public function isExpanded($offset) {
+ return isset($this->expanded[$offset]);
+ }
+ /**
+ * whether or not this collection is expandable
+ * @return boolean
+ */
+ public function isExpandable() {
+ return $this->expandable;
+ }
+ /**
+ * @param Doctrine_IndexGenerator $generator
+ * @return void
+ */
+ public function setGenerator($generator) {
+ $this->generator = $generator;
+ }
+ /**
+ * @return Doctrine_IndexGenerator
+ */
+ public function getGenerator() {
+ return $this->generator;
+ }
+ /**
+ * @return array
+ */
+ public function getData() {
+ return $this->data;
+ }
+ /**
+ * @param array $data
+ */
+ public function addData(array $data) {
+ $this->data[] = $data;
+ }
+ /**
+ * @return mixed
+ */
+ public function getLast() {
+ return end($this->data);
+ }
+ /**
+ * @return void
+ */
+ public function setReference(Doctrine_Record $record,Doctrine_Relation $relation) {
+ $this->reference = $record;
+ $this->relation = $relation;
+
+ if($relation instanceof Doctrine_ForeignKey ||
+ $relation instanceof Doctrine_LocalKey) {
+
+ $this->reference_field = $relation->getForeign();
+
+ $value = $record->get($relation->getLocal());
+
+ foreach($this->getNormalIterator() as $record) {
+ if($value !== null) {
+ $record->rawSet($this->reference_field, $value);
+ } else {
+ $record->rawSet($this->reference_field, $this->reference);
+ }
+ }
+ }
+ }
+ /**
+ * @return mixed
+ */
+ public function getReference() {
+ return $this->reference;
+ }
+ /**
+ * @return boolean
+ */
+ public function expand($key) {
+ $where = array();
+ $params = array();
+ $limit = null;
+ $offset = null;
+
+ switch(get_class($this)):
+ case "Doctrine_Collection_Offset":
+ $limit = $this->getLimit();
+ $offset = (floor($key / $limit) * $limit);
+
+ if( ! $this->expandable && isset($this->expanded[$offset]))
+ return false;
+
+ $fields = implode(", ",$this->table->getColumnNames());
+ break;
+ default:
+ if( ! $this->expandable)
+ return false;
+
+ if( ! isset($this->reference))
+ return false;
+
+ $id = $this->reference->getID();
+
+ if(empty($id))
+ return false;
+
+ switch(get_class($this)):
+ case "Doctrine_Collection_Immediate":
+ $fields = implode(", ",$this->table->getColumnNames());
+ break;
+ default:
+ $fields = implode(", ",$this->table->getPrimaryKeys());
+ endswitch;
+
+
+ endswitch;
+
+ if(isset($this->relation)) {
+ if($this->relation instanceof Doctrine_ForeignKey) {
+ $params = array($this->reference->getID());
+ $where[] = $this->reference_field." = ?";
+
+ if( ! isset($offset)) {
+ $ids = $this->getPrimaryKeys();
+
+ if( ! empty($ids)) {
+ $where[] = $this->table->getIdentifier()." NOT IN (".substr(str_repeat("?, ",count($ids)),0,-2).")";
+ $params = array_merge($params,$ids);
+ }
+
+ $this->expandable = false;
+ }
+
+
+ } elseif($this->relation instanceof Doctrine_Association) {
+
+ $asf = $fk->getAssociationFactory();
+ $query = "SELECT ".$foreign." FROM ".$asf->getTableName()." WHERE ".$local."=".$this->getID();
+
+ $table = $fk->getTable();
+ $graph = new Doctrine_DQL_Parser($table->getSession());
+
+ $q = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$table->getIdentifier()." IN ($query)";
+
+ }
+ }
+
+ $query = "SELECT ".$fields." FROM ".$this->table->getTableName();
+
+ // apply column aggregation inheritance
+ foreach($this->table->getInheritanceMap() as $k => $v) {
+ $where[] = $k." = ?";
+ $params[] = $v;
+ }
+ if( ! empty($where)) {
+ $query .= " WHERE ".implode(" AND ",$where);
+ }
+
+ $params = array_merge($params, array_values($this->table->getInheritanceMap()));
+
+ $coll = $this->table->execute($query, $params, $limit, $offset);
+
+ if( ! isset($offset)) {
+ foreach($coll as $record) {
+ if(isset($this->reference_field))
+ $record->rawSet($this->reference_field,$this->reference);
+
+ $this->reference->addReference($record);
+ }
+ } else {
+ $i = $offset;
+
+ foreach($coll as $record) {
+ if(isset($this->reference)) {
+ $this->reference->addReference($record,$i);
+ } else
+ $this->data[$i] = $record;
+
+ $i++;
+ }
+
+ $this->expanded[$offset] = true;
+
+ // check if the fetched collection's record count is smaller
+ // than the query limit, if so this collection has been expanded to its max size
+
+ if(count($coll) < $limit) {
+ $this->expandable = false;
+ }
+ }
+
+ return $coll;
+ }
+ /**
+ * @return boolean
+ */
+ public function remove($key) {
+ if( ! isset($this->data[$key]))
+ throw new InvalidKeyException();
+
+ $removed = $this->data[$key];
+
+ unset($this->data[$key]);
+ return $removed;
+ }
+ /**
+ * @param mixed $key
+ * @return boolean
+ */
+ public function contains($key) {
+ return isset($this->data[$key]);
+ }
+ /**
+ * @param mixed $key
+ * @return object Doctrine_Record return a specified record
+ */
+ public function get($key) {
+ if( ! isset($this->data[$key])) {
+ $this->expand($key);
+
+ if( ! isset($this->data[$key]))
+ $this->data[$key] = $this->table->create();
+
+ if(isset($this->reference_field)) {
+ $value = $this->reference->get($this->relation->getLocal());
+
+ if($value !== null) {
+ $this->data[$key]->rawSet($this->reference_field, $value);
+ } else {
+ $this->data[$key]->rawSet($this->reference_field, $this->reference);
+ }
+ }
+ }
+
+ return $this->data[$key];
+ }
+
+ /**
+ * @return array an array containing all primary keys
+ */
+ public function getPrimaryKeys() {
+ $list = array();
+ $name = $this->table->getIdentifier();
+
+ foreach($this->data as $record):
+ if(is_array($record) && isset($record[$name])) {
+ $list[] = $record[$name];
+ } else {
+ $list[] = $record->getID();
+ }
+ endforeach;
+ return $list;
+ }
+ /**
+ * returns all keys
+ * @return array
+ */
+ public function getKeys() {
+ return array_keys($this->data);
+ }
+ /**
+ * count
+ * this class implements interface countable
+ * @return integer number of records in this collection
+ */
+ public function count() {
+ return count($this->data);
+ }
+ /**
+ * set
+ * @param integer $key
+ * @param Doctrine_Record $record
+ * @return void
+ */
+ public function set($key,Doctrine_Record $record) {
+ if(isset($this->reference_field))
+ $record->rawSet($this->reference_field,$this->reference);
+
+ $this->data[$key] = $record;
+ }
+
+ /**
+ * adds a record to collection
+ * @param Doctrine_Record $record record to be added
+ * @param string $key optional key for the record
+ * @return boolean
+ */
+ public function add(Doctrine_Record $record,$key = null) {
+ if(isset($this->reference_field))
+ $record->rawSet($this->reference_field,$this->reference);
+
+ if(isset($key)) {
+ if(isset($this->data[$key]))
+ return false;
+
+ $this->data[$key] = $record;
+ return true;
+ }
+
+ if(isset($this->generator)) {
+ $key = $this->generator->getIndex($record);
+ $this->data[$key] = $record;
+ } else
+ $this->data[] = $record;
+
+ return true;
+ }
+ /**
+ * @param Doctrine_Query $query
+ * @param integer $key
+ */
+ public function populate(Doctrine_Query $query) {
+ $name = $this->table->getComponentName();
+
+ if($this instanceof Doctrine_Collection_Immediate ||
+ $this instanceof Doctrine_Collection_Offset) {
+
+ $data = $query->getData($name);
+ if(is_array($data)) {
+ foreach($data as $k=>$v):
+ $this->table->setData($v);
+ $this->add($this->table->getRecord());
+ endforeach;
+ }
+ } elseif($this instanceof Doctrine_Collection_Batch) {
+ $this->data = $query->getData($name);
+
+ if(isset($this->generator)) {
+ foreach($this->data as $k => $v) {
+ $record = $this->get($k);
+ $i = $this->generator->getIndex($record);
+ $this->data[$i] = $record;
+ unset($this->data[$k]);
+ }
+ }
+ }
+ }
+ /**
+ * @return Doctrine_Iterator_Normal
+ */
+ public function getNormalIterator() {
+ return new Doctrine_Iterator_Normal($this);
+ }
+ /**
+ * save
+ * saves all records
+ */
+ public function save() {
+ $this->table->getSession()->saveCollection($this);
+ }
+ /**
+ * single shot delete
+ * deletes all records from this collection
+ * uses only one database query to perform this operation
+ * @return boolean
+ */
+ public function delete() {
+ $ids = $this->table->getSession()->deleteCollection($this);
+ $this->data = array();
+ }
+ /**
+ * getIterator
+ * @return object ArrayIterator
+ */
+ public function getIterator() {
+ $data = $this->data;
+ return new ArrayIterator($data);
+ }
+ /**
+ * returns a string representation of this object
+ */
+ public function __toString() {
+ return Doctrine_Lib::getCollectionAsString($this);
+ }
+}
+?>
diff --git a/Doctrine/Configurable.php b/Doctrine/Configurable.php
new file mode 100644
index 000000000..7cd5a393c
--- /dev/null
+++ b/Doctrine/Configurable.php
@@ -0,0 +1,174 @@
+ 1)
+ throw new Doctrine_Exception("Cache slam defense should be a floating point number between 0 and 1");
+ break;
+ case Doctrine::ATTR_FETCHMODE:
+ if($value < 0)
+ throw new Doctrine_Exception("Unknown fetchmode. See Doctrine::FETCH_* constants.");
+ break;
+ case Doctrine::ATTR_LISTENER:
+ $this->setEventListener($value);
+ break;
+ case Doctrine::ATTR_PK_COLUMNS:
+ if( ! is_array($value))
+ throw new Doctrine_Exception("The value of Doctrine::ATTR_PK_COLUMNS attribute must be an array");
+ break;
+ case Doctrine::ATTR_PK_TYPE:
+ if($value != Doctrine::INCREMENT_KEY && $value != Doctrine::UNIQUE_KEY)
+ throw new Doctrine_Exception("The value of Doctrine::ATTR_PK_TYPE attribute must be either Doctrine::INCREMENT_KEY or Doctrine::UNIQUE_KEY");
+
+ break;
+ case Doctrine::ATTR_LOCKMODE:
+ if($this instanceof Doctrine_Session) {
+ if($this->getState() != Doctrine_Session::STATE_OPEN)
+ throw new Doctrine_Exception("Couldn't set lockmode. There are transactions open.");
+
+ } elseif($this instanceof Doctrine_Manager) {
+ foreach($this as $session) {
+ if($session->getState() != Doctrine_Session::STATE_OPEN)
+ throw new Doctrine_Exception("Couldn't set lockmode. There are transactions open.");
+ }
+ } else {
+ throw new Doctrine_Exception("Lockmode attribute can only be set at the global or session level.");
+ }
+ break;
+ case Doctrine::ATTR_CREATE_TABLES:
+ $value = (bool) $value;
+ break;
+ case Doctrine::ATTR_COLL_LIMIT:
+ if($value < 1) {
+ throw new Doctrine_Exception("Collection limit should be a value greater than or equal to 1.");
+ }
+ break;
+ case Doctrine::ATTR_COLL_KEY:
+ if( ! ($this instanceof Doctrine_Table))
+ throw new Doctrine_Exception("This attribute can only be set at table level.");
+
+ if( ! $this->hasColumn($value))
+ throw new Doctrine_Exception("Couldn't set collection key attribute. No such column '$value'");
+
+
+ break;
+ case Doctrine::ATTR_VLD:
+
+ break;
+ case Doctrine::ATTR_CACHE:
+ if($value != Doctrine::CACHE_SQLITE && $value != Doctrine::CACHE_NONE)
+ throw new Doctrine_Exception("Unknown cache container. See Doctrine::CACHE_* constants for availible containers.");
+ break;
+ default:
+ throw new Doctrine_Exception("Unknown attribute.");
+ endswitch;
+
+ $this->attributes[$attribute] = $value;
+
+ }
+ /**
+ * @param Doctrine_EventListener $listener
+ * @return void
+ */
+ final public function setEventListener(Doctrine_EventListener $listener) {
+ $i = Doctrine::ATTR_LISTENER;
+ $this->attributes[$i] = $listener;
+ }
+ /**
+ * returns the value of an attribute
+ *
+ * @param integer $attribute
+ * @return mixed
+ */
+ final public function getAttribute($attribute) {
+ $attribute = (int) $attribute;
+
+ if($attribute < 1 || $attribute > 16)
+ throw new InvalidKeyException();
+
+ if( ! isset($this->attributes[$attribute])) {
+ if(isset($this->parent))
+ return $this->parent->getAttribute($attribute);
+
+ return null;
+ }
+ return $this->attributes[$attribute];
+ }
+ /**
+ * getAttributes
+ * returns all attributes as an array
+ *
+ * @return array
+ */
+ final public function getAttributes() {
+ return $this->attributes;
+ }
+ /**
+ * sets a parent for this configurable component
+ * the parent must be configurable component itself
+ *
+ * @param Doctrine_Configurable $component
+ * @return void
+ */
+ final public function setParent(Doctrine_Configurable $component) {
+ $this->parent = $component;
+ }
+ /**
+ * getParent
+ * returns the parent of this component
+ *
+ * @return Doctrine_Configurable
+ */
+ final public function getParent() {
+ return $this->parent;
+ }
+}
+?>
diff --git a/Doctrine/DB.php b/Doctrine/DB.php
new file mode 100644
index 000000000..ab82c3876
--- /dev/null
+++ b/Doctrine/DB.php
@@ -0,0 +1,140 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array("Doctrine_DBStatement",array($this)));
+ }
+
+
+ public static function getConn($dsn,$username = null, $password = null) {
+ static $instance;
+
+ if( ! isset($instance)) {
+ $instance = new Doctrine_DB($dsn,$username,$password);
+ }
+ return $instance;
+ }
+
+ /**
+ * @param string $dsn PEAR::DB like DSN
+ * format: schema://user:password@address/dbname
+ */
+ public static function getConnection($dsn = null) {
+ static $instance = array();
+ $md5 = md5($dsn);
+
+ if( ! isset($instance[$md5])) {
+ if( ! isset($dsn)) {
+ $a = parse_url(self::DSN);
+ } else {
+ $a = parse_url($dsn);
+ }
+ $e = array();
+
+ $e[0] = $a["scheme"].":host=".$a["host"].";dbname=".substr($a["path"],1);
+ $e[1] = $a["user"];
+ $e[2] = $a["pass"];
+
+ $instance[$md5] = new Doctrine_DB($e[0],$e[1],$e[2]);
+ }
+ return $instance[$md5];
+ }
+ /**
+ * @param string $query query to be executed
+ */
+ public function query($query) {
+ try {
+ $this->queries[] = $query;
+ $time = microtime();
+
+ $stmt = parent::query($query);
+
+ $this->exectimes[] = (microtime() - $time);
+ return $stmt;
+ } catch(PDOException $e) {
+ throw $e;
+ }
+ }
+ /**
+ * @param string $query query to be prepared
+ */
+ public function prepare($query) {
+ $this->queries[] = $query;
+ return parent::prepare($query);
+ }
+ /**
+ * @param string $time exectime of the last executed query
+ * @return void
+ */
+ public function addExecTime($time) {
+ $this->exectimes[] = $time;
+ }
+
+ public function getExecTimes() {
+ return $this->exectimes;
+ }
+ /**
+ * @return array an array of executed queries
+ */
+ public function getQueries() {
+ return $this->queries;
+ }
+ /**
+ * @return ArrayIterator
+ */
+ public function getIterator() {
+ return new ArrayIterator($this->queries);
+ }
+ /**
+ * returns the number of executed queries
+ * @return integer
+ */
+ public function count() {
+ return count($this->queries);
+ }
+
+}
+class Doctrine_DBStatement extends PDOStatement {
+ /**
+ * @param Doctrine_DB $dbh Doctrine Database Handler
+ */
+ private $dbh;
+ /**
+ * @param Doctrine_DB $dbh
+ */
+ private function __construct(Doctrine_DB $dbh) {
+ $this->dbh = $dbh;
+ }
+ /**
+ * @param array $params
+ */
+ public function execute(array $params = null) {
+ $time = microtime();
+ $result = parent::execute($params);
+
+ $exectime = (microtime() - $time);
+ $this->dbh->addExecTime($exectime);
+ return $result;
+ }
+}
+?>
diff --git a/Doctrine/DataDict.php b/Doctrine/DataDict.php
new file mode 100644
index 000000000..f7d5f773b
--- /dev/null
+++ b/Doctrine/DataDict.php
@@ -0,0 +1,101 @@
+getRoot()."/adodb-hack/adodb.inc.php");
+
+ $this->dbh = $dbh;
+ $this->dict = NewDataDictionary($dbh);
+ }
+
+ public function metaColumns(Doctrine_Table $table) {
+ return $this->dict->metaColumns($table->getTableName());
+ }
+
+
+ public function createTable($tablename, $columns) {
+ foreach($columns as $name => $args) {
+ $r[] = $name." ".$this->getADOType($args[0],$args[1])." ".str_replace("|"," ",$args[2]);
+ }
+
+
+ $r = implode(", ",$r);
+ $a = $this->dict->createTableSQL($tablename,$r);
+
+ $return = true;
+ foreach($a as $sql) {
+ try {
+ $this->dbh->query($sql);
+ } catch(PDOException $e) {
+ if($this->dbh->getAttribute(PDO::ATTR_DRIVER_NAME) == "sqlite")
+ throw $e;
+ $return = false;
+ }
+ }
+
+ return $return;
+ }
+ /**
+ * converts doctrine type to adodb type
+ *
+ * @param string $type column type
+ * @param integer $length column length
+ */
+ public function getADOType($type,$length) {
+ switch($type):
+ case "string":
+ case "s":
+ if($length < 255)
+ return "C($length)";
+ elseif($length < 4000)
+ return "X";
+ else
+ return "X2";
+ break;
+ case "mbstring":
+ if($length < 255)
+ return "C2($length)";
+
+ return "X2";
+ case "clob":
+ return "XL";
+ break;
+ case "d":
+ case "date":
+ return "D";
+ break;
+ case "float":
+ case "f":
+ case "double":
+ return "F";
+ break;
+ case "timestamp":
+ case "t":
+ return "T";
+ break;
+ case "boolean":
+ case "bool":
+ return "L";
+ break;
+ case "integer":
+ case "int":
+ case "i":
+ if(empty($length))
+ return "I8";
+ elseif($length < 4)
+ return "I1";
+ elseif($length < 6)
+ return "I2";
+ elseif($length < 10)
+ return "I4";
+ elseif($length <= 20)
+ return "I8";
+ else
+ throw new Doctrine_Exception("Too long integer (max length is 20).");
+
+ break;
+ endswitch;
+ }
+}
+?>
diff --git a/Doctrine/Debugger.php b/Doctrine/Debugger.php
new file mode 100644
index 000000000..dc81c8c96
--- /dev/null
+++ b/Doctrine/Debugger.php
@@ -0,0 +1,151 @@
+object = $object;
+ $this->code = $code;
+ }
+ final public function getCode() {
+ return $this->code;
+ }
+ final public function getObject() {
+ return $this->object;
+ }
+}
+class Doctrine_Debugger extends Doctrine_EventListener {
+ const EVENT_LOAD = 1;
+ const EVENT_PRELOAD = 2;
+ const EVENT_SLEEP = 3;
+ const EVENT_WAKEUP = 4;
+ const EVENT_UPDATE = 5;
+ const EVENT_PREUPDATE = 6;
+ const EVENT_CREATE = 7;
+ const EVENT_PRECREATE = 8;
+
+ const EVENT_SAVE = 9;
+ const EVENT_PRESAVE = 10;
+ const EVENT_INSERT = 11;
+ const EVENT_PREINSERT = 12;
+ const EVENT_DELETE = 13;
+ const EVENT_PREDELETE = 14;
+ const EVENT_EVICT = 15;
+ const EVENT_PREEVICT = 16;
+ const EVENT_CLOSE = 17;
+ const EVENT_PRECLOSE = 18;
+
+ const EVENT_OPEN = 19;
+ const EVENT_COMMIT = 20;
+ const EVENT_PRECOMMIT = 21;
+ const EVENT_ROLLBACK = 22;
+ const EVENT_PREROLLBACK = 23;
+ const EVENT_BEGIN = 24;
+ const EVENT_PREBEGIN = 25;
+ const EVENT_COLLDELETE = 26;
+ const EVENT_PRECOLLDELETE = 27;
+ private $debug;
+
+ public function getMessages() {
+ return $this->debug;
+ }
+
+
+ public function onLoad(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_LOAD);
+ }
+ public function onPreLoad(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PRELOAD);
+ }
+
+ public function onSleep(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_SLEEP);
+ }
+
+ public function onWakeUp(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_WAKEUP);
+ }
+
+ public function onUpdate(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_UPDATE);
+ }
+ public function onPreUpdate(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREUPDATE);
+ }
+
+ public function onCreate(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_CREATE);
+ }
+ public function onPreCreate(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PRECREATE);
+ }
+
+ public function onSave(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_SAVE);
+ }
+ public function onPreSave(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PRESAVE);
+ }
+
+ public function onInsert(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_INSERT);
+ }
+ public function onPreInsert(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREINSERT);
+ }
+
+ public function onDelete(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_DELETE);
+ }
+ public function onPreDelete(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREDELETE);
+ }
+
+ public function onEvict(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_EVICT);
+ }
+ public function onPreEvict(Doctrine_Record $record) {
+ $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREEVICT);
+ }
+
+ public function onClose(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_CLOSE);
+ }
+ public function onPreClose(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PRECLOSE);
+ }
+
+ public function onOpen(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_OPEN);
+ }
+
+ public function onTransactionCommit(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_COMMIT);
+ }
+ public function onPreTransactionCommit(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PRECOMMIT);
+ }
+
+ public function onTransactionRollback(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_ROLLBACK);
+ }
+ public function onPreTransactionRollback(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PREROLLBACK);
+ }
+
+ public function onTransactionBegin(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_BEGIN);
+ }
+ public function onPreTransactionBegin(Doctrine_Session $session) {
+ $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PREBEGIN);
+ }
+
+ public function onCollectionDelete(Doctrine_Collection $collection) {
+ $this->debug[] = new Doctrine_DebugMessage($collection,self::EVENT_COLLDELETE);
+ }
+ public function onPreCollectionDelete(Doctrine_Collection $collection) {
+ $this->debug[] = new Doctrine_DebugMessage($collection,self::EVENT_PRECOLLDELETE);
+ }
+}
+?>
diff --git a/Doctrine/EventListener.php b/Doctrine/EventListener.php
new file mode 100644
index 000000000..24bb1ecd1
--- /dev/null
+++ b/Doctrine/EventListener.php
@@ -0,0 +1,124 @@
+
diff --git a/Doctrine/Exception.php b/Doctrine/Exception.php
new file mode 100644
index 000000000..7e8461591
--- /dev/null
+++ b/Doctrine/Exception.php
@@ -0,0 +1,6 @@
+
diff --git a/Doctrine/ForeignKey.php b/Doctrine/ForeignKey.php
new file mode 100644
index 000000000..701d93885
--- /dev/null
+++ b/Doctrine/ForeignKey.php
@@ -0,0 +1,7 @@
+
diff --git a/Doctrine/Form.class.php b/Doctrine/Form.class.php
new file mode 100644
index 000000000..4a4befdda
--- /dev/null
+++ b/Doctrine/Form.class.php
@@ -0,0 +1,77 @@
+record = $record;
+ $this->columns = $record->getTable()->getColumns();
+ $this->keys = array_keys($this->columns);
+ $this->index = 0;
+ $this->count = count($this->keys);
+ }
+ public function current() {
+ $i = $this->index;
+ $column = $this->keys[$i];
+
+ $definitions = $this->columns[$column];
+
+ $e = explode("|",$definitions[2]);
+ $enum = false;
+ foreach($e as $v) {
+ $e2 = explode(":",$v);
+ if($e2[0] == "enum") {
+ $enum = explode("-",$e2[1]);
+ break;
+ }
+ }
+ $length = $definitions[1];
+ if( ! in_array("autoincrement",$e) && ! in_array("protected",$e)) {
+ if($enum) {
+ $elements[$column] = "\n";
+ } else {
+ if($length <= 255) {
+ $elements[$column] = "\n";
+ } else {
+ $elements[$column] = "\n";
+ }
+ }
+ return $elements[$column];
+ } else {
+ $this->index++;
+
+ if($this->index < $this->count)
+ return self::current();
+ }
+ }
+ public function key() {
+ $i = $this->index;
+ return $this->keys[$i];
+ }
+ public function next() {
+ $this->index++;
+ }
+ public function rewind() {
+ $this->index = 0;
+ }
+ public function valid() {
+ if($this->index >= $this->count)
+ return false;
+
+ return true;
+ }
+}
+?>
diff --git a/Doctrine/Identifier.php b/Doctrine/Identifier.php
new file mode 100644
index 000000000..9bcc3a20b
--- /dev/null
+++ b/Doctrine/Identifier.php
@@ -0,0 +1,24 @@
+
diff --git a/Doctrine/IdentityMap.class.php b/Doctrine/IdentityMap.class.php
new file mode 100644
index 000000000..4d0e15eed
--- /dev/null
+++ b/Doctrine/IdentityMap.class.php
@@ -0,0 +1,51 @@
+_table = $_table;
+ }
+
+
+ abstract public function get();
+}
+class Doctrine_IdentityMap {
+ private $identityMap = array();
+
+ /**
+ * first checks if record exists in identityMap, if not
+ * returns a new record
+ *
+ * @return Doctrine_Record
+ */
+ public function get() {
+ $key = $this->getIdentifier();
+
+ if( ! is_array($key))
+ $key = array($key);
+
+
+ foreach($key as $k) {
+ if( ! isset($this->data[$k]))
+ throw new Doctrine_Exception("No primary key found");
+
+ $id[] = $this->data[$k];
+ }
+ $id = implode(' ', $id);
+
+ if(isset($this->identityMap[$id]))
+ $record = $this->identityMap[$id];
+ else {
+ $record = new $this->name($this);
+ $this->identityMap[$id] = $record;
+ }
+ $this->data = array();
+
+ return $record;
+ }
+}
+?>
diff --git a/Doctrine/IndexGenerator.php b/Doctrine/IndexGenerator.php
new file mode 100644
index 000000000..01b1bcd9d
--- /dev/null
+++ b/Doctrine/IndexGenerator.php
@@ -0,0 +1,25 @@
+name = $name;
+ }
+ /**
+ * @param Doctrine_Record $record
+ * @return mixed
+ */
+ public function getIndex(Doctrine_Record $record) {
+ $value = $record->get($this->name);
+ if($value === null)
+ throw new Doctrine_Exception("Couldn't create collection index. Record field '".$this->name."' was null.");
+
+ return $value;
+ }
+}
+?>
diff --git a/Doctrine/Iterator.php b/Doctrine/Iterator.php
new file mode 100644
index 000000000..61246fa3f
--- /dev/null
+++ b/Doctrine/Iterator.php
@@ -0,0 +1,83 @@
+collection = $collection;
+ $this->keys = $this->collection->getKeys();
+ $this->count = $this->collection->count();
+ }
+ /**
+ * rewinds the iterator
+ *
+ * @return void
+ */
+ public function rewind() {
+ $this->index = 0;
+ $i = $this->index;
+ if(isset($this->keys[$i]))
+ $this->key = $this->keys[$i];
+ }
+
+ /**
+ * returns the current key
+ *
+ * @return integer
+ */
+ public function key() {
+ return $this->key;
+ }
+ /**
+ * returns the current record
+ *
+ * @return Doctrine_Record
+ */
+ public function current() {
+ return $this->collection->get($this->key);
+ }
+ /**
+ * advances the internal pointer
+ *
+ * @return void
+ */
+ public function next() {
+ $this->index++;
+ $i = $this->index;
+ if(isset($this->keys[$i]))
+ $this->key = $this->keys[$i];
+ }
+}
+
+
+?>
diff --git a/Doctrine/Lib.php b/Doctrine/Lib.php
new file mode 100644
index 000000000..e8a9ba91b
--- /dev/null
+++ b/Doctrine/Lib.php
@@ -0,0 +1,140 @@
+";
+ $r[] = "Component : ".$record->getTable()->getComponentName();
+ $r[] = "ID : ".$record->getID();
+ $r[] = "References : ".count($record->getReferences());
+ $r[] = "State : ".Doctrine_Lib::getRecordStateAsString($record->getState());
+ $r[] = "OID : ".$record->getOID();
+ $r[] = "";
+ return implode("\n",$r)."
";
+ }
+ /**
+ * getStateAsString
+ * returns a given session state as string
+ * @param integer $state session state
+ */
+ public static function getSessionStateAsString($state) {
+ switch($state):
+ case Doctrine_Session::STATE_OPEN:
+ return "open";
+ break;
+ case Doctrine_Session::STATE_CLOSED:
+ return "closed";
+ break;
+ case Doctrine_Session::STATE_BUSY:
+ return "busy";
+ break;
+ case Doctrine_Session::STATE_ACTIVE:
+ return "active";
+ break;
+ endswitch;
+ }
+ /**
+ * returns a string representation of Doctrine_Session object
+ * @param Doctrine_Session $session
+ * @return string
+ */
+ public function getSessionAsString(Doctrine_Session $session) {
+ $r[] = "
"; + $r[] = "Doctrine_Session object"; + $r[] = "State : ".Doctrine_Lib::getSessionStateAsString($session->getState()); + $r[] = "Open Transactions : ".$session->getTransactionLevel(); + $r[] = "Open Factories : ".$session->count(); + $sum = 0; + $rsum = 0; + $csum = 0; + foreach($session->getTables() as $objTable) { + if($objTable->getCache() instanceof Doctrine_Cache_File) { + $sum += array_sum($objTable->getCache()->getStats()); + $rsum += $objTable->getRepository()->count(); + $csum += $objTable->getCache()->count(); + } + } + $r[] = "Cache Hits : ".$sum." hits "; + $r[] = "Cache : ".$csum." objects "; + + $r[] = "Repositories : ".$rsum." objects "; + $queries = false; + if($session->getDBH() instanceof Doctrine_DB) { + $handler = "Doctrine Database Handler"; + $queries = count($session->getDBH()->getQueries()); + $sum = array_sum($session->getDBH()->getExecTimes()); + } elseif($session->getDBH() instanceof PDO) { + $handler = "PHP Native PDO Driver"; + } else + $handler = "Unknown Database Handler"; + + $r[] = "DB Handler : ".$handler; + if($queries) { + $r[] = "Executed Queries : ".$queries; + $r[] = "Sum of Exec Times : ".$sum; + } + + $r[] = ""; + return implode("\n",$r)."
"; + $r[] = "Component : ".$this->getComponentName(); + $r[] = "Table : ".$this->getTableName(); + $r[] = "Repository : ".$this->getRepository()->count()." objects"; + if($table->getCache() instanceof Doctrine_Cache_File) { + $r[] = "Cache : ".$this->getCache()->count()." objects"; + $r[] = "Cache hits : ".array_sum($this->getCache()->getStats())." hits"; + } + $r[] = ""; + return implode("\n",$r)."
"; + $r[] = get_class($collection); + + foreach($collection as $key => $record) { + $r[] = "Key : ".$key." ID : ".$record->getID(); + } + $r[] = ""; + return implode("\n",$r); + } +} +?> diff --git a/Doctrine/LocalKey.php b/Doctrine/LocalKey.php new file mode 100644 index 000000000..dc43566a8 --- /dev/null +++ b/Doctrine/LocalKey.php @@ -0,0 +1,7 @@ + diff --git a/Doctrine/Manager.php b/Doctrine/Manager.php new file mode 100644 index 000000000..09c5e50ad --- /dev/null +++ b/Doctrine/Manager.php @@ -0,0 +1,249 @@ +root = dirname(__FILE__); + $this->null = new Doctrine_Null; + + Doctrine_Record::initNullObject($this->null); + Doctrine_Collection::initNullObject($this->null); + } + /** + * @return Doctrine_Null + */ + final public function getNullObject() { + return $this->null; + } + /** + * setDefaultAttributes + * sets default attributes + * + * @return boolean + */ + final public function setDefaultAttributes() { + static $init = false; + if( ! $init) { + $init = true; + $attributes = array( + Doctrine::ATTR_CACHE_DIR => "%ROOT%".DIRECTORY_SEPARATOR."cachedir", + Doctrine::ATTR_FETCHMODE => Doctrine::FETCH_LAZY, + Doctrine::ATTR_CACHE_TTL => 100, + Doctrine::ATTR_CACHE_SIZE => 100, + Doctrine::ATTR_CACHE => Doctrine::CACHE_NONE, + Doctrine::ATTR_BATCH_SIZE => 5, + Doctrine::ATTR_COLL_LIMIT => 5, + Doctrine::ATTR_LISTENER => new EmptyEventListener(), + Doctrine::ATTR_PK_COLUMNS => array("id"), + Doctrine::ATTR_PK_TYPE => Doctrine::INCREMENT_KEY, + Doctrine::ATTR_LOCKMODE => 1, + Doctrine::ATTR_VLD => false, + Doctrine::ATTR_CREATE_TABLES => true + ); + foreach($attributes as $attribute => $value) { + $old = $this->getAttribute($attribute); + if($old === null) + $this->setAttribute($attribute,$value); + } + return true; + } + return false; + } + /** + * returns the root directory of Doctrine + * + * @return string + */ + final public function getRoot() { + return $this->root; + } + /** + * getInstance + * returns an instance of this class + * (this class uses the singleton pattern) + * + * @return Doctrine_Manager + */ + final public static function getInstance() { + static $instance; + if( ! isset($instance)) + $instance = new self(); + + return $instance; + } + + /** + * openSession + * opens a new session and saves it to Doctrine_Manager->sessions + * + * @param PDO $pdo PDO database driver + * @param string $name name of the session, if empty numeric key is used + * @return Doctrine_Session + */ + final public function openSession(PDO $pdo, $name = null) { + // initialize the default attributes + $this->setDefaultAttributes(); + + if($name !== null) { + $name = (string) $name; + if(isset($this->sessions[$name])) + throw new InvalidKeyException(); + + } else { + $name = $this->index; + $this->index++; + } + switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)): + case "mysql": + $this->sessions[$name] = new Doctrine_Session_Mysql($this,$pdo); + break; + case "sqlite": + $this->sessions[$name] = new Doctrine_Session_Sqlite($this,$pdo); + break; + case "pgsql": + $this->sessions[$name] = new Doctrine_Session_Pgsql($this,$pdo); + break; + case "oci": + $this->sessions[$name] = new Doctrine_Session_Oracle($this,$pdo); + break; + case "mssql": + $this->sessions[$name] = new Doctrine_Session_Mssql($this,$pdo); + break; + case "firebird": + $this->sessions[$name] = new Doctrine_Session_Firebird($this,$pdo); + break; + case "informix": + $this->sessions[$name] = new Doctrine_Session_Informix($this,$pdo); + break; + endswitch; + + + $this->currIndex = $name; + return $this->sessions[$name]; + } + /** + * getSession + * @param integer $index + * @return object Doctrine_Session + * @throws InvalidKeyException + */ + final public function getSession($index) { + if( ! isset($this->sessions[$index])) + throw new InvalidKeyException(); + + $this->currIndex = $index; + return $this->sessions[$index]; + } + /** + * closes the session + * + * @param Doctrine_Session $session + * @return void + */ + final public function closeSession(Doctrine_Session $session) { + $session->close(); + unset($session); + } + /** + * getSessions + * returns all opened sessions + * + * @return array + */ + final public function getSessions() { + return $this->sessions; + } + /** + * setCurrentSession + * sets the current session to $key + * + * @param mixed $key the session key + * @throws InvalidKeyException + * @return void + */ + final public function setCurrentSession($key) { + $key = (string) $key; + if( ! isset($this->sessions[$key])) + throw new InvalidKeyException(); + + $this->currIndex = $key; + } + /** + * count + * returns the number of opened sessions + * + * @return integer + */ + public function count() { + return count($this->sessions); + } + /** + * getIterator + * returns an ArrayIterator that iterates through all sessions + * + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->sessions); + } + /** + * getCurrentSession + * returns the current session + * + * @throws Doctrine_Session_Exception if there are no open sessions + * @return Doctrine_Session + */ + final public function getCurrentSession() { + $i = $this->currIndex; + if( ! isset($this->sessions[$i])) + throw new Doctrine_Session_Exception(); + + return $this->sessions[$i]; + } + /** + * __toString + * returns a string representation of this object + * + * @return string + */ + public function __toString() { + $r[] = "
"; + $r[] = "Doctrine_Manager"; + $r[] = "Sessions : ".count($this->sessions); + $r[] = ""; + return implode("\n",$r); + } +} +?> diff --git a/Doctrine/Module.class.php b/Doctrine/Module.class.php new file mode 100644 index 000000000..4587521ee --- /dev/null +++ b/Doctrine/Module.class.php @@ -0,0 +1,60 @@ +name = $name; + } + /** + * returns the name of this module + * + * @return string + */ + public function getName() { + return $this->name; + } + /** + * flush + * saves all components + * + * @return void + */ + public function flush() { + $session = Doctrine_Manager::getInstance()->getCurrentSession(); + + $tree = $session->buildFlushTree($this->components); + } + /** + * getIterator + * this class implements IteratorAggregate interface + * returns an iterator that iterates through the components + * in this module + * + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->components); + } + /** + * count + * this class implements Countable interface + * returns the number of components in this module + * + * @return integer + */ + public function count() { + return count($this->components); + } +} +?> diff --git a/Doctrine/Null.php b/Doctrine/Null.php new file mode 100644 index 000000000..4c4012af7 --- /dev/null +++ b/Doctrine/Null.php @@ -0,0 +1,6 @@ + diff --git a/Doctrine/Query.php b/Doctrine/Query.php new file mode 100644 index 000000000..300105eb8 --- /dev/null +++ b/Doctrine/Query.php @@ -0,0 +1,1005 @@ + array(), + "from" => array(), + "join" => array(), + "where" => array(), + "group" => array(), + "having" => array(), + "orderby" => array(), + "limit" => false, + "offset" => false, + ); + /** + * @var array $parts SQL query string parts + */ + protected $parts = array( + "columns" => array(), + "from" => array(), + "join" => array(), + "where" => array(), + "group" => array(), + "having" => array(), + "orderby" => array(), + "limit" => false, + "offset" => false, + ); + /** + * constructor + * + * @param Doctrine_Session $session + */ + public function __construct(Doctrine_Session $session) { + $this->session = $session; + } + /** + * clear + * resets all the variables + * + * @return void + */ + private function clear() { + $this->fetchModes = array(); + $this->tables = array(); + + $this->parts = array( + "columns" => array(), + "from" => array(), + "join" => array(), + "where" => array(), + "group" => array(), + "having" => array(), + "orderby" => array(), + "limit" => false, + "offset" => false, + ); + $this->inheritanceApplied = false; + $this->aggregate = false; + $this->data = array(); + $this->connectors = array(); + $this->collections = array(); + $this->joined = array(); + $this->joins = array(); + } + /** + * loadFields + * loads fields for a given table and + * constructs a little bit of sql for every field + * + * fields of the tables become: [tablename].[fieldname] as [tablename]__[fieldname] + * + * @access private + * @param object Doctrine_Table $table a Doctrine_Table object + * @param integer $fetchmode fetchmode the table is using eg. Doctrine::FETCH_LAZY + * @param array $names fields to be loaded (only used in lazy property loading) + * @return void + */ + private function loadFields(Doctrine_Table $table, $fetchmode, array $names) { + $name = $table->getComponentName(); + + switch($fetchmode): + case Doctrine::FETCH_OFFSET: + $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT); + case Doctrine::FETCH_IMMEDIATE: + if( ! empty($names)) + throw new Doctrine_Exception("Lazy property loading can only be used with fetching strategies lazy, batch and lazyoffset."); + + $names = $table->getColumnNames(); + break; + case Doctrine::FETCH_LAZY_OFFSET: + $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT); + case Doctrine::FETCH_LAZY: + case Doctrine::FETCH_BATCH: + $names = array_merge($table->getPrimaryKeys(), $names); + break; + default: + throw new Doctrine_Exception("Unknown fetchmode."); + endswitch; + $cname = $table->getComponentName(); + $this->fetchModes[$cname] = $fetchmode; + $tablename = $table->getTableName(); + + $count = count($this->tables); + foreach($names as $name) { + if($count == 0) { + $this->parts["columns"][] = $tablename.".".$name; + } else { + $this->parts["columns"][] = $tablename.".".$name." AS ".$cname."__".$name; + } + } + } + /** + * sets a query part + * + * @param string $name + * @param array $args + * @return void + */ + public function __call($name, $args) { + $name = strtolower($name); + if(isset($this->parts[$name])) { + $method = "parse".ucwords($name); + switch($name): + case "where": + $this->parts[$name] = array($this->$method($args[0])); + break; + case "limit": + case "offset": + if($args[0] == null) + $args[0] = false; + + $this->parts[$name] = $args[0]; + break; + case "from": + $this->parts['columns'] = array(); + $this->joins = array(); + $this->tables = array(); + $this->fetchModes = array(); + default: + $this->parts[$name] = array(); + $this->$method($args[0]); + endswitch; + } + + return $this; + } + /** + * returns a query part + * + * @param $name query part name + * @return mixed + */ + public function get($name) { + if( ! isset($this->parts[$name])) + return false; + + return $this->parts[$name]; + } + /** + * sets a query part + * + * @param $name query part name + * @param $value query part value + * @return boolean + */ + public function set($name, $value) { + + if(isset($this->parts[$name])) { + $method = "parse".ucwords($name); + switch($name): + case "where": + $this->parts[$name] = array($this->$method($value)); + break; + case "limit": + case "offset": + if($value == null) + $value = false; + + $this->parts[$name] = $value; + break; + case "from": + $this->parts['columns'] = array(); + $this->joins = array(); + $this->tables = array(); + $this->fetchModes = array(); + default: + $this->parts[$name] = array(); + $this->$method($value); + endswitch; + + return true; + } + return false; + } + /** + * returns the built sql query + * + * @return string + */ + final public function getQuery() { + if(empty($this->parts["columns"]) || empty($this->parts["from"])) + return false; + + // build the basic query + $q = "SELECT ".implode(", ",$this->parts["columns"]). + " FROM "; + + foreach($this->parts["from"] as $tname => $bool) { + $a[] = $tname; + } + $q .= implode(", ",$a); + + if( ! empty($this->parts['join'])) { + foreach($this->parts['join'] as $part) { + $q .= " ".implode(' ', $part); + } + } + + $this->applyInheritance(); + if( ! empty($this->parts["where"])) + $q .= " WHERE ".implode(" ",$this->parts["where"]); + + if( ! empty($this->parts["orderby"])) + $q .= " ORDER BY ".implode(", ",$this->parts["orderby"]); + + if( ! empty($this->parts["limit"]) || ! empty($this->offset)) + $q = $this->session->modifyLimitQuery($q,$this->parts["limit"],$this->offset); + + return $q; + } + /** + * sql delete for mysql + */ + final public function buildDelete() { + if(empty($this->parts["columns"]) || empty($this->parts["from"])) + return false; + + $a = array_merge(array_keys($this->parts["from"]),$this->joined); + $q = "DELETE ".implode(", ",$a)." FROM "; + $a = array(); + + foreach($this->parts["from"] as $tname => $bool) { + $str = $tname; + if(isset($this->parts["join"][$tname])) + $str .= " ".$this->parts["join"][$tname]; + + $a[] = $str; + } + + $q .= implode(", ",$a); + $this->applyInheritance(); + if( ! empty($this->parts["where"])) + $q .= " WHERE ".implode(" ",$this->parts["where"]); + + if( ! empty($this->parts["orderby"])) + $q .= " ORDER BY ".implode(", ",$this->parts["orderby"]); + + if( ! empty($this->parts["limit"]) && ! empty($this->offset)) + $q = $this->session->modifyLimitQuery($q,$this->parts["limit"],$this->offset); + + return $q; + } + /** + * applyInheritance + * applies column aggregation inheritance to DQL query + * + * @return boolean + */ + final public function applyInheritance() { + if($this->inheritanceApplied) + return false; + + // get the inheritance maps + $array = array(); + + foreach($this->tables as $objTable): + $tname = $objTable->getTableName(); + $array[$tname][] = $objTable->getInheritanceMap(); + endforeach; + + // apply inheritance maps + $str = ""; + $c = array(); + + foreach($array as $tname => $maps) { + $a = array(); + foreach($maps as $map) { + $b = array(); + foreach($map as $field=>$value) { + $b[] = $tname.".$field = $value"; + } + if( ! empty($b)) $a[] = implode(" AND ",$b); + } + if( ! empty($a)) $c[] = implode(" || ",$a); + } + + $str .= implode(" || ",$c); + + $this->addWhere($str); + $this->inheritanceApplied = true; + return true; + } + /** + * @param string $where + * @return boolean + */ + final public function addWhere($where) { + if(empty($where)) + return false; + + if($this->parts["where"]) { + $this->parts["where"][] = "AND (".$where.")"; + } else { + $this->parts["where"][] = "(".$where.")"; + } + return true; + } + /** + * getData + * @param $key the component name + * @return array the data row for the specified component + */ + final public function getData($key) { + if(isset($this->data[$key]) && is_array($this->data[$key])) + return $this->data[$key]; + + return array(); + } + /** + * execute + * executes the dql query and populates all collections + * + * @param string $params + * @return Doctrine_Collection the root collection + */ + public function execute($params = array()) { + $this->data = array(); + $this->collections = array(); + + switch(count($this->tables)): + case 0: + throw new DQLException(); + break; + case 1: + $query = $this->getQuery(); + + $keys = array_keys($this->tables); + + $name = $this->tables[$keys[0]]->getComponentName(); + $stmt = $this->session->execute($query,$params); + + while($data = $stmt->fetch(PDO::FETCH_ASSOC)): + foreach($data as $key => $value): + $e = explode("__",$key); + if(count($e) > 1) { + $data[$e[1]] = $value; + } else { + $data[$e[0]] = $value; + } + unset($data[$key]); + endforeach; + $this->data[$name][] = $data; + endwhile; + + return $this->getCollection($keys[0]); + break; + default: + $query = $this->getQuery(); + + $keys = array_keys($this->tables); + $root = $keys[0]; + $stmt = $this->session->execute($query,$params); + + $previd = array(); + + $coll = $this->getCollection($root); + $prev[$root] = $coll; + + $array = $this->parseData($stmt); + + $colls = array(); + + foreach($array as $data) { + /** + * remove duplicated data rows and map data into objects + */ + foreach($data as $key => $row) { + if(empty($row)) + continue; + + $ids = $this->tables[$key]->getIdentifier(); + + if(is_array($ids)) { + $emptyID = false; + foreach($ids as $id) { + if($row[$id] == null) { + $emptyID = true; + break; + } + } + if($emptyID) + continue; + } else { + if($row[$ids] === null) + continue; + } + + $name = $this->tables[$key]->getComponentName(); + + if( ! isset($previd[$name])) + $previd[$name] = array(); + + + if($previd[$name] !== $row) { + // set internal data + $this->tables[$name]->setData($row); + + // initialize a new record + $record = $this->tables[$name]->getRecord(); + + if($name == $root) { + // add record into root collection + $coll->add($record); + } else { + $pointer = $this->joins[$name]; + + $fk = $this->tables[$pointer]->getForeignKey($this->tables[$pointer]->getAlias($name)); + + switch($fk->getType()): + case Doctrine_Relation::ONE_COMPOSITE: + case Doctrine_Relation::ONE_AGGREGATE: + $last = $prev[$pointer]->getLast(); + + $last->rawSet($this->connectors[$name]->getLocal(), $record->getID()); + + $last->initSingleReference($record); + + $prev[$name] = $record; + break; + default: + // one-to-many relation or many-to-many relation + $last = $prev[$pointer]->getLast(); + + if( ! $last->hasReference($name)) { + $prev[$name] = $this->getCollection($name); + $last->initReference($prev[$name],$this->connectors[$name]); + } + $last->addReference($record); + endswitch; + } + } + + $previd[$name] = $row; + } + } + + return $coll; + endswitch; + } + /** + * parseData + * parses the data returned by PDOStatement + * + * @return array + */ + public function parseData(PDOStatement $stmt) { + $array = array(); + $keys = array(); + foreach(array_keys($this->tables) as $key) { + $k = strtolower($key); + $keys[$k] = $key; + } + while($data = $stmt->fetch(PDO::FETCH_ASSOC)): + /** + * parse the data into two-dimensional array + */ + foreach($data as $key => $value): + $e = explode("__",$key); + + if(count($e) > 1) { + $data[$keys[$e[0]]][$e[1]] = $value; + } else { + $data[0][$e[0]] = $value; + } + unset($data[$key]); + endforeach; + $array[] = $data; + endwhile; + $stmt->closeCursor(); + return $array; + } + /** + * returns a Doctrine_Table for given name + * + * @param string $name component name + * @return Doctrine_Table + */ + public function getTable($name) { + return $this->tables[$name]; + } + /** + * getCollection + * + * @parma string $name component name + * @param integer $index + */ + private function getCollection($name) { + $table = $this->session->getTable($name); + switch($this->fetchModes[$name]): + case Doctrine::FETCH_BATCH: + $coll = new Doctrine_Collection_Batch($table); + break; + case Doctrine::FETCH_LAZY: + $coll = new Doctrine_Collection_Lazy($table); + break; + case Doctrine::FETCH_OFFSET: + $coll = new Doctrine_Collection_Offset($table); + break; + case Doctrine::FETCH_IMMEDIATE: + $coll = new Doctrine_Collection_Immediate($table); + break; + case Doctrine::FETCH_LAZY_OFFSET: + $coll = new Doctrine_Collection_LazyOffset($table); + break; + endswitch; + + $coll->populate($this); + return $coll; + } + /** + * query the database with DQL (Doctrine Query Language) + * + * @param string $query DQL query + * @param array $params parameters + */ + public function query($query,$params = array()) { + $this->parseQuery($query); + + if($this->aggregate) { + $keys = array_keys($this->tables); + $query = $this->getQuery(); + $stmt = $this->tables[$keys[0]]->getSession()->select($query,$this->parts["limit"],$this->offset); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + if(count($data) == 1) { + return current($data); + } else { + return $data; + } + } else { + return $this->execute($params); + } + } + /** + * DQL PARSER + * + * @param string $query DQL query + * @return void + */ + final public function parseQuery($query) { + $this->clear(); + $e = self::bracketExplode($query," ","(",")"); + + $parts = array(); + foreach($e as $k=>$part): + switch(strtolower($part)): + case "select": + case "from": + case "where": + case "limit": + case "offset": + $p = $part; + $parts[$part] = array(); + break; + case "order": + $p = $part; + $i = $k+1; + if(isset($e[$i]) && strtolower($e[$i]) == "by") { + $parts[$part] = array(); + } + break; + case "by": + continue; + default: + $parts[$p][] = $part; + endswitch; + endforeach; + + foreach($parts as $k => $part) { + $part = implode(" ",$part); + switch(strtoupper($k)): + case "SELECT": + $this->parseSelect($part); + break; + case "FROM": + $this->parseFrom($part); + break; + case "WHERE": + $this->addWhere($this->parseWhere($part)); + break; + case "ORDER": + $this->parseOrderBy($part); + break; + case "LIMIT": + $this->parts["limit"] = trim($part); + break; + case "OFFSET": + $this->offset = trim($part); + break; + endswitch; + } + } + /** + * DQL ORDER BY PARSER + * parses the order by part of the query string + * + * @param string $str + * @return void + */ + private function parseOrderBy($str) { + foreach(explode(",",trim($str)) as $r) { + $r = trim($r); + $e = explode(" ",$r); + $a = explode(".",$e[0]); + + if(count($a) > 1) { + $field = array_pop($a); + $reference = implode(".",$a); + $name = end($a); + + $this->load($reference, false); + $tname = $this->tables[$name]->getTableName(); + + $r = $tname.".".$field; + if(isset($e[1])) + $r .= " ".$e[1]; + } + $this->parts["orderby"][] = $r; + } + } + /** + * DQL SELECT PARSER + * parses the select part of the query string + * + * @param string $str + * @return void + */ + private function parseSelect($str) { + $this->aggregate = true; + foreach(explode(",",trim($str)) as $reference) { + + $e = explode(" AS ",trim($reference)); + + $f = explode("(",$e[0]); + $a = explode(".",$f[1]); + $field = substr(array_pop($a),0,-1); + + $reference = trim(implode(".",$a)); + + $objTable = $this->load($reference); + if(isset($e[1])) + $s = " AS $e[1]"; + + $this->parts["columns"][] = $f[0]."(".$objTable->getTableName().".$field)$s"; + + } + } + /** + * DQL FROM PARSER + * parses the from part of the query string + + * @param string $str + * @return void + */ + private function parseFrom($str) { + foreach(explode(",",trim($str)) as $reference) { + $reference = trim($reference); + $table = $this->load($reference); + } + } + /** + * returns Doctrine::FETCH_* constant + * + * @param string $mode + * @return integer + */ + private function parseFetchMode($mode) { + switch(strtolower($mode)): + case "i": + case "immediate": + $fetchmode = Doctrine::FETCH_IMMEDIATE; + break; + case "b": + case "batch": + $fetchmode = Doctrine::FETCH_BATCH; + break; + case "l": + case "lazy": + $fetchmode = Doctrine::FETCH_LAZY; + break; + case "o": + case "offset": + $fetchmode = Doctrine::FETCH_OFFSET; + break; + case "lo": + case "lazyoffset": + $fetchmode = Doctrine::FETCH_LAZYOFFSET; + default: + throw new DQLException("Unknown fetchmode '$mode'. The availible fetchmodes are 'i', 'b' and 'l'."); + endswitch; + return $fetchmode; + } + /** + * DQL WHERE PARSER + * parses the where part of the query string + * + * + * @param string $str + * @return string + */ + private function parseWhere($str) { + $tmp = trim($str); + $str = self::bracketTrim($tmp,"(",")"); + + $brackets = false; + while($tmp != $str) { + $brackets = true; + $tmp = $str; + $str = self::bracketTrim($str,"(",")"); + } + + $parts = self::bracketExplode($str," && ","(",")"); + if(count($parts) > 1) { + $ret = array(); + foreach($parts as $part) { + $ret[] = $this->parseWhere($part); + } + $r = implode(" AND ",$ret); + } else { + $parts = self::bracketExplode($str," || ","(",")"); + if(count($parts) > 1) { + $ret = array(); + foreach($parts as $part) { + $ret[] = $this->parseWhere($part); + } + $r = implode(" OR ",$ret); + } else { + return $this->loadWhere($parts[0]); + } + } + if($brackets) + return "(".$r.")"; + else + return $r; + } + /** + * trims brackets + * + * @param string $str + * @param string $e1 the first bracket, usually '(' + * @param string $e2 the second bracket, usually ')' + */ + public static function bracketTrim($str,$e1,$e2) { + if(substr($str,0,1) == $e1 && substr($str,-1) == $e2) + return substr($str,1,-1); + else + return $str; + } + /** + * bracketExplode + * usage: + * $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com' + * now exploding $str with parameters $d = ' AND ', $e1 = '(' and $e2 = ')' + * would return an array: + * array("(age < 20 AND age > 18)", "email LIKE 'John@example.com'") + * + * @param string $str + * @param string $d the delimeter which explodes the string + * @param string $e1 the first bracket, usually '(' + * @param string $e2 the second bracket, usually ')' + * + */ + public static function bracketExplode($str,$d,$e1,$e2) { + $str = explode("$d",$str); + $i = 0; + $term = array(); + foreach($str as $key=>$val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + $s1 = substr_count($term[$i],"$e1"); + $s2 = substr_count($term[$i],"$e2"); + if($s1 == $s2) $i++; + } else { + $term[$i] .= "$d".trim($val); + $c1 = substr_count($term[$i],"$e1"); + $c2 = substr_count($term[$i],"$e2"); + if($c1 == $c2) $i++; + } + } + return $term; + } + /** + * loadWhere + * + * @param string $where + */ + private function loadWhere($where) { + $e = explode(" ",$where); + $r = array_shift($e); + $a = explode(".",$r); + + + if(count($a) > 1) { + $field = array_pop($a); + $operator = array_shift($e); + $value = implode(" ",$e); + $reference = implode(".",$a); + + if(count($a) > 1) + $objTable = $this->tables[$a[0]]->getForeignKey(end($a))->getTable(); + else + $objTable = $this->session->getTable(end($a)); + + $where = $objTable->getTableName().".".$field." ".$operator." ".$value; + + if(count($a) > 1 && isset($a[1])) { + $root = $a[0]; + $fk = $this->tables[$root]->getForeignKey($a[1]); + if($fk instanceof Doctrine_Association) { + $asf = $fk->getAssociationFactory(); + + switch($fk->getType()): + case Doctrine_Relation::ONE_AGGREGATE: + case Doctrine_Relation::ONE_COMPOSITE: + + break; + case Doctrine_Relation::MANY_AGGREGATE: + case Doctrine_Relation::MANY_COMPOSITE: + + // subquery needed + $where = $objTable->getComponentName().".".$field." ".$operator." ".$value; + $b = $fk->getTable()->getComponentName(); + + $graph = new Doctrine_Query($this->session); + $graph->parseQuery("FROM $b-l WHERE $where"); + $where = $this->tables[$root]->getTableName().".".$this->tables[$root]->getIdentifier()." IN (SELECT ".$fk->getLocal()." FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." IN (".$graph->getQuery()."))"; + break; + endswitch; + } else + $this->load($reference, false); + + } else + $this->load($reference, false); + } + return $where; + } + /** + * @param string $path the path of the loadable component + * @param integer $fetchmode optional fetchmode, if not set the components default fetchmode will be used + * @throws DQLException + */ + final public function load($path, $loadFields = true) { + $e = preg_split("/[.:]/",$path); + $index = 0; + + foreach($e as $key => $fullname) { + try { + $e2 = preg_split("/[-(]/",$fullname); + $name = $e2[0]; + + if($key == 0) { + + $table = $this->session->getTable($name); + + $tname = $table->getTableName(); + $this->parts["from"][$tname] = true; + + } else { + + $index += strlen($e[($key - 1)]) + 1; + // the mark here is either '.' or ':' + $mark = substr($path,($index - 1),1); + + + $fk = $table->getForeignKey($name); + $name = $fk->getTable()->getComponentName(); + + $tname = $table->getTableName(); + + $tname2 = $fk->getTable()->getTableName(); + + $this->connectors[$name] = $fk; + + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + + switch($mark): + case ":": + $this->parts["join"][$tname][$tname2] = "INNER JOIN ".$tname2." ON ".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign(); + break; + case ".": + $this->parts["join"][$tname][$tname2] = "LEFT JOIN ".$tname2." ON ".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign(); + break; + endswitch; + + $c = $table->getComponentName(); + $this->joins[$name] = $c; + } elseif($fk instanceof Doctrine_Association) { + $asf = $fk->getAssociationFactory(); + + switch($fk->getType()): + case Doctrine_Relation::ONE_AGGREGATE: + case Doctrine_Relation::ONE_COMPOSITE: + + break; + case Doctrine_Relation::MANY_AGGREGATE: + case Doctrine_Relation::MANY_COMPOSITE: + + //$this->addWhere("SELECT ".$fk->getLocal()." FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." IN (SELECT ".$fk->getTable()->getComponentName().")"); + $this->parts["from"][$tname] = true; + break; + endswitch; + } + + $table = $fk->getTable(); + + } + + if( ! isset($this->tables[$name])) { + $this->tables[$name] = $table; + + if($loadFields && ! $this->aggregate) { + $fields = array(); + + if(strpos($fullname, "-") === false) { + $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); + + if(isset($e2[1])) + $fields = explode(",",substr($e2[1],0,-1)); + + } else { + if(isset($e2[1])) { + $fetchmode = $this->parseFetchMode($e2[1]); + } else + $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); + + if(isset($e2[2])) + $fields = explode(",",substr($e2[2],0,-1)); + } + + $this->loadFields($table, $fetchmode, $fields); + } + } + + } catch(Exception $e) { + throw new DQLException($e->getMessage(),$e->getCode()); + } + } + } +} + +?> diff --git a/Doctrine/Record.php b/Doctrine/Record.php new file mode 100644 index 000000000..86d8980f7 --- /dev/null +++ b/Doctrine/Record.php @@ -0,0 +1,1110 @@ +table = $table; + $exists = ( ! $this->table->isNewEntry()); + } else { + $this->table = Doctrine_Manager::getInstance()->getCurrentSession()->getTable(get_class($this)); + $exists = false; + } + + // Check if the current session has the records table in its registry + // If not this is record is only used for creating table definition and setting up + // relations. + + if($this->table->getSession()->hasTable($this->table->getComponentName())) { + + $this->oid = self::$index; + + self::$index++; + + $keys = $this->table->getPrimaryKeys(); + + if( ! $exists) { + // listen the onPreCreate event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreCreate($this); + } else { + // listen the onPreLoad event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreLoad($this); + } + // get the data array + $this->data = $this->table->getData(); + + // get the column count + $count = count($this->data); + + // clean data array + $cols = $this->cleanData(); + + $this->prepareIdentifiers($exists); + + if( ! $exists) { + + if($cols > 0) + $this->state = Doctrine_Record::STATE_TDIRTY; + else + $this->state = Doctrine_Record::STATE_TCLEAN; + + // listen the onCreate event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onCreate($this); + + } else { + $this->state = Doctrine_Record::STATE_CLEAN; + + if($count < $this->table->getColumnCount()) { + $this->state = Doctrine_Record::STATE_PROXY; + } + + + // listen the onLoad event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this); + } + $this->table->getRepository()->add($this); + } + } + /** + * initNullObject + */ + public static function initNullObject(Doctrine_Null $null) { + self::$null = $null; + } + /** + * setUp + * implemented by child classes + */ + public function setUp() { } + /** + * return the object identifier + * + * @return integer + */ + public function getOID() { + return $this->oid; + } + /** + * cleanData + * modifies data array + * example: + * + * $data = array("name"=>"John","lastname"=> null,"id"=>1,"unknown"=>"unknown"); + * $names = array("name","lastname","id"); + * $data after operation: + * $data = array("name"=>"John","lastname" => array(),"id"=>1); + */ + private function cleanData() { + $cols = 0; + $tmp = $this->data; + + $this->data = array(); + + foreach($this->table->getColumnNames() as $name) { + if( ! isset($tmp[$name])) { + $this->data[$name] = self::$null; + + } else { + $cols++; + $this->data[$name] = $tmp[$name]; + } + } + + return $cols; + } + /** + * prepares identifiers + * + * @return void + */ + private function prepareIdentifiers($exists = true) { + switch($this->table->getIdentifierType()): + case Doctrine_Identifier::AUTO_INCREMENT: + case Doctrine_Identifier::SEQUENCE: + if($exists) { + $name = $this->table->getIdentifier(); + + if(isset($this->data[$name])) + $this->id = $this->data[$name]; + + unset($this->data[$name]); + } + break; + case Doctrine_Identifier::COMPOSITE: + $names = $this->table->getIdentifier(); + $this->id = array(); + + foreach($names as $name) { + if($this->data[$name] === self::$null) + $this->id[$name] = null; + else + $this->id[$name] = $this->data[$name]; + } + break; + endswitch; + } + /** + * this method is automatically called when this Doctrine_Record is serialized + * + * @return array + */ + public function __sleep() { + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onSleep($this); + + $this->table = $this->table->getComponentName(); + // unset all vars that won't need to be serialized + + unset($this->modified); + unset($this->associations); + unset($this->state); + unset($this->collections); + unset($this->references); + unset($this->originals); + unset($this->oid); + + foreach($this->data as $k=>$v) { + if($v instanceof Doctrine_Record) + $this->data[$k] = array(); + } + return array_keys(get_object_vars($this)); + + } + /** + * unseralize + * this method is automatically called everytime a Doctrine_Record object is unserialized + * + * @return void + */ + public function __wakeup() { + + $this->modified = array(); + $this->state = Doctrine_Record::STATE_CLEAN; + + $name = $this->table; + + $manager = Doctrine_Manager::getInstance(); + $sess = $manager->getCurrentSession(); + + $this->oid = self::$index; + self::$index++; + + $this->table = $sess->getTable($name); + + $this->table->getRepository()->add($this); + + $this->cleanData(); + + //unset($this->data['id']); + + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onWakeUp($this); + + } + /** + * addCollection + * @param Doctrine_Collection $collection + * @param mixed $key + */ + final public function addCollection(Doctrine_Collection $collection,$key = null) { + if($key !== null) { + if(isset($this->collections[$key])) + throw InvalidKeyException(); + + $this->collections[$key] = $collection; + } else { + $this->collections[] = $collection; + } + } + /** + * getCollection + * @param integer $key + * @return Doctrine_Collection + */ + final public function getCollection($key) { + return $this->collections[$key]; + } + /** + * hasCollections + * whether or not this record is part of a collection + * + * @return boolean + */ + final public function hasCollections() { + return (! empty($this->collections)); + } + /** + * getState + * returns the current state of the object + * + * @see Doctrine_Record::STATE_* constants + * @return integer + */ + final public function getState() { + return $this->state; + } + /** + * refresh + * refresh internal data from the database + * + * @return boolean + */ + final public function refresh() { + $id = $this->getID(); + if( ! is_array($id)) + $id = array($id); + + if(empty($id)) + return false; + + $id = array_values($id); + + $query = $this->table->getQuery()." WHERE ".implode(" = ? && ",$this->table->getPrimaryKeys())." = ?"; + $this->data = $this->table->getSession()->execute($query,$id)->fetch(PDO::FETCH_ASSOC); + + $this->modified = array(); + $this->cleanData(); + + $this->prepareIdentifiers(); + + $this->state = Doctrine_Record::STATE_CLEAN; + + return true; + } + /** + * factoryRefresh + * @throws Doctrine_Exception + * @return void + */ + final public function factoryRefresh() { + $data = $this->table->getData(); + $id = $this->id; + + $this->prepareIdentifiers(); + + if($this->id != $id) + throw new Doctrine_Record_Exception(); + + $this->data = $data; + + $this->cleanData(); + + $this->state = Doctrine_Record::STATE_CLEAN; + $this->modified = array(); + } + /** + * return the factory that created this data access object + * @return object Doctrine_Table a Doctrine_Table object + */ + final public function getTable() { + return $this->table; + } + /** + * return all the internal data + * @return array an array containing all the properties + */ + final public function getData() { + return $this->data; + } + /** + * get + * returns a value of a property or a related component + * + * @param $name name of the property or related component + * @throws InvalidKeyException + * @return mixed + */ + public function get($name) { + if(isset($this->data[$name])) { + + // check if the property is null (= it is the Doctrine_Null object located in self::$null) + if($this->data[$name] === self::$null) { + + // no use trying to load the data from database if the Doctrine_Record is not a proxy + if($this->state == Doctrine_Record::STATE_PROXY) { + if( ! empty($this->collections)) { + foreach($this->collections as $collection) { + $collection->load($this); + } + } else { + $this->refresh(); + } + $this->state = Doctrine_Record::STATE_CLEAN; + } + + if($this->data[$name] === self::$null) + return null; + } + return $this->data[$name]; + } + + if($name == $this->table->getIdentifier()) + return $this->id; + + if( ! isset($this->references[$name])) + $this->loadReference($name); + + + return $this->references[$name]; + } + /** + * rawSet + * doctrine uses this function internally, not recommended for developers + * + * @param mixed $name name of the property or reference + * @param mixed $value value of the property or reference + */ + final public function rawSet($name,$value) { + if($value instanceof Doctrine_Record) + $id = $value->getID(); + + if( ! empty($id)) + $value = $id; + + if(isset($this->data[$name])) { + if($this->data[$name] === self::$null) { + if($this->data[$name] !== $value) { + switch($this->state): + case Doctrine_Record::STATE_CLEAN: + $this->state = Doctrine_Record::STATE_DIRTY; + break; + case Doctrine_Record::STATE_TCLEAN: + $this->state = Doctrine_Record::STATE_TDIRTY; + endswitch; + } + } + + if($this->state == Doctrine_Record::STATE_TCLEAN) + $this->state = Doctrine_Record::STATE_TDIRTY; + + $this->data[$name] = $value; + $this->modified[] = $name; + } + } + /** + * set + * method for altering properties and Doctrine_Record references + * + * @param mixed $name name of the property or reference + * @param mixed $value value of the property or reference + * @throws InvalidKeyException + * @throws InvalidTypeException + * @return void + */ + public function set($name,$value) { + if(isset($this->data[$name])) { + + if($value instanceof Doctrine_Record) { + $id = $value->getID(); + + if( ! empty($id)) + $value = $value->getID(); + } + + $old = $this->get($name); + + if($old !== $value) { + $this->data[$name] = $value; + $this->modified[] = $name; + switch($this->state): + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_PROXY: + $this->state = Doctrine_Record::STATE_DIRTY; + break; + case Doctrine_Record::STATE_TCLEAN: + $this->state = Doctrine_Record::STATE_TDIRTY; + break; + endswitch; + } + } else { + // if not found, throws InvalidKeyException + + $fk = $this->table->getForeignKey($name); + + // one-to-many or one-to-one relation + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + switch($fk->getType()): + case Doctrine_Relation::MANY_COMPOSITE: + case Doctrine_Relation::MANY_AGGREGATE: + // one-to-many relation found + if( ! ($value instanceof Doctrine_Collection)) + throw new Doctrine_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references."); + + $value->setReference($this,$fk); + break; + case Doctrine_Relation::ONE_COMPOSITE: + case Doctrine_Relation::ONE_AGGREGATE: + // one-to-one relation found + if( ! ($value instanceof Doctrine_Record)) + throw new Doctrine_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Record when setting one-to-one references."); + + if($fk->getLocal() == $this->table->getIdentifier()) { + $this->references[$name]->set($fk->getForeign(),$this); + } else { + $this->set($fk->getLocal(),$value); + } + break; + endswitch; + + } elseif($fk instanceof Doctrine_Association) { + // join table relation found + if( ! ($value instanceof Doctrine_Collection)) + throw new Doctrine_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references."); + } + + $this->references[$name] = $value; + } + } + /** + * __isset + * + * @param string $name + * @return boolean + */ + public function __isset($name) { + if(isset($this->data[$name])) + return true; + + if(isset($this->references[$name])) + return true; + + return false; + } + /** + * @param string $name + * @return void + */ + public function __unset($name) { + if(isset($this->data[$name])) + $this->data[$name] = array(); + + // todo: what to do with references ? + } + /** + * applies the changes made to this object into database + * this method is smart enough to know if any changes are made + * and whether to use INSERT or UPDATE statement + * + * this method also saves the related composites + * + * @return void + */ + final public function save() { + $this->table->getSession()->beginTransaction(); + + // listen the onPreSave event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($this); + + $saveLater = $this->table->getSession()->saveRelated($this); + + $this->table->getSession()->save($this); + + foreach($saveLater as $fk) { + $table = $fk->getTable(); + $alias = $this->table->getAlias($table->getComponentName()); + + if(isset($this->references[$alias])) { + $obj = $this->references[$alias]; + $obj->save(); + } + } + + // save the MANY-TO-MANY associations + + $this->saveAssociations(); + + $this->table->getSession()->commit(); + } + /** + * returns an array of modified fields and associated values + * @return array + */ + final public function getModified() { + $a = array(); + + foreach($this->modified as $k=>$v) { + $a[$v] = $this->data[$v]; + } + return $a; + } + /** + * returns an array of modified fields and values with data preparation + * adds column aggregation inheritance and converts Records into primary key values + * + * @return array + */ + final public function getPrepared() { + $a = array(); + + foreach($this->table->getInheritanceMap() as $k => $v) { + $this->set($k,$v); + } + + foreach($this->modified as $k => $v) { + if($this->data[$v] instanceof Doctrine_Record) { + $this->data[$v] = $this->data[$v]->getID(); + } + $a[$v] = $this->data[$v]; + } + + return $a; + } + /** + * this class implements countable interface + * @return integer the number of columns + */ + public function count() { + return count($this->data); + } + /** + * getIterator + * @return ArrayIterator an ArrayIterator that iterates through the data + */ + public function getIterator() { + return new ArrayIterator($this->data); + } + /** + * saveAssociations + * save the associations of many-to-many relations + * this method also deletes associations that do not exist anymore + * @return void + */ + final public function saveAssociations() { + foreach($this->table->getForeignKeys() as $fk): + $table = $fk->getTable(); + $name = $table->getComponentName(); + $alias = $this->table->getAlias($name); + + if($fk instanceof Doctrine_Association) { + switch($fk->getType()): + case Doctrine_Relation::MANY_COMPOSITE: + + break; + case Doctrine_Relation::MANY_AGGREGATE: + $asf = $fk->getAssociationFactory(); + + if(isset($this->references[$alias])) { + + $new = $this->references[$alias]; + + if( ! isset($this->originals[$alias])) { + $this->loadReference($alias); + } + + $r = $this->getRelationOperations($alias,$new); + + foreach($r["delete"] as $record) { + $query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?" + ." && ".$fk->getLocal()." = ?"; + $this->table->getSession()->execute($query, array($record->getID(),$this->getID())); + } + foreach($r["add"] as $record) { + $reldao = $asf->create(); + $reldao->set($fk->getForeign(),$record); + $reldao->set($fk->getLocal(),$this); + $reldao->save(); + } + $this->originals[$alias] = clone $this->references[$alias]; + } + break; + endswitch; + } elseif($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + + switch($fk->getType()): + case Doctrine_Relation::ONE_COMPOSITE: + if(isset($this->originals[$alias]) && $this->originals[$alias]->getID() != $this->references[$alias]->getID()) + $this->originals[$alias]->delete(); + + break; + case Doctrine_Relation::MANY_COMPOSITE: + if(isset($this->references[$alias])) { + $new = $this->references[$alias]; + + if( ! isset($this->originals[$alias])) + $this->loadReference($alias); + + $r = $this->getRelationOperations($alias,$new); + + foreach($r["delete"] as $record) { + $record->delete(); + } + + $this->originals[$alias] = clone $this->references[$alias]; + } + break; + endswitch; + } + endforeach; + } + /** + * get the records that need to be added + * and/or deleted in order to change the old collection + * to the new one + * + * The algorithm here is very simple and definitely not + * the fastest one, since we have to iterate through the collections twice. + * the complexity of this algorithm is O(n^2) + * + * First we iterate through the new collection and get the + * records that do not exist in the old collection (Doctrine_Records that need to be added). + * + * Then we iterate through the old collection and get the records + * that do not exists in the new collection (Doctrine_Records that need to be deleted). + */ + final public function getRelationOperations($name, Doctrine_Collection $new) { + $r["add"] = array(); + $r["delete"] = array(); + + + + foreach($new as $k=>$record) { + + $found = false; + + if($record->getID() !== null) { + foreach($this->originals[$name] as $k2 => $record2) { + if($record2->getID() == $record->getID()) { + $found = true; + break; + } + } + } + if( ! $found) { + $this->originals[$name][] = $record; + $r["add"][] = $record; + } + } + + foreach($this->originals[$name] as $k => $record) { + if($record->getID() === null) + continue; + + $found = false; + foreach($new as $k2=>$record2) { + if($record2->getID() == $record->getID()) { + $found = true; + break; + } + } + + if( ! $found) { + $r["delete"][] = $record; + unset($this->originals[$name][$k]); + } + } + + return $r; + } + /** + * getOriginals + */ + final public function getOriginals($name) { + if( ! isset($this->originals[$name])) + throw new InvalidKeyException(); + + return $this->originals[$name]; + } + /** + * deletes this data access object and all the related composites + * this operation is isolated by a transaction + * + * this event can be listened by the onPreDelete and onDelete listeners + * + * @return boolean true on success, false on failure + */ + public function delete() { + $this->table->getSession()->delete($this); + } + /** + * returns a copy of this object + * @return DAO + */ + public function copy() { + return $this->table->create($this->data); + } + /** + * @param integer $id + * @return void + */ + final public function setID($id = false) { + if($id === false) { + $this->id = false; + $this->cleanData(); + $this->state = Doctrine_Record::STATE_TCLEAN; + $this->modified = array(); + } elseif($id === true) { + $this->prepareIdentifiers(false); + $this->state = Doctrine_Record::STATE_CLEAN; + $this->modified = array(); + } else { + $this->id = $id; + $this->state = Doctrine_Record::STATE_CLEAN; + $this->modified = array(); + } + } + /** + * return the primary key(s) this object is pointing at + * @return mixed id + */ + final public function getID() { + return $this->id; + } + /** + * getLast + * this method is used internally be Doctrine_Query + * it is needed to provide compatibility between + * records and collections + * + * @return Doctrine_Record + */ + public function getLast() { + return $this; + } + /** + * hasRefence + * @param string $name + * @return boolean + */ + public function hasReference($name) { + return isset($this->references[$name]); + } + /** + * initalizes a one-to-one relation + * + * @param Doctrine_Record $record + * @param Doctrine_Relation $connector + * @return void + */ + public function initSingleReference(Doctrine_Record $record) { + $name = $this->table->getAlias($record->getTable()->getComponentName()); + $this->references[$name] = $record; + } + /** + * initalizes a one-to-many / many-to-many relation + * + * @param Doctrine_Collection $coll + * @param Doctrine_Relation $connector + * @return void + */ + public function initReference(Doctrine_Collection $coll, Doctrine_Relation $connector) { + $name = $this->table->getAlias($coll->getTable()->getComponentName()); + $coll->setReference($this, $connector); + $this->references[$name] = $coll; + $this->originals[$name] = clone $coll; + } + /** + * addReference + * @param Doctrine_Record $record + * @param mixed $key + * @return void + */ + public function addReference(Doctrine_Record $record, $key = null) { + $name = $this->table->getAlias($record->getTable()->getComponentName()); + + $this->references[$name]->add($record, $key); + $this->originals[$name]->add($record, $key); + } + /** + * getReferences + * @return array all references + */ + public function getReferences() { + return $this->references; + } + + /** + * @throws InvalidKeyException + * @param name + * @return void + */ + final public function loadReference($name) { + $fk = $this->table->getForeignKey($name); + $table = $fk->getTable(); + + $local = $fk->getLocal(); + $foreign = $fk->getForeign(); + $graph = $table->getQueryObject(); + $type = $fk->getType(); + + switch($this->getState()): + case Doctrine_Record::STATE_TDIRTY: + case Doctrine_Record::STATE_TCLEAN: + + if($type == Doctrine_Relation::ONE_COMPOSITE || + $type == Doctrine_Relation::ONE_AGGREGATE) { + + // ONE-TO-ONE + $this->references[$name] = $table->create(); + + if($fk instanceof Doctrine_ForeignKey) { + $this->references[$name]->set($fk->getForeign(),$this); + } else { + $this->set($fk->getLocal(),$this->references[$name]); + } + } else { + $this->references[$name] = new Doctrine_Collection($table); + if($fk instanceof Doctrine_ForeignKey) { + // ONE-TO-MANY + $this->references[$name]->setReference($this,$fk); + } + $this->originals[$name] = new Doctrine_Collection($table); + } + break; + case Doctrine_Record::STATE_DIRTY: + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_PROXY: + + switch($fk->getType()): + case Doctrine_Relation::ONE_COMPOSITE: + case Doctrine_Relation::ONE_AGGREGATE: + + // ONE-TO-ONE + $id = $this->get($local); + + if($fk instanceof Doctrine_LocalKey) { + + if(empty($id)) { + $this->references[$name] = $table->create(); + $this->set($fk->getLocal(),$this->references[$name]); + } else { + try { + $this->references[$name] = $table->find($id); + } catch(Doctrine_Find_Exception $e) { + + } + } + + } elseif ($fk instanceof Doctrine_ForeignKey) { + + if(empty($id)) { + $this->references[$name] = $table->create(); + $this->references[$name]->set($fk->getForeign(), $this); + } else { + $dql = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$fk->getForeign()." = ?"; + $coll = $graph->query($dql, array($id)); + $this->references[$name] = $coll[0]; + $this->references[$name]->set($fk->getForeign(), $this); + } + } + break; + default: + // ONE-TO-MANY + if($fk instanceof Doctrine_ForeignKey) { + $id = $this->get($local); + $query = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$fk->getForeign()." = ?"; + $coll = $graph->query($query,array($id)); + + $this->references[$name] = $coll; + $this->references[$name]->setReference($this, $fk); + + $this->originals[$name] = clone $coll; + + } elseif($fk instanceof Doctrine_Association) { + $asf = $fk->getAssociationFactory(); + $query = "SELECT ".$foreign." FROM ".$asf->getTableName()." WHERE ".$local." = ?"; + + $graph = new Doctrine_Query($table->getSession()); + $query = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".".$table->getIdentifier()." IN ($query)"; + + $coll = $graph->query($query, array($this->getID())); + + $this->references[$name] = $coll; + $this->originals[$name] = clone $coll; + + } + endswitch; + break; + endswitch; + } + + /** + * binds One-to-One composite relation + * + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function ownsOne($componentName,$foreignKey, $localKey = null) { + $this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_COMPOSITE, $localKey); + } + /** + * binds One-to-Many composite relation + * + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function ownsMany($componentName,$foreignKey, $localKey = null) { + $this->table->bind($componentName,$foreignKey,Doctrine_Relation::MANY_COMPOSITE, $localKey); + } + /** + * binds One-to-One aggregate relation + * + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function hasOne($componentName,$foreignKey, $localKey = null) { + $this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_AGGREGATE, $localKey); + } + /** + * binds One-to-Many aggregate relation + * + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function hasMany($componentName,$foreignKey, $localKey = null) { + $this->table->bind($componentName,$foreignKey,Doctrine_Relation::MANY_AGGREGATE, $localKey); + } + /** + * setInheritanceMap + * @param array $inheritanceMap + * @return void + */ + final public function setInheritanceMap(array $inheritanceMap) { + $this->table->setInheritanceMap($inheritanceMap); + } + /** + * setPrimaryKey + * @param mixed $key + */ + final public function setPrimaryKey($key) { + $this->table->setPrimaryKey($key); + } + /** + * setTableName + * @param string $name table name + * @return void + */ + final public function setTableName($name) { + $this->table->setTableName($name); + } + /** + * setAttribute + * @param integer $attribute + * @param mixed $value + * @see Doctrine::ATTR_* constants + * @return void + */ + final public function setAttribute($attribute, $value) { + $this->table->setAttribute($attribute,$value); + } + /** + * hasColumn + * sets a column definition + * + * @param string $name + * @param string $type + * @param integer $length + * @param mixed $options + * @return void + */ + final public function hasColumn($name, $type, $length = 20, $options = "") { + $this->table->setColumn($name, $type, $length, $options); + } + /** + * returns a string representation of this object + */ + public function __toString() { + return Doctrine_Lib::getRecordAsString($this); + } +} +?> diff --git a/Doctrine/Relation.php b/Doctrine/Relation.php new file mode 100644 index 000000000..47b67dfd3 --- /dev/null +++ b/Doctrine/Relation.php @@ -0,0 +1,101 @@ +table = $table; + $this->local = $local; + $this->foreign = $foreign; + $this->type = $type; + } + /** + * @return integer bind type 1 or 0 + */ + public function getType() { + return $this->type; + } + /** + * @return object Doctrine_Table foreign factory object + */ + public function getTable() { + return $this->table; + } + /** + * @return string the name of the local column + */ + public function getLocal() { + return $this->local; + } + /** + * @return string the name of the foreign column where + * the local column is pointing at + */ + public function getForeign() { + return $this->foreign; + } + /** + * __toString + */ + public function __toString() { + $r[] = "
"; + $r[] = "Class : ".get_class($this); + $r[] = "Component : ".$this->table->getComponentName(); + $r[] = "Table : ".$this->table->getTableName(); + $r[] = "Local key : ".$this->local; + $r[] = "Foreign key : ".$this->foreign; + $r[] = "Type : ".$this->type; + $r[] = ""; + return implode("\n", $r); + } +} + +?> diff --git a/Doctrine/Repository.php b/Doctrine/Repository.php new file mode 100644 index 000000000..d3952d659 --- /dev/null +++ b/Doctrine/Repository.php @@ -0,0 +1,114 @@ +table = $table; + } + /** + * @return object Doctrine_Table + */ + public function getTable() { + return $this->table; + } + /** + * add + * @param Doctrine_Record $record record to be added into registry + */ + public function add(Doctrine_Record $record) { + $oid = $record->getOID(); + + if(isset($this->registry[$oid])) + return false; + + $this->registry[$oid] = $record; + + return true; + } + /** + * get + * @param integer $oid + * @throws InvalidKeyException + */ + public function get($oid) { + if( ! isset($this->registry[$oid])) + throw new InvalidKeyException(); + + return $this->registry[$oid]; + } + /** + * count + * Doctrine_Registry implements interface Countable + * @return integer the number of records this registry has + */ + public function count() { + return count($this->registry); + } + /** + * @param integer $oid object identifier + * @return boolean whether ot not the operation was successful + */ + public function evict($oid) { + if( ! isset($this->registry[$oid])) + return false; + + unset($this->registry[$oid]); + return true; + } + /** + * @return integer number of records evicted + */ + public function evictAll() { + $evicted = 0; + foreach($this->registry as $oid=>$record) { + if($this->evict($oid)) + $evicted++; + } + return $evicted; + } + /** + * getIterator + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->registry); + } + /** + * contains + * @param integer $oid object identifier + */ + public function contains($oid) { + return isset($this->registry[$oid]); + } + /** + * loadAll + * @return void + */ + public function loadAll() { + $this->table->findAll(); + } +} +?> diff --git a/Doctrine/Session.php b/Doctrine/Session.php new file mode 100644 index 000000000..8b02049ba --- /dev/null +++ b/Doctrine/Session.php @@ -0,0 +1,934 @@ +dbh = $pdo; + + $this->setParent($manager); + + $this->state = Doctrine_Session::STATE_OPEN; + + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + switch($this->getAttribute(Doctrine::ATTR_CACHE)): + case Doctrine::CACHE_SQLITE: + $dir = $this->getAttribute(Doctrine::ATTR_CACHE_DIR).DIRECTORY_SEPARATOR; + $dsn = "sqlite:".$dir."data.cache"; + + $this->cacheHandler = Doctrine_DB::getConn($dsn); + $this->cacheHandler->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->cacheHandler->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + break; + endswitch; + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onOpen($this); + } + + public function getCacheHandler() { + return $this->cacheHandler; + } + /** + * returns the state of this session + * + * @see Doctrine_Session::STATE_* constants + * @return integer the session state + */ + public function getState() { + return $this->state; + } + /** + * returns the manager that created this session + * + * @return Doctrine_Manager + */ + public function getManager() { + return $this->getParent(); + } + /** + * returns the database handler of which this session uses + * + * @return object PDO the database handler + */ + public function getDBH() { + return $this->dbh; + } + /** + * query + * queries the database with Doctrine Query Language + * + * @param string $query DQL query + * @param array $params query parameters + */ + final public function query($query,array $params = array()) { + $parser = new Doctrine_Query($this); + + return $parser->query($query, $params); + } + /** + * queries the database with limit and offset + * added to the query and returns a PDOStatement object + * + * @param string $query + * @param integer $limit + * @param integer $offset + * @return PDOStatement + */ + public function select($query,$limit = 0,$offset = 0) { + if($limit > 0 || $offset > 0) + $query = $this->modifyLimitQuery($query,$limit,$offset); + + return $this->dbh->query($query); + } + /** + * @param string $query sql query + * @param array $params query parameters + * + * @return PDOStatement + */ + public function execute($query, array $params = array()) { + if( ! empty($params)) { + $stmt = $this->dbh->prepare($query); + $stmt->execute($params); + return $stmt; + } else { + return $this->dbh->query($query); + } + } + /** + * whether or not this session has table $name initialized + * + * @param $mixed $name + * @return boolean + */ + public function hasTable($name) { + return isset($this->tables[$name]); + } + /** + * returns a table object for given component name + * + * @param string $name component name + * @return object Doctrine_Table + */ + public function getTable($name) { + if(isset($this->tables[$name])) + return $this->tables[$name]; + + $class = $name."Table"; + + if(class_exists($class) && in_array("Doctrine_Table", class_parents($class))) { + return new $class($name); + } else { + + return new Doctrine_Table($name); + } + } + /** + * returns an array of all initialized tables + * + * @return array + */ + public function getTables() { + return $this->tables; + } + /** + * returns an iterator that iterators through all + * initialized table objects + * + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->tables); + } + /** + * returns the count of initialized table objects + * + * @return integer + */ + public function count() { + return count($this->tables); + } + /** + * @param $objTable a Doctrine_Table object to be added into registry + * @return boolean + */ + public function addTable(Doctrine_Table $objTable) { + $name = $objTable->getComponentName(); + + if(isset($this->tables[$name])) + return false; + + $this->tables[$name] = $objTable; + return true; + } + /** + * creates a record + * + * create creates a record + * @param string $name component name + * @return Doctrine_Record Doctrine_Record object + */ + public function create($name) { + return $this->getTable($name)->create(); + } + + + /** + * buildFlushTree + * builds a flush tree that is used in transactions + * + * @return array + */ + public function buildFlushTree(array $tables) { + $tree = array(); + foreach($tables as $k => $table) { + $k = $k.$table; + if( ! ($table instanceof Doctrine_Table)) + $table = $this->getTable($table); + + $nm = $table->getComponentName(); + + $index = array_search($nm,$tree); + if($index === false) { + $tree[] = $nm; + $index = max(array_keys($tree)); + + //print "$k -- adding $nm...