From 98dc74b639f1fea9a8127f994cbdb7ffdeec0ca5 Mon Sep 17 00:00:00 2001 From: doctrine Date: Tue, 30 May 2006 08:42:10 +0000 Subject: [PATCH] Added a folder remotely --- Doctrine/Access.php | 82 +++ Doctrine/Association.php | 38 ++ Doctrine/Cache.php | 71 ++ Doctrine/Collection.php | 446 +++++++++++++ Doctrine/Configurable.php | 174 +++++ Doctrine/DB.php | 140 ++++ Doctrine/DataDict.php | 101 +++ Doctrine/Debugger.php | 151 +++++ Doctrine/EventListener.php | 124 ++++ Doctrine/Exception.php | 6 + Doctrine/ForeignKey.php | 7 + Doctrine/Form.class.php | 77 +++ Doctrine/Identifier.php | 24 + Doctrine/IdentityMap.class.php | 51 ++ Doctrine/IndexGenerator.php | 25 + Doctrine/Iterator.php | 83 +++ Doctrine/Lib.php | 140 ++++ Doctrine/LocalKey.php | 7 + Doctrine/Manager.php | 249 +++++++ Doctrine/Module.class.php | 60 ++ Doctrine/Null.php | 6 + Doctrine/Query.php | 1005 +++++++++++++++++++++++++++++ Doctrine/Record.php | 1110 ++++++++++++++++++++++++++++++++ Doctrine/Relation.php | 101 +++ Doctrine/Repository.php | 114 ++++ Doctrine/Session.php | 934 +++++++++++++++++++++++++++ Doctrine/Statement.class.php | 188 ++++++ Doctrine/Table.php | 870 +++++++++++++++++++++++++ Doctrine/Validator.php | 186 ++++++ 29 files changed, 6570 insertions(+) create mode 100644 Doctrine/Access.php create mode 100644 Doctrine/Association.php create mode 100644 Doctrine/Cache.php create mode 100644 Doctrine/Collection.php create mode 100644 Doctrine/Configurable.php create mode 100644 Doctrine/DB.php create mode 100644 Doctrine/DataDict.php create mode 100644 Doctrine/Debugger.php create mode 100644 Doctrine/EventListener.php create mode 100644 Doctrine/Exception.php create mode 100644 Doctrine/ForeignKey.php create mode 100644 Doctrine/Form.class.php create mode 100644 Doctrine/Identifier.php create mode 100644 Doctrine/IdentityMap.class.php create mode 100644 Doctrine/IndexGenerator.php create mode 100644 Doctrine/Iterator.php create mode 100644 Doctrine/Lib.php create mode 100644 Doctrine/LocalKey.php create mode 100644 Doctrine/Manager.php create mode 100644 Doctrine/Module.class.php create mode 100644 Doctrine/Null.php create mode 100644 Doctrine/Query.php create mode 100644 Doctrine/Record.php create mode 100644 Doctrine/Relation.php create mode 100644 Doctrine/Repository.php create mode 100644 Doctrine/Session.php create mode 100644 Doctrine/Statement.class.php create mode 100644 Doctrine/Table.php create mode 100644 Doctrine/Validator.php 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)."
"; + } + /** + * returns a string representation of Doctrine_Table object + * @param Doctrine_Table $table + * @return string + */ + public function getTableAsString(Doctrine_Table $table) { + $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)."
"; + } + /** + * returns a string representation of Doctrine_Collection object + * @param Doctrine_Collection $collection + * @return string + */ + public function getCollectionAsString(Doctrine_Collection $collection) { + $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...
"; + } + + $rels = $table->getForeignKeys(); + + // group relations + + foreach($rels as $key => $rel) { + if($rel instanceof Doctrine_ForeignKey) { + unset($rels[$key]); + array_unshift($rels, $rel); + } + } + + foreach($rels as $rel) { + $name = $rel->getTable()->getComponentName(); + $index2 = array_search($name,$tree); + $type = $rel->getType(); + + // skip self-referenced relations + if($name === $nm) + continue; + + if($rel instanceof Doctrine_ForeignKey) { + if($index2 !== false) { + if($index2 >= $index) + continue; + + unset($tree[$index]); + array_splice($tree,$index2,0,$nm); + $index = $index2; + + //print "$k -- pushing $nm into $index2...
"; + + } else { + $tree[] = $name; + //print "$k -- adding $nm :$name...
"; + } + + } elseif($rel instanceof Doctrine_LocalKey) { + if($index2 !== false) { + if($index2 <= $index) + continue; + + unset($tree[$index2]); + array_splice($tree,$index,0,$name); + + //print "$k -- pushing $name into $index...
"; + + } else { + //array_splice($tree, $index, 0, $name); + array_unshift($tree,$name); + $index++; + + //print "$k -- pushing $name into 0...
"; + } + } elseif($rel instanceof Doctrine_Association) { + $t = $rel->getAssociationFactory(); + $n = $t->getComponentName(); + + if($index2 !== false) + unset($tree[$index2]); + + array_splice($tree,$index, 0,$name); + $index++; + + $index3 = array_search($n,$tree); + + if($index3 !== false) { + if($index3 >= $index) + continue; + + unset($tree[$index]); + array_splice($tree,$index3,0,$n); + $index = $index2; + + //print "$k -- pushing $nm into $index3...
"; + + } else { + $tree[] = $n; + //print "$k -- adding $nm :$name...
"; + } + } + //print_r($tree); + } + //print_r($tree); + + } + return array_values($tree); + } + + /** + * flush + * saves all the records from all tables + * this operation is isolated using a transaction + * + * @return void + */ + public function flush() { + $this->beginTransaction(); + $this->saveAll(); + $this->commit(); + } + /** + * saveAll + * saves all the records from all tables + * + * @return void + */ + private function saveAll() { + $tree = $this->buildFlushTree($this->tables); + + foreach($tree as $name) { + $table = $this->tables[$name]; + + foreach($table->getRepository() as $record) { + $this->save($record); + } + } + foreach($tree as $name) { + $table = $this->tables[$name]; + foreach($table->getRepository() as $record) { + $record->saveAssociations(); + } + } + } + /** + * clear + * clears all repositories + * + * @return void + */ + public function clear() { + foreach($this->tables as $k => $table) { + $table->getRepository()->evictAll(); + $table->clear(); + } + $this->tables = array(); + } + /** + * close + * closes the session + * + * @return void + */ + public function close() { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreClose($this); + + $this->clear(); + $this->state = Doctrine_Session::STATE_CLOSED; + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onClose($this); + } + /** + * get the current transaction nesting level + * + * @return integer + */ + public function getTransactionLevel() { + return $this->transaction_level; + } + /** + * beginTransaction + * starts a new transaction + * @return void + */ + public function beginTransaction() { + if($this->transaction_level == 0) { + + if($this->getAttribute(Doctrine::ATTR_LOCKMODE) == Doctrine::LOCK_PESSIMISTIC) { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreTransactionBegin($this); + $this->dbh->beginTransaction(); + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionBegin($this); + } + $this->state = Doctrine_Session::STATE_ACTIVE; + } else { + $this->state = Doctrine_Session::STATE_BUSY; + } + $this->transaction_level++; + } + /** + * commits the current transaction + * if lockmode is optimistic this method starts a transaction + * and commits it instantly + * @return void + */ + public function commit() { + + $this->transaction_level--; + + if($this->transaction_level == 0) { + + + if($this->getAttribute(Doctrine::ATTR_LOCKMODE) == Doctrine::LOCK_OPTIMISTIC) { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreTransactionBegin($this); + + $this->dbh->beginTransaction(); + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionBegin($this); + } + + if($this->getAttribute(Doctrine::ATTR_VLD)) + $this->validator = new Doctrine_Validator(); + + try { + + $this->bulkInsert(); + $this->bulkUpdate(); + $this->bulkDelete(); + + if($this->getAttribute(Doctrine::ATTR_VLD)) { + if($this->validator->hasErrors()) { + $this->rollback(); + throw new Doctrine_Validator_Exception($this->validator); + } + } + + } catch(PDOException $e) { + $this->rollback(); + + throw new Doctrine_Exception($e->getMessage()); + } + $this->dbh->commit(); + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionCommit($this); + + $this->delete = array(); + $this->state = Doctrine_Session::STATE_OPEN; + + $this->validator = null; + + } elseif($this->transaction_level == 1) + $this->state = Doctrine_Session::STATE_ACTIVE; + } + /** + * rollback + * rolls back all transactions + * @return void + */ + public function rollback() { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreTransactionRollback($this); + + $this->transaction_level = 0; + $this->dbh->rollback(); + $this->state = Doctrine_Session::STATE_OPEN; + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionRollback($this); + } + /** + * bulkInsert + * inserts all the objects in the pending insert list into database + * @return void + */ + public function bulkInsert() { + if(empty($this->insert)) + return false; + + foreach($this->insert as $name => $inserts) { + if( ! isset($inserts[0])) + continue; + + $record = $inserts[0]; + $table = $record->getTable(); + $seq = $table->getSequenceName(); + + $increment = false; + $keys = $table->getPrimaryKeys(); + $id = null; + + if(count($keys) == 1 && $keys[0] == $table->getIdentifier()) { + $increment = true; + } + + foreach($inserts as $k => $record) { + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($record); + // listen the onPreInsert event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreInsert($record); + + + $this->insert($record); + if($increment) { + if($k == 0) { + // record uses auto_increment column + + $id = $table->getMaxIdentifier(); + } + + $record->setID($id); + $id++; + } else + $record->setID(true); + + // listen the onInsert event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onInsert($record); + + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onSave($record); + } + } + $this->insert = array(); + return true; + } + /** + * returns maximum identifier values + * + * @param array $names an array of component names + * @return array + */ + public function getMaximumValues(array $names) { + $values = array(); + foreach($names as $name) { + $table = $this->tables[$name]; + $keys = $table->getPrimaryKeys(); + $tablename = $table->getTableName(); + + if(count($keys) == 1 && $keys[0] == $table->getIdentifier()) { + // record uses auto_increment column + + $sql = "SELECT MAX(".$table->getIdentifier().") FROM ".$tablename; + $stmt = $this->dbh->query($sql); + $data = $stmt->fetch(PDO::FETCH_NUM); + $values[$tablename] = $data[0]; + + $stmt->closeCursor(); + } + } + return $values; + } + /** + * bulkUpdate + * updates all objects in the pending update list + * + * @return void + */ + public function bulkUpdate() { + foreach($this->update as $name => $updates) { + $ids = array(); + + foreach($updates as $k => $record) { + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($record); + // listen the onPreUpdate event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreUpdate($record); + + $this->update($record); + // listen the onUpdate event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onUpdate($record); + + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onSave($record); + + $ids[] = $record->getID(); + } + if(isset($record)) + $record->getTable()->getCache()->deleteMultiple($ids); + } + $this->update = array(); + } + /** + * bulkDelete + * deletes all records from the pending delete list + * + * @return void + */ + public function bulkDelete() { + foreach($this->delete as $name => $deletes) { + $record = false; + $ids = array(); + foreach($deletes as $k => $record) { + $ids[] = $record->getID(); + $record->setID(false); + } + if($record instanceof Doctrine_Record) { + $table = $record->getTable(); + + $params = substr(str_repeat("?, ",count($ids)),0,-2); + $query = "DELETE FROM ".$record->getTable()->getTableName()." WHERE ".$table->getIdentifier()." IN(".$params.")"; + $this->execute($query,$ids); + + $record->getTable()->getCache()->deleteMultiple($ids); + } + } + $this->delete = array(); + } + /** + * saves a collection + * + * @param Doctrine_Collection $coll + * @return void + */ + public function saveCollection(Doctrine_Collection $coll) { + $this->beginTransaction(); + + foreach($coll as $key=>$record): + $record->save(); + endforeach; + + $this->commit(); + } + /** + * deletes all records from collection + * + * @param Doctrine_Collection $coll + * @return void + */ + public function deleteCollection(Doctrine_Collection $coll) { + $this->beginTransaction(); + foreach($coll as $k=>$record) { + $record->delete(); + } + $this->commit(); + } + /** + * saves the given record + * + * @param Doctrine_Record $record + * @return void + */ + public function save(Doctrine_Record $record) { + switch($record->getState()): + case Doctrine_Record::STATE_TDIRTY: + $this->addInsert($record); + break; + case Doctrine_Record::STATE_DIRTY: + case Doctrine_Record::STATE_PROXY: + $this->addUpdate($record); + break; + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_TCLEAN: + // do nothing + break; + endswitch; + } + /** + * saves all related records to $record + * + * @param Doctrine_Record $record + */ + final public function saveRelated(Doctrine_Record $record) { + $saveLater = array(); + foreach($record->getReferences() as $k=>$v) { + $fk = $record->getTable()->getForeignKey($k); + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + switch($fk->getType()): + case Doctrine_Relation::ONE_COMPOSITE: + case Doctrine_Relation::MANY_COMPOSITE: + $local = $fk->getLocal(); + $foreign = $fk->getForeign(); + + if($record->getTable()->hasPrimaryKey($fk->getLocal())) { + switch($record->getState()): + case Doctrine_Record::STATE_TDIRTY: + case Doctrine_Record::STATE_TCLEAN: + $saveLater[$k] = $fk; + break; + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_DIRTY: + $v->save(); + break; + endswitch; + } else { + // ONE-TO-ONE relationship + $obj = $record->get($fk->getTable()->getComponentName()); + + if($obj->getState() != Doctrine_Record::STATE_TCLEAN) + $obj->save(); + + } + break; + endswitch; + } elseif($fk instanceof Doctrine_Association) { + $v->save(); + } + } + return $saveLater; + } + /** + * updates the given record + * + * @param Doctrine_Record $record + * @return boolean + */ + private function update(Doctrine_Record $record) { + $array = $record->getModified(); + + if(empty($array)) + return false; + + $set = array(); + foreach($array as $name => $value): + $set[] = $name." = ?"; + + if($value instanceof Doctrine_Record) { + switch($value->getState()): + case Doctrine_Record::STATE_TCLEAN: + case Doctrine_Record::STATE_TDIRTY: + $record->save(); + default: + $array[$name] = $value->getID(); + $record->set($name, $value->getID()); + endswitch; + } + endforeach; + + if(isset($this->validator)) { + if( ! $this->validator->validateRecord($record)) { + return false; + } + } + + $params = array_values($array); + $id = $record->getID(); + + + if( ! is_array($id)) + $id = array($id); + + $id = array_values($id); + $params = array_merge($params, $id); + + + $sql = "UPDATE ".$record->getTable()->getTableName()." SET ".implode(", ",$set)." WHERE ".implode(" = ? && ",$record->getTable()->getPrimaryKeys())." = ?"; + + $stmt = $this->dbh->prepare($sql); + $stmt->execute($params); + + $record->setID(true); + + return true; + } + /** + * inserts a record into database + * + * @param Doctrine_Record $record + * @return boolean + */ + private function insert(Doctrine_Record $record) { + $array = $record->getPrepared(); + + if(empty($array)) + return false; + + $seq = $record->getTable()->getSequenceName(); + + if( ! empty($seq)) { + $id = $this->getNextID($seq); + $name = $record->getTable()->getIdentifier(); + $array[$name] = $id; + } + + if(isset($this->validator)) { + if( ! $this->validator->validateRecord($record)) { + return false; + } + } + + $strfields = join(", ", array_keys($array)); + $strvalues = substr(str_repeat("?, ",count($array)),0,-2); + + $sql = "INSERT INTO ".$record->getTable()->getTableName()." (".$strfields.") VALUES (".$strvalues.")"; + + $stmt = $this->dbh->prepare($sql); + + $stmt->execute(array_values($array)); + + return true; + } + /** + * deletes all related composites + * this method is always called internally when a record is deleted + * + * @return void + */ + final public function deleteComposites(Doctrine_Record $record) { + foreach($record->getTable()->getForeignKeys() as $fk) { + switch($fk->getType()): + case Doctrine_Relation::ONE_COMPOSITE: + case Doctrine_Relation::MANY_COMPOSITE: + $obj = $record->get($fk->getTable()->getComponentName()); + $obj->delete(); + break; + endswitch; + } + } + /** + * 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 + */ + final public function delete(Doctrine_Record $record) { + switch($record->getState()): + case Doctrine_Record::STATE_PROXY: + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_DIRTY: + $this->beginTransaction(); + + $this->deleteComposites($record); + $this->addDelete($record); + + $this->commit(); + return true; + break; + default: + return false; + endswitch; + } + /** + * adds record into pending insert list + * @param Doctrine_Record $record + */ + public function addInsert(Doctrine_Record $record) { + $name = $record->getTable()->getComponentName(); + $this->insert[$name][] = $record; + } + /** + * adds record into penging update list + * @param Doctrine_Record $record + */ + public function addUpdate(Doctrine_Record $record) { + $name = $record->getTable()->getComponentName(); + $this->update[$name][] = $record; + } + /** + * adds record into pending delete list + * @param Doctrine_Record $record + */ + public function addDelete(Doctrine_Record $record) { + $name = $record->getTable()->getComponentName(); + $this->delete[$name][] = $record; + } + /** + * returns the pending insert list + * + * @return array + */ + public function getInserts() { + return $this->insert; + } + /** + * returns the pending update list + * + * @return array + */ + public function getUpdates() { + return $this->update; + } + /** + * returns the pending delete list + * + * @return array + */ + public function getDeletes() { + return $this->delete; + } + + /** + * returns a string representation of this object + * @return string + */ + public function __toString() { + return Doctrine_Lib::getSessionAsString($this); + } +} +?> diff --git a/Doctrine/Statement.class.php b/Doctrine/Statement.class.php new file mode 100644 index 000000000..39cd71374 --- /dev/null +++ b/Doctrine/Statement.class.php @@ -0,0 +1,188 @@ +query = $query; + $this->stmt = $stmt; + } + public function set($name, $value) { } + public function get($name) { } + /** + * getCollection + * returns Doctrine_Collection object + * + * @parma string $name component name + * @param integer $index + * @return Doctrine_Collection + */ + 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; + } + /** + * execute + * executes the dql query, populates all collections + * and returns the root collection + * + * @param array $params + * @return Doctrine_Collection + */ + public function execute($params = 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); + + $array = $this->parseData($stmt); + + foreach($array as $data): + + /** + * remove duplicated data rows and map data into objects + */ + foreach($data as $key => $row): + if(empty($row)) + continue; + + $key = ucwords($key); + $name = $this->tables[$key]->getComponentName(); + + if( ! isset($previd[$name])) + $previd[$name] = array(); + + + if($previd[$name] !== $row) { + $this->tables[$name]->setData($row); + $record = $this->tables[$name]->getRecord(); + + if($name == $root) { + $this->tables[$name]->setData($row); + $record = $this->tables[$name]->getRecord(); + $coll->add($record); + } else { + $last = $coll->getLast(); + + if( ! $last->hasReference($name)) { + $last->initReference($this->getCollection($name),$this->connectors[$name]); + } + $last->addReference($record); + } + } + + $previd[$name] = $row; + endforeach; + endforeach; + + return $coll; + endswitch; + } + /** + * parseData + * parses the data returned by PDOStatement + * + * @param PDOStatement $stmt + * @return array + */ + public function parseData(PDOStatement $stmt) { + $array = array(); + 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[$e[0]][$e[1]] = $value; + } else { + $data[0][$e[0]] = $value; + } + unset($data[$key]); + endforeach; + $array[] = $data; + endwhile; + $stmt->closeCursor(); + return $array; + } +} +?> diff --git a/Doctrine/Table.php b/Doctrine/Table.php new file mode 100644 index 000000000..440f5281e --- /dev/null +++ b/Doctrine/Table.php @@ -0,0 +1,870 @@ +session = Doctrine_Manager::getInstance()->getCurrentSession(); + + $this->setParent($this->session); + + $this->name = $name; + + if( ! class_exists($name) || empty($name)) + throw new Doctrine_Exception("Couldn't find class $name"); + + $record = new $name($this); + + + $names = array(); + + $class = $name; + + + + // get parent classes + + do { + if($class == "Doctrine_Record") break; + + $name = $class; + $names[] = $name; + } while($class = get_parent_class($class)); + + // reverse names + $names = array_reverse($names); + + + // create database table + if(method_exists($record,"setTableDefinition")) { + $record->setTableDefinition(); + + $this->columnCount = count($this->columns); + + if(isset($this->columns)) { + $method = new ReflectionMethod($this->name,"setTableDefinition"); + $class = $method->getDeclaringClass(); + + if( ! isset($this->tableName)) + $this->tableName = strtolower($class->getName()); + + switch(count($this->primaryKeys)): + case 0: + $this->columns = array_merge(array("id" => array("integer",11,"autoincrement|primary")), $this->columns); + $this->primaryKeys[] = "id"; + $this->identifier = "id"; + $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT; + break; + default: + if(count($this->primaryKeys) > 1) { + $this->identifier = $this->primaryKeys; + $this->identifierType = Doctrine_Identifier::COMPOSITE; + + } else { + foreach($this->primaryKeys as $pk) { + $o = $this->columns[$pk][2]; + $e = explode("|",$o); + $found = false; + + + foreach($e as $option) { + if($found) + break; + + $e2 = explode(":",$option); + + switch(strtolower($e2[0])): + case "unique": + $this->identifierType = Doctrine_Identifier::UNIQUE; + $found = true; + break; + case "autoincrement": + $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT; + $found = true; + break; + case "seq": + $this->identifierType = Doctrine_Identifier::SEQUENCE; + $found = true; + break; + endswitch; + } + if( ! isset($this->identifierType)) + $this->identifierType = Doctrine_Identifier::NORMAL; + + $this->identifier = $pk; + } + } + endswitch; + + if($this->getAttribute(Doctrine::ATTR_CREATE_TABLES)) { + $dict = new Doctrine_DataDict($this->getSession()->getDBH()); + $dict->createTable($this->tableName, $this->columns); + } + + } + } else { + throw new Doctrine_Exception("Class '$name' has no table definition."); + } + + $record->setUp(); + + // save parents + array_pop($names); + $this->parents = $names; + + $this->query = "SELECT ".implode(", ",array_keys($this->columns))." FROM ".$this->getTableName(); + + // check if an instance of this table is already initialized + if( ! $this->session->addTable($this)) + throw new Doctrine_Table_Exception(); + + $this->initComponents(); + } + /** + * initializes components this table uses + * + * @return void + */ + final public function initComponents() { + $this->repository = new Doctrine_Repository($this); + switch($this->getAttribute(Doctrine::ATTR_CACHE)): + case Doctrine::CACHE_SQLITE: + $this->cache = new Doctrine_Cache_Sqlite($this); + break; + case Doctrine::CACHE_NONE: + $this->cache = new Doctrine_Cache($this); + break; + endswitch; + } + /** + * @return Doctrine_Repository + */ + public function getRepository() { + return $this->repository; + } + /** + * setColumn + * @param string $name + * @param string $type + * @param integer $length + * @param mixed $options + * @return void + */ + final public function setColumn($name, $type, $length, $options = "") { + $this->columns[$name] = array($type,$length,$options); + + $e = explode("|",$options); + if(in_array("primary",$e)) { + $this->primaryKeys[] = $name; + } + } + /** + * @return mixed + */ + final public function getIdentifier() { + return $this->identifier; + } + /** + * @return integer + */ + final public function getIdentifierType() { + return $this->identifierType; + } + /** + * hasColumn + * @return boolean + */ + final public function hasColumn($name) { + return isset($this->columns[$name]); + } + /** + * @param mixed $key + * @return void + */ + final public function setPrimaryKey($key) { + switch(gettype($key)): + case "array": + $this->primaryKeys = array_values($key); + break; + case "string": + $this->primaryKeys[] = $key; + break; + endswitch; + } + /** + * returns all primary keys + * @return array + */ + final public function getPrimaryKeys() { + return $this->primaryKeys; + } + /** + * @return boolean + */ + final public function hasPrimaryKey($key) { + return in_array($key,$this->primaryKeys); + } + /** + * @param $sequence + * @return void + */ + final public function setSequenceName($sequence) { + $this->sequenceName = $sequence; + } + /** + * @return string sequence name + */ + final public function getSequenceName() { + return $this->sequenceName; + } + /** + * setInheritanceMap + * @param array $inheritanceMap + * @return void + */ + final public function setInheritanceMap(array $inheritanceMap) { + $this->inheritanceMap = $inheritanceMap; + } + /** + * @return array inheritance map (array keys as fields) + */ + final public function getInheritanceMap() { + return $this->inheritanceMap; + } + /** + * return all composite paths in the form [component1].[component2]. . .[componentN] + * @return array + */ + final public function getCompositePaths() { + $array = array(); + $name = $this->getComponentName(); + foreach($this->bound as $k=>$a) { + try { + $fk = $this->getForeignKey($k); + switch($fk->getType()): + case Doctrine_Relation::ONE_COMPOSITE: + case Doctrine_Relation::MANY_COMPOSITE: + $n = $fk->getTable()->getComponentName(); + $array[] = $name.".".$n; + $e = $fk->getTable()->getCompositePaths(); + if( ! empty($e)) { + foreach($e as $name) { + $array[] = $name.".".$n.".".$name; + } + } + break; + endswitch; + } catch(InvalidKeyException $e) { + + } + } + return $array; + } + /** + * returns all bound relations + * + * @return array + */ + final public function getBounds() { + return $this->bound; + } + /** + * returns a bound relation array + * + * @param string $name + * @return array + */ + final public function getBound($name) { + if( ! isset($this->bound[$name])) + throw new InvalidKeyException(); + + return $this->bound[$name]; + } + /** + * returns a bound relation array + * + * @param string $name + * @return array + */ + final public function getBoundForName($name) { + foreach($this->bound as $k => $bound) { + if($bound[3] == $name) { + return $this->bound[$k]; + } + } + throw new InvalidKeyException(); + } + /** + * returns the alias for given component name + * + * @param string $name + * @return string + */ + final public function getAlias($name) { + if(isset($this->boundAliases[$name])) + return $this->boundAliases[$name]; + + return $name; + } + /** + * returns component name for given alias + * + * @param string $alias + * @return string + */ + final public function getAliasName($alias) { + if($name = array_search($this->boundAliases,$alias)) + return $name; + + throw new InvalidKeyException(); + } + /** + * unbinds all relations + * + * @return void + */ + final public function unbindAll() { + $this->bound = array(); + $this->relations = array(); + $this->boundAliases = array(); + } + /** + * unbinds a relation + * returns true on success, false on failure + * + * @param $name + * @return boolean + */ + final public function unbind() { + if( ! isset($this->bound[$name])) + return false; + + unset($this->bound[$name]); + + if(isset($this->relations[$name])) + unset($this->relations[$name]); + + if(isset($this->boundAliases[$name])) + unset($this->boundAliases[$name]); + + return true; + } + /** + * binds a relation + * + * @param string $name + * @param string $field + * @return void + */ + final public function bind($name,$field,$type,$localKey) { + if(isset($this->relations[$name])) + throw new InvalidKeyException(); + + $e = explode(" as ",$name); + $name = $e[0]; + + if(isset($e[1])) { + $alias = $e[1]; + $this->boundAliases[$name] = $alias; + } else + $alias = $name; + + + $this->bound[$alias] = array($field,$type,$localKey,$name); + } + /** + * getComponentName + * @return string the component name + */ + final public function getComponentName() { + return $this->name; + } + /** + * @return Doctrine_Session + */ + final public function getSession() { + return $this->session; + } + /** + * @return Doctrine_Cache + */ + final public function getCache() { + return $this->cache; + } + /** + * @param string $name component name of which a foreign key object is bound + * @return Doctrine_Relation + */ + final public function getForeignKey($name) { + if(isset($this->relations[$name])) + return $this->relations[$name]; + + if(isset($this->bound[$name])) { + $type = $this->bound[$name][1]; + $local = $this->bound[$name][2]; + list($component, $foreign) = explode(".",$this->bound[$name][0]); + $alias = $name; + $name = $this->bound[$alias][3]; + + $table = $this->session->getTable($name); + + if($component == $this->name || in_array($component, $this->parents)) { + + // ONE-TO-ONE + if($type == Doctrine_Relation::ONE_COMPOSITE || + $type == Doctrine_Relation::ONE_AGGREGATE) { + if( ! isset($local)) + $local = $table->getIdentifier(); + + $relation = new Doctrine_LocalKey($table,$foreign,$local,$type); + } else + throw new Doctrine_Mapping_Exception("Only one-to-one relations are possible when local reference key is used."); + + } elseif($component == $name || ($component == $alias && $name == $this->name)) { + if( ! isset($local)) + $local = $this->identifier; + + // ONE-TO-MANY or ONE-TO-ONE + $relation = new Doctrine_ForeignKey($table,$local,$foreign,$type); + + } else { + // MANY-TO-MANY + // only aggregate relations allowed + + if($type != Doctrine_Relation::MANY_AGGREGATE) + throw new Doctrine_Mapping_Exception("Only aggregate relations are allowed for many-to-many relations"); + + $classes = array_merge($this->parents, array($this->name)); + + foreach(array_reverse($classes) as $class) { + try { + $bound = $table->getBoundForName($class); + break; + } catch(InvalidKeyException $exc) { } + + } + if( ! isset($local)) + $local = $this->identifier; + + $e2 = explode(".",$bound[0]); + $fields = explode("-",$e2[1]); + + if($e2[0] != $component) + throw new Doctrine_Mapping_Exception($e2[0]." doesn't match ".$component); + + $associationTable = $this->session->getTable($e2[0]); + + if(count($fields) > 1) { + // SELF-REFERENCING THROUGH JOIN TABLE + $this->relations[$e2[0]] = new Doctrine_ForeignKey($associationTable,$local,$fields[0],Doctrine_Relation::MANY_COMPOSITE); + + $relation = new Doctrine_Association($table,$associationTable,$fields[0],$fields[1],$type); + } else { + // NORMAL MANY-TO-MANY RELATIONSHIP + $this->relations[$e2[0]] = new Doctrine_ForeignKey($associationTable,$local,$e2[1],Doctrine_Relation::MANY_COMPOSITE); + + $relation = new Doctrine_Association($table,$associationTable,$e2[1],$foreign,$type); + } + + } + $this->relations[$alias] = $relation; + return $this->relations[$alias]; + } + throw new InvalidKeyException(); + } + /** + * returns an array containing all foreign key objects + * + * @return array + */ + final public function getForeignKeys() { + $a = array(); + foreach($this->bound as $k=>$v) { + $this->getForeignKey($k); + } + + return $this->relations; + } + /** + * sets the database table name + * + * @param string $name database table name + * @return void + */ + final public function setTableName($name) { + $this->tableName = $name; + } + + /** + * returns the database table name + * + * @return string + */ + final public function getTableName() { + return $this->tableName; + } + /** + * create + * creates a new record + * + * @param $array an array where keys are field names and values representing field values + * @return Doctrine_Record + */ + public function create(array $array = array()) { + $this->data = $array; + $this->isNewEntry = true; + $record = new $this->name($this); + $this->isNewEntry = false; + $this->data = array(); + return $record; + } + /** + * finds a record by its identifier + * + * @param $id database row id + * @throws Doctrine_Find_Exception + * @return Doctrine_Record a record for given database identifier + */ + public function find($id) { + if($id !== null) { + if( ! is_array($id)) + $id = array($id); + else + $id = array_values($id); + + $query = $this->query." WHERE ".implode(" = ? AND ",$this->primaryKeys)." = ?"; + $query = $this->applyInheritance($query); + + $params = array_merge($id, array_values($this->inheritanceMap)); + + $this->data = $this->session->execute($query,$params)->fetch(PDO::FETCH_ASSOC); + + if($this->data === false) + throw new Doctrine_Find_Exception(); + } + return $this->getRecord(); + } + /** + * applyInheritance + * @param $where query where part to be modified + * @return string query where part with column aggregation inheritance added + */ + final public function applyInheritance($where) { + if( ! empty($this->inheritanceMap)) { + $a = array(); + foreach($this->inheritanceMap as $field => $value) { + $a[] = $field." = ?"; + } + $i = implode(" AND ",$a); + $where .= " AND $i"; + } + return $where; + } + /** + * findAll + * returns a collection of records + * + * @return Doctrine_Collection + */ + public function findAll() { + $graph = new Doctrine_Query($this->session); + $users = $graph->query("FROM ".$this->name); + return $users; + } + /** + * findBySql + * finds records with given sql where clause + * returns a collection of records + * + * @param string $sql SQL after WHERE clause + * @param array $params query parameters + * @return Doctrine_Collection + */ + public function findBySql($sql, array $params = array()) { + $q = new Doctrine_Query($this->session); + $users = $q->query("FROM ".$this->name." WHERE ".$sql, $params); + return $users; + } + /** + * clear + * clears the first level cache (identityMap) + * + * @return void + */ + public function clear() { + $this->identityMap = array(); + } + /** + * getRecord + * first checks if record exists in identityMap, if not + * returns a new record + * + * @return Doctrine_Record + */ + public function getRecord() { + $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; + } + /** + * @param $id database row id + * @throws Doctrine_Find_Exception + * @return DAOProxy a proxy for given identifier + */ + final public function getProxy($id = null) { + if($id !== null) { + $query = "SELECT ".implode(", ",$this->primaryKeys)." FROM ".$this->getTableName()." WHERE ".implode(" = ? && ",$this->primaryKeys)." = ?"; + $query = $this->applyInheritance($query); + + $params = array_merge(array($id), array_values($this->inheritanceMap)); + + $this->data = $this->session->execute($query,$params)->fetch(PDO::FETCH_ASSOC); + + if($this->data === false) + throw new Doctrine_Find_Exception(); + } + return $this->getRecord(); + } + /** + * getTableDescription + * @return Doctrine_Table_Description + */ + final public function getTableDescription() { + return $this->columns; + } + /** + * @return Doctrine_Query a Doctrine_Query object + */ + public function getQueryObject() { + $graph = new Doctrine_Query($this->getSession()); + $graph->load($this->getComponentName()); + return $graph; + } + /** + * execute + * @param string $query + * @param array $array + * @param integer $limit + * @param integer $offset + */ + public function execute($query, array $array = array(), $limit = null, $offset = null) { + $coll = new Doctrine_Collection($this); + $query = $this->session->modifyLimitQuery($query,$limit,$offset); + if( ! empty($array)) { + $stmt = $this->session->getDBH()->prepare($query); + $stmt->execute($array); + } else { + $stmt = $this->session->getDBH()->query($query); + } + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + $stmt->closeCursor(); + + foreach($data as $row) { + $this->data = $row; + $record = $this->getRecord(); + $coll->add($record); + } + return $coll; + } + /** + * @return integer + */ + final public function getColumnCount() { + return $this->columnCount; + } + /** + * returns all columns and their definitions + * + * @return array + */ + final public function getColumns() { + return $this->columns; + } + /** + * returns an array containing all the column names + * + * @return array + */ + public function getColumnNames() { + return array_keys($this->columns); + } + /** + * setData + * doctrine uses this function internally + * users are strongly discouraged to use this function + * + * @param array $data internal data + * @return void + */ + public function setData(array $data) { + $this->data = $data; + } + /** + * returns the maximum primary key value + * + * @return integer + */ + final public function getMaxIdentifier() { + $sql = "SELECT MAX(".$this->getIdentifier().") FROM ".$this->getTableName(); + $stmt = $this->session->getDBH()->query($sql); + $data = $stmt->fetch(PDO::FETCH_NUM); + return isset($data[0])?$data[0]:1; + } + /** + * return whether or not a newly created object is new or not + * + * @return boolean + */ + final public function isNewEntry() { + return $this->isNewEntry; + } + /** + * returns simple cached query + * + * @return string + */ + final public function getQuery() { + return $this->query; + } + /** + * returns internal data, used by Doctrine_Record instances + * when retrieving data from database + * + * @return array + */ + final public function getData() { + return $this->data; + } + /** + * returns a string representation of this object + * + * @return string + */ + public function __toString() { + return Doctrine_Lib::getTableAsString($this); + } +} +?> diff --git a/Doctrine/Validator.php b/Doctrine/Validator.php new file mode 100644 index 000000000..e3548c9de --- /dev/null +++ b/Doctrine/Validator.php @@ -0,0 +1,186 @@ +getModified(); + $columns = $record->getTable()->getColumns(); + $name = $record->getTable()->getComponentName(); + + $err = array(); + foreach($modified as $key => $value) { + $column = $columns[$key]; + + if(strlen($value) > $column[1]) { + $err[$key] = Doctrine_Validator::ERR_LENGTH; + continue; + } + + if(self::gettype($value) !== $column[0]) { + $err[$key] = Doctrine_Validator::ERR_TYPE; + continue; + } + + $e = explode("|",$column[2]); + + foreach($e as $k => $arg) { + if(empty($arg) || $arg == "primary" || $arg == "protected" || $arg == "autoincrement") + continue; + + $args = explode(":",$arg); + if( ! isset($args[1])) + $args[1] = ''; + + $validator = self::getValidator($args[0]); + if( ! $validator->validate($record, $key, $value, $args[1])) { + switch(strtolower($args[0])): + case "unique": + $err[$key] = Doctrine_Validator::ERR_UNIQUE; + break; + case "notnull": + $err[$key] = Doctrine_Validator::ERR_NULL; + break; + case "notblank": + $err[$key] = Doctrine_Validator::ERR_BLANK; + break; + case "enum": + $err[$key] = Doctrine_Validator::ERR_VALID; + break; + default: + $err[$key] = Doctrine_Validator::ERR_VALID; + break; + endswitch; + } + + // errors found quit validation looping for this column + if(isset($err[$key])) + break; + } + } + + if( ! empty($err)) { + $this->stack[$name][] = $err; + return false; + } + + return true; + } + /** + * whether or not this validator has errors + * + * @return boolean + */ + public function hasErrors() { + return (count($this->stack) > 0); + } + /** + * returns the error stack + * + * @return array + */ + public function getErrorStack() { + return $this->stack; + } + /** + * returns the type of loosely typed variable + * + * @param mixed $var + * @return string + */ + public static function gettype($var) { + $type = gettype($var); + switch($type): + case "string": + if(preg_match("/^[0-9]+$/",$var)) return "integer"; + elseif(is_numeric($var)) return "float"; + else return $type; + break; + default: + return $type; + endswitch; + } +} +?>