From b8d1ab881b3b7392f29d68f57b223c74a3f324de Mon Sep 17 00:00:00 2001 From: zYne Date: Fri, 11 May 2007 19:38:16 +0000 Subject: [PATCH] --- draft/new-core/Connection.php | 1042 +++++++++++++++++++++++ draft/new-core/Query.php | 495 ++--------- draft/new-core/Table.php | 1499 +++++++++++++++++++++++++++++++++ 3 files changed, 2588 insertions(+), 448 deletions(-) create mode 100644 draft/new-core/Connection.php create mode 100644 draft/new-core/Table.php diff --git a/draft/new-core/Connection.php b/draft/new-core/Connection.php new file mode 100644 index 000000000..92071914c --- /dev/null +++ b/draft/new-core/Connection.php @@ -0,0 +1,1042 @@ +. + */ +Doctrine::autoload('Doctrine_Configurable'); +/** + * Doctrine_Connection + * + * @package Doctrine + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision: 1270 $ + * @author Konsta Vesterinen + * @author Lukas Smith (MDB2 library) + */ +abstract class Doctrine_Connection extends Doctrine_Configurable implements Countable, IteratorAggregate +{ + /** + * @var $dbh the database handler + */ + protected $dbh; + /** + * @var array $tables an array containing all the initialized Doctrine_Table objects + * keys representing Doctrine_Table component names and values as Doctrine_Table objects + */ + protected $tables = array(); + /** + * @var array $exported + */ + protected $exported = array(); + /** + * @var string $driverName the name of this connection driver + */ + protected $driverName; + /** + * @var array $supported an array containing all features this driver supports, + * keys representing feature names and values as + * one of the following (true, false, 'emulated') + */ + protected $supported = array(); + /** + * @var array $modules an array containing all modules + * transaction Doctrine_Transaction driver, handles savepoint and transaction isolation abstraction + * + * expression Doctrine_Expression driver, handles expression abstraction + * + * dataDict Doctrine_DataDict driver, handles datatype abstraction + * + * export Doctrine_Export driver, handles db structure modification abstraction (contains + * methods such as alterTable, createConstraint etc.) + * import Doctrine_Import driver, handles db schema reading + * + * sequence Doctrine_Sequence driver, handles sequential id generation and retrieval + * + * @see Doctrine_Connection::__get() + * @see Doctrine_DataDict + * @see Doctrine_Expression + * @see Doctrine_Export + * @see Doctrine_Transaction + * @see Doctrine_Sequence + */ + private $modules = array('transaction' => false, + 'expression' => false, + 'dataDict' => false, + 'export' => false, + 'import' => false, + 'sequence' => false, + 'unitOfWork' => false, + ); + /** + * @var array $properties an array of connection properties + */ + protected $properties = array('sql_comments' => array(array('start' => '--', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false) + ), + 'identifier_quoting' => array('start' => '"', + 'end' => '"', + 'escape' => '"' + ), + 'string_quoting' => array('start' => "'", + 'end' => "'", + 'escape' => false, + 'escape_pattern' => false + ), + 'wildcards' => array('%', '_'), + 'varchar_max_length' => 255, + ); + /** + * @var array $serverInfo + */ + protected $serverInfo = array(); + /** + * @var array $availableDrivers an array containing all availible drivers + */ + private static $availableDrivers = array( + 'Mysql', + 'Pgsql', + 'Oracle', + 'Informix', + 'Mssql', + 'Sqlite', + 'Firebird' + ); + + /** + * the constructor + * + * @param Doctrine_Manager $manager the manager object + * @param PDO|Doctrine_Adapter_Interface $adapter database driver + */ + public function __construct(Doctrine_Manager $manager, $adapter) + { + if ( ! ($adapter instanceof PDO) && ! in_array('Doctrine_Adapter_Interface', class_implements($adapter))) { + throw new Doctrine_Connection_Exception("First argument should be an instance of PDO or implement Doctrine_Adapter_Interface"); + } + $this->dbh = $adapter; + + //$this->modules['transaction'] = new Doctrine_Connection_Transaction($this); + $this->modules['unitOfWork'] = new Doctrine_Connection_UnitOfWork($this); + + $this->setParent($manager); + + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); + $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onOpen($this); + } + /** + * getName + * returns the name of this driver + * + * @return string the name of this driver + */ + public function getName() + { + return $this->driverName; + } + /** + * __get + * lazy loads given module and returns it + * + * @see Doctrine_DataDict + * @see Doctrine_Expression + * @see Doctrine_Export + * @see Doctrine_Transaction + * @see Doctrine_Connection::$modules all availible modules + * @param string $name the name of the module to get + * @throws Doctrine_Connection_Exception if trying to get an unknown module + * @return Doctrine_Connection_Module connection module + */ + public function __get($name) + { + if (isset($this->properties[$name])) + return $this->properties[$name]; + + if ( ! isset($this->modules[$name])) { + throw new Doctrine_Connection_Exception('Unknown module / property ' . $name); + } + if ($this->modules[$name] === false) { + switch ($name) { + case 'unitOfWork': + $this->modules[$name] = new Doctrine_Connection_UnitOfWork($this); + break; + default: + $class = 'Doctrine_' . ucwords($name) . '_' . $this->getName(); + $this->modules[$name] = new $class($this); + } + } + + return $this->modules[$name]; + } + /** + * Quotes pattern (% and _) characters in a string) + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @param string the input string to quote + * + * @return string quoted string + */ + public function escapePattern($text) + { + if ($this->string_quoting['escape_pattern']) { + $text = str_replace($this->string_quoting['escape_pattern'], $this->string_quoting['escape_pattern'] . $this->string_quoting['escape_pattern'], $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, $this->string_quoting['escape_pattern'] . $wildcard, $text); + } + } + return $text; + } + /** + * convertBoolean + * some drivers need the boolean values to be converted into integers + * when using DQL API + * + * This method takes care of that conversion + * + * @param array $item + * @return void + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $k => $value) { + if (is_bool($item)) { + $item[$k] = (int) $value; + } + } + } else { + $item = (int) $item; + } + return $item; + } + /** + * Quote a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + pgsql + * + sqlite + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str identifier name to be quoted + * @param bool $checkOption check the 'quote_identifier' option + * + * @return string quoted identifier string + */ + public function quoteIdentifier($str, $checkOption = true) + { + if ($checkOption && ! $this->getAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER)) { + return $str; + } + $str = str_replace($this->properties['identifier_quoting']['end'], + $this->properties['identifier_quoting']['escape'] . + $this->properties['identifier_quoting']['end'], $str); + + return $this->properties['identifier_quoting']['start'] + . $str . $this->properties['identifier_quoting']['end']; + } + /** + * returns the manager that created this connection + * + * @return Doctrine_Manager + */ + public function getManager() + { + return $this->getParent(); + } + /** + * returns the database handler of which this connection uses + * + * @return PDO the database handler + */ + public function getDbh() + { + return $this->dbh; + } + /** + * converts given driver name + * + * @param + */ + public function driverName($name) + { + } + /** + * supports + * + * @param string $feature the name of the feature + * @return boolean whether or not this drivers supports given feature + */ + public function supports($feature) + { + return (isset($this->supported[$feature]) + && ($this->supported[$feature] === 'emulated' + || $this->supported[$feature] + ) + ); + } + /** + * quote + * quotes given input parameter + * + * @param mixed $input parameter to be quoted + * @param string $type + * @return mixed + */ + public function quote($input, $type = null) + { + if ($type == null) { + $type = gettype($input); + } + switch ($type) { + case 'integer': + case 'enum': + case 'boolean': + case 'double': + case 'float': + case 'bool': + case 'int': + return $input; + case 'array': + case 'object': + $input = serialize($input); + case 'string': + case 'char': + case 'varchar': + case 'text': + case 'gzip': + case 'blob': + case 'clob': + return $this->dbh->quote($input); + } + } + /** + * Removes any formatting in an sequence name using the 'seqname_format' option + * + * @param string $sqn string that containts name of a potential sequence + * @return string name of the sequence with possible formatting removed + */ + public function fixSequenceName($sqn) + { + $seqPattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $this->getAttribute(Doctrine::ATTR_SEQNAME_FORMAT)).'$/i'; + $seqName = preg_replace($seqPattern, '\\1', $sqn); + + if ($seqName && ! strcasecmp($sqn, $this->getSequenceName($seqName))) { + return $seqName; + } + return $sqn; + } + /** + * Removes any formatting in an index name using the 'idxname_format' option + * + * @param string $idx string that containts name of anl index + * @return string name of the index with possible formatting removed + */ + public function fixIndexName($idx) + { + $indexPattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $this->getAttribute(Doctrine::ATTR_IDXNAME_FORMAT)).'$/i'; + $indexName = preg_replace($indexPattern, '\\1', $idx); + if ($indexName && ! strcasecmp($idx, $this->getIndexName($indexName))) { + return $indexName; + } + return $idx; + } + /** + * adds sequence name formatting to a sequence name + * + * @param string name of the sequence + * @return string formatted sequence name + */ + public function getSequenceName($sqn) + { + return sprintf($this->getAttribute(Doctrine::ATTR_SEQNAME_FORMAT), + preg_replace('/[^a-z0-9_\$.]/i', '_', $sqn)); + } + /** + * adds index name formatting to a index name + * + * @param string name of the index + * @return string formatted index name + */ + public function getIndexName($idx) + { + return sprintf($this->getAttribute(Doctrine::ATTR_IDXNAME_FORMAT), + preg_replace('/[^a-z0-9_\$]/i', '_', $idx)); + } + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the REPLACE query just updates its values instead of + * inserting a new row. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only MySQL and SQLIte implement it natively, this type of + * query isemulated through this method for other DBMS using standard types + * of queries inside a transaction to assure the atomicity of the operation. + * + * @param string name of the table on which the REPLACE query will + * be executed. + * + * @param array an associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. + * + * The values of the array are values to be assigned to the specified field. + * + * @param array $keys an array containing all key fields (primary key fields + * or unique index fields) for this table + * + * the uniqueness of a row will be determined according to + * the provided key fields + * + * this method will fail if no key fields are specified + * + * @throws Doctrine_Connection_Exception if this driver doesn't support replace + * @throws Doctrine_Connection_Exception if some of the key values was null + * @throws Doctrine_Connection_Exception if there were no key fields + * @throws PDOException if something fails at PDO level + * @return integer number of rows affected + */ + public function replace($table, array $fields, array $keys) + { + //if ( ! $this->supports('replace')) + // throw new Doctrine_Connection_Exception('replace query is not supported'); + + if (empty($keys)) { + throw new Doctrine_Connection_Exception('Not specified which fields are keys'); + } + $condition = $values = array(); + + foreach ($fields as $name => $value) { + $values[$name] = $value; + + if (in_array($name, $keys)) { + if ($value === null) + throw new Doctrine_Connection_Exception('key value '.$name.' may not be null'); + + $condition[] = $name . ' = ?'; + $conditionValues[] = $value; + } + } + + $query = 'DELETE FROM ' . $this->quoteIdentifier($table) . ' WHERE ' . implode(' AND ', $condition); + $affectedRows = $this->exec($query); + + $this->insert($table, $values); + + $affectedRows++; + + + return $affectedRows; + } + /** + * Inserts a table row with specified data. + * + * @param string $table The table to insert data into. + * @param array $values An associateve array containing column-value pairs. + * @return boolean + */ + public function insert($table, array $values = array()) { + if (empty($values)) { + return false; + } + // column names are specified as array keys + $cols = array_keys($values); + + // build the statement + $query = 'INSERT INTO ' . $this->quoteIdentifier($table) + . '(' . implode(', ', $cols) . ') ' + . 'VALUES (' . substr(str_repeat('?, ', count($values)), 0, -2) . ')'; + + // prepare and execute the statement + $this->execute($query, array_values($values)); + + return true; + } + /** + * Set the charset on the current connection + * + * @param string charset + * + * @return void + */ + public function setCharset($charset) + { + + } + /** + * Set the date/time format for the current connection + * + * @param string time format + * + * @return void + */ + public function setDateFormat($format = null) + { + } + /** + * fetchAll + * + * @param string $statement sql query to be executed + * @param array $params prepared statement params + * @return array + */ + public function fetchAll($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetchAll(PDO::FETCH_ASSOC); + } + /** + * fetchOne + * + * @param string $statement sql query to be executed + * @param array $params prepared statement params + * @param int $colnum 0-indexed column number to retrieve + * @return mixed + */ + public function fetchOne($statement, array $params = array(), $colnum = 0) + { + return $this->execute($statement, $params)->fetchColumn($colnum); + } + /** + * fetchRow + * + * @param string $statement sql query to be executed + * @param array $params prepared statement params + * @return array + */ + public function fetchRow($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetch(PDO::FETCH_ASSOC); + } + /** + * fetchArray + * + * @param string $statement sql query to be executed + * @param array $params prepared statement params + * @return array + */ + public function fetchArray($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetch(PDO::FETCH_NUM); + } + /** + * fetchColumn + * + * @param string $statement sql query to be executed + * @param array $params prepared statement params + * @param int $colnum 0-indexed column number to retrieve + * @return array + */ + public function fetchColumn($statement, array $params = array(), $colnum = 0) + { + return $this->execute($statement, $params)->fetchAll(PDO::FETCH_COLUMN, $colnum); + } + /** + * fetchAssoc + * + * @param string $statement sql query to be executed + * @param array $params prepared statement params + * @return array + */ + public function fetchAssoc($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetchAll(PDO::FETCH_ASSOC); + } + /** + * fetchBoth + * + * @param string $statement sql query to be executed + * @param array $params prepared statement params + * @return array + */ + public function fetchBoth($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetchAll(PDO::FETCH_BOTH); + } + /** + * query + * queries the database using Doctrine Query Language + * returns a collection of Doctrine_Record objects + * + * + * $users = $conn->query('SELECT u.* FROM User u'); + * + * $users = $conn->query('SELECT u.* FROM User u WHERE u.name LIKE ?', array('someone')); + * + * + * @param string $query DQL query + * @param array $params query parameters + * @see Doctrine_Query + * @return Doctrine_Collection Collection of Doctrine_Record objects + */ + public function query($query, array $params = array()) + { + $parser = new Doctrine_Query($this); + + return $parser->query($query, $params); + } + /** + * query + * queries the database using Doctrine Query Language and returns + * the first record found + * + * + * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.id = ?', array(1)); + * + * $user = $conn->queryOne('SELECT u.* FROM User u WHERE u.name LIKE ? AND u.password = ?', + * array('someone', 'password') + * ); + * + * + * @param string $query DQL query + * @param array $params query parameters + * @see Doctrine_Query + * @return Doctrine_Record|false Doctrine_Record object on success, + * boolean false on failure + */ + public function queryOne($query, array $params = array()) + { + $parser = new Doctrine_Query($this); + + $coll = $parser->query($query, $params); + if ( ! $coll->contains(0)) { + return false; + } + return $coll[0]; + } + /** + * 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); + } + /** + * standaloneQuery + * + * @param string $query sql query + * @param array $params query parameters + * + * @return PDOStatement|Doctrine_Adapter_Statement + */ + public function standaloneQuery($query, $params = array()) + { + return $this->execute($query, $params); + } + /** + * execute + * @param string $query sql query + * @param array $params query parameters + * + * @return PDOStatement|Doctrine_Adapter_Statement + */ + public function execute($query, array $params = array()) + { + try { + if ( ! empty($params)) { + $stmt = $this->dbh->prepare($query); + $stmt->execute($params); + return $stmt; + } else { + return $this->dbh->query($query); + } + } catch(Doctrine_Adapter_Exception $e) { + } catch(PDOException $e) { } + + $this->rethrowException($e); + } + /** + * exec + * @param string $query sql query + * @param array $params query parameters + * + * @return PDOStatement|Doctrine_Adapter_Statement + */ + public function exec($query, array $params = array()) { + try { + if ( ! empty($params)) { + $stmt = $this->dbh->prepare($query); + $stmt->execute($params); + return $stmt->rowCount(); + } else { + return $this->dbh->exec($query); + } + } catch(Doctrine_Adapter_Exception $e) { + } catch(PDOException $e) { } + + $this->rethrowException($e); + } + /** + * rethrowException + * + * @throws Doctrine_Connection_Exception + */ + private function rethrowException(Exception $e) + { + $name = 'Doctrine_Connection_' . $this->driverName . '_Exception'; + + $exc = new $name($e->getMessage(), (int) $e->getCode()); + if ( ! is_array($e->errorInfo)) { + $e->errorInfo = array(null, null, null, null); + } + $exc->processErrorInfo($e->errorInfo); + + throw $exc; + } + /** + * hasTable + * whether or not this connection 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, $allowExport = true) + { + if (isset($this->tables[$name])) { + return $this->tables[$name]; + } + $class = $name . 'Table'; + + if (class_exists($class) && in_array('Doctrine_Table', class_parents($class))) { + $table = new $class($name, $this); + } else { + $table = new Doctrine_Table($name, $this); + } + + $this->tables[$name] = $table; + + if ($allowExport) { + + // the following is an algorithm for loading all + // the related tables for all loaded tables + + $next = count($this->tables); + $prev = count($this->exported); + $stack = $this->exported; + while ($prev < $next) { + $prev = count($this->tables); + + foreach($this->tables as $name => $tableObject) { + if (isset($stack[$name])) { + continue; + } else { + $stack[$name] = true; + } + + $tableObject->getRelations(); + + //$this->getTable('RelationTestChild')->getRelation('Children'); + } + $next = count($this->tables); + } + + + // when all the tables are loaded we build the array in which the order of the tables is + // relationally correct so that then those can be created in the given order) + + $names = array_keys($this->tables); + + $names = $this->unitOfWork->buildFlushTree($names); + + foreach($names as $name) { + $tableObject = $this->tables[$name]; + + if (isset($this->exported[$name])) { + continue; + } + + if ($tableObject->getAttribute(Doctrine::ATTR_EXPORT) & Doctrine::EXPORT_TABLES) { + + $tableObject->export(); + } + $this->exported[$name] = true; + } + } + + return $table; + } + /** + * 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 + * + * + * foreach ($conn as $index => $table) { + * print $table; // get a string representation of each table object + * } + * + * + * @return ArrayIterator SPL ArrayIterator object + */ + public function getIterator() + { + return new ArrayIterator($this->tables); + } + /** + * returns the count of initialized table objects + * + * @return integer + */ + public function count() + { + return count($this->tables); + } + /** + * addTable + * adds a Doctrine_Table object into connection registry + * + * @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; + } + /** + * create + * 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(); + } + /** + * flush + * saves all the records from all tables + * this operation is isolated using a transaction + * + * @throws PDOException if something went wrong at database level + * @return void + */ + public function flush() + { + $this->beginTransaction(); + $this->unitOfWork->saveAll(); + $this->commit(); + } + /** + * clear + * clears all repositories + * + * @return void + */ + public function clear() + { + foreach ($this->tables as $k => $table) { + $table->getRepository()->evictAll(); + $table->clear(); + } + } + /** + * evictTables + * evicts all tables + * + * @return void + */ + public function evictTables() + { + $this->tables = array(); + $this->exported = array(); + } + /** + * close + * closes the connection + * + * @return void + */ + public function close() + { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreClose($this); + + $this->clear(); + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onClose($this); + } + /** + * get the current transaction nesting level + * + * @return integer + */ + public function getTransactionLevel() + { + return $this->transaction->getTransactionLevel(); + } + /** + * beginTransaction + * starts a new transaction + * + * this method can be listened by onPreBeginTransaction and onBeginTransaction + * listener methods + * + * @return void + */ + public function beginTransaction() + { + $this->transaction->beginTransaction(); + } + /** + * commits the current transaction + * if lockmode is optimistic this method starts a transaction + * and commits it instantly + * + * @return void + */ + public function commit() + { + $this->transaction->commit(); + } + /** + * rollback + * rolls back all transactions + * + * this method also listens to onPreTransactionRollback and onTransactionRollback + * eventlisteners + * + * @return void + */ + public function rollback() + { + $this->transaction->rollback(); + } + /** + * saves the given record + * + * @param Doctrine_Record $record + * @return void + */ + public function save(Doctrine_Record $record) + { + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($record); + + switch ($record->state()) { + case Doctrine_Record::STATE_TDIRTY: + $this->unitOfWork->insert($record); + break; + case Doctrine_Record::STATE_DIRTY: + case Doctrine_Record::STATE_PROXY: + $this->unitOfWork->update($record); + break; + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_TCLEAN: + // do nothing + break; + }; + + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onSave($record); + } + /** + * 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(Doctrine_Record $record) + { + if ( ! $record->exists()) { + return false; + } + $this->beginTransaction(); + + $record->getTable()->getListener()->onPreDelete($record); + + $this->unitOfWork->deleteComposites($record); + + $this->transaction->addDelete($record); + + $record->getTable()->getListener()->onDelete($record); + + $this->commit(); + + return true; + } + /** + * returns a string representation of this object + * @return string + */ + public function __toString() + { + return Doctrine_Lib::getConnectionAsString($this); + } +} + diff --git a/draft/new-core/Query.php b/draft/new-core/Query.php index 8573cd4eb..cfaf30126 100644 --- a/draft/new-core/Query.php +++ b/draft/new-core/Query.php @@ -49,14 +49,10 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { */ private $isSubquery; - private $tableStack; - private $relationStack = array(); private $isDistinct = false; - - protected $components = array(); - + private $neededTables = array(); /** * @var array $pendingFields @@ -123,16 +119,6 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { return null; } - public function getTableStack() - { - return $this->tableStack; - } - - public function getRelationStack() - { - return $this->relationStack; - } - public function isDistinct($distinct = null) { if(isset($distinct)) @@ -146,7 +132,7 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { $tableAlias = $this->getTableAlias($componentAlias); if ( ! isset($this->tables[$tableAlias])) - throw new Doctrine_Query_Exception('Unknown component path '.$componentAlias); + throw new Doctrine_Query_Exception('Unknown component path ' . $componentAlias); $table = $this->tables[$tableAlias]; @@ -415,60 +401,6 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { $this->parts = $parts_old; return (int) $this->getConnection()->fetchOne($q, $params); } - /** - * 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 - */ - protected function loadFields(Doctrine_Table $table, $fetchmode, array $names, $cpath) - { - $name = $table->getComponentName(); - - switch($fetchmode): - case Doctrine::FETCH_OFFSET: - $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT); - case Doctrine::FETCH_IMMEDIATE: - if( ! empty($names)) { - // only auto-add the primary key fields if this query object is not - // a subquery of another query object - $names = array_unique(array_merge($table->getPrimaryKeys(), $names)); - } else { - $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_unique(array_merge($table->getPrimaryKeys(), $names)); - break; - default: - throw new Doctrine_Exception("Unknown fetchmode."); - endswitch; - - $component = $table->getComponentName(); - $tablename = $this->tableAliases[$cpath]; - - $this->fetchModes[$tablename] = $fetchmode; - - $count = count($this->tables); - - foreach($names as $name) { - if($count == 0) { - $this->parts['select'][] = $tablename . '.' . $name; - } else { - $this->parts['select'][] = $tablename . '.' . $name . ' AS ' . $tablename . '__' . $name; - } - } - } /** * addFrom * @@ -656,35 +588,6 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { } - 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]; - } - /** - * set - * sets a query SET part - * this method should only be used with UPDATE queries - * - * @param $name name of the field - * @param $value field value - * @return Doctrine_Query - */ - public function set($name, $value) - { - $class = new Doctrine_Query_Set($this); - $this->parts['set'][] = $class->parse($name . ' = ' . $value); - return $this; } /** @@ -704,15 +607,7 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { { switch ($this->type) { case self::DELETE: - /** - no longer needed? - - if ($this->conn->getName() == 'Mysql') { - $q = 'DELETE ' . end($this->tableAliases) . ' FROM '; - } else { - */ - $q = 'DELETE FROM '; - // } + $q = 'DELETE FROM '; break; case self::UPDATE: $q = 'UPDATE '; @@ -1058,49 +953,49 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { foreach($parts as $k => $part) { $part = implode(' ', $part); - switch(strtoupper($k)) { - case 'CREATE': + switch(strtolower($k)) { + case 'create': $this->type = self::CREATE; break; - case 'INSERT': + case 'insert': $this->type = self::INSERT; break; - case 'DELETE': + case 'delete': $this->type = self::DELETE; break; - case 'SELECT': + case 'select': $this->type = self::SELECT; $this->parseSelect($part); break; - case 'UPDATE': + case 'update': $this->type = self::UPDATE; $k = 'FROM'; - case 'FROM': + case 'from': $class = 'Doctrine_Query_' . ucwords(strtolower($k)); $parser = new $class($this); $parser->parse($part); break; - case 'SET': + case 'set': $class = 'Doctrine_Query_' . ucwords(strtolower($k)); $parser = new $class($this); $this->parts['set'][] = $parser->parse($part); break; - case 'GROUP': - case 'ORDER': + case 'group': + case 'order': $k .= 'by'; - case 'WHERE': - case 'HAVING': + case 'where': + case 'having': $class = 'Doctrine_Query_' . ucwords(strtolower($k)); $parser = new $class($this); $name = strtolower($k); $this->parts[$name][] = $parser->parse($part); break; - case 'LIMIT': + case 'limit': $this->parts['limit'] = trim($part); break; - case 'OFFSET': + case 'offset': $this->parts['offset'] = trim($part); break; } @@ -1120,220 +1015,6 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { $parser = new Doctrine_Query_Part_Orderby($this); return $parser->parse($str); } - /** - * returns Doctrine::FETCH_* constant - * - * @param string $mode - * @return integer - */ - final public 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 Doctrine_Query_Exception("Unknown fetchmode '$mode'. The availible fetchmodes are 'i', 'b' and 'l'."); - endswitch; - return $fetchmode; - } - /** - * 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 - * - * example: - * - * parameters: - * $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com' - * $d = ' AND ' - * $e1 = '(' - * $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 = ')') - { - if(is_array($d)) { - $a = preg_split('/('.implode('|', $d).')/', $str); - $d = stripslashes($d[0]); - } else - $a = explode("$d",$str); - - $i = 0; - $term = array(); - foreach($a 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; - } - /** - * quoteExplode - * - * example: - * - * parameters: - * $str = email LIKE 'John@example.com' - * $d = ' AND ' - * - * would return an array: - * array("email", "LIKE", "'John@example.com'") - * - * @param string $str - * @param string $d the delimeter which explodes the string - */ - public static function quoteExplode($str, $d = ' ') - { - if(is_array($d)) { - $a = preg_split('/('.implode('|', $d).')/', $str); - $d = stripslashes($d[0]); - } else - $a = explode("$d",$str); - - $i = 0; - $term = array(); - foreach($a as $key => $val) { - if (empty($term[$i])) { - $term[$i] = trim($val); - - if( ! (substr_count($term[$i], "'") & 1)) - $i++; - } else { - $term[$i] .= "$d".trim($val); - - if( ! (substr_count($term[$i], "'") & 1)) - $i++; - } - } - return $term; - } - /** - * sqlExplode - * - * explodes a string into array using custom brackets and - * quote delimeters - * - * - * example: - * - * parameters: - * $str = "(age < 20 AND age > 18) AND name LIKE 'John Doe'" - * $d = ' ' - * $e1 = '(' - * $e2 = ')' - * - * would return an array: - * array('(age < 20 AND age > 18)', - * 'name', - * 'LIKE', - * 'John Doe') - * - * @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 ')' - * - * @return array - */ - public static function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')') - { - if ($d == ' ') { - $d = array(' ', '\s'); - } - if(is_array($d)) { - if (in_array(' ', $d)) { - $d[] = '\s'; - } - $str = preg_split('/('.implode('|', $d).')/', $str); - $d = stripslashes($d[0]); - } else { - $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 (substr($term[$i],0,1) == "(") { - if($s1 == $s2) { - $i++; - } - } else { - if ( ! (substr_count($term[$i], "'") & 1) && - ! (substr_count($term[$i], "\"") & 1) && - ! (substr_count($term[$i], "�") & 1) - ) { $i++; } - } - } else { - $term[$i] .= "$d".trim($val); - $c1 = substr_count($term[$i],"$e1"); - $c2 = substr_count($term[$i],"$e2"); - - if(substr($term[$i],0,1) == "(") { - if($c1 == $c2) { - $i++; - } - } else { - if ( ! (substr_count($term[$i], "'") & 1) && - ! (substr_count($term[$i], "\"") & 1) && - ! (substr_count($term[$i], "�") & 1) - ) { $i++; } - } - } - } - return $term; - } /** * generateAlias * @@ -1349,7 +1030,34 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { return $tableName; } } + public function load($path, $loadFields = true) + { + // parse custom join conditions + $e = explode(' ON ', $path); + + $joinCondition = ''; + if (count($e) > 1) { + $joinCondition = ' AND ' . $e[1]; + $path = $e[0]; + } + + $tmp = explode(' ', $path); + $componentAlias = (count($tmp) > 1) ? end($tmp) : false; + + + $e = preg_split("/[.:]/", $tmp[0], -1); + + foreach ($e as $key => $part) { + + if ($key === 0) { + // process the root of the path + + } else { + + } + } + } /** * loads a component * @@ -1358,18 +1066,8 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { * @throws Doctrine_Query_Exception * @return Doctrine_Table */ - final public function load($path, $loadFields = true) + final public function load2($path, $loadFields = true) { - - // parse custom join conditions - $e = explode(' ON ', $path); - - $joinCondition = ''; - if(count($e) > 1) { - $joinCondition = ' AND ' . $e[1]; - $path = $e[0]; - } - $tmp = explode(' ',$path); $componentAlias = (count($tmp) > 1) ? end($tmp) : false; @@ -1410,7 +1108,7 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { if( ! isset($this->tableAliases[$currPath])) { $this->tableIndexes[$tname] = 1; - } + } $this->parts['from'] = $this->conn->quoteIdentifier($table->getTableName()); @@ -1503,7 +1201,7 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { if ($fk instanceof Doctrine_Relation_Association_Self) { $this->parts['join'][$tname][$tname2] .= ' OR ' . $tname2 . '.' . $table->getIdentifier() . ' = ' - . $assocAlias . '.' . $fk->getLocal(); + . $assocAlias . '.' . $fk->getLocal(); } } else { @@ -1577,104 +1275,5 @@ class Doctrine_Query2 extends Doctrine_Hydrate2 implements Countable { return $table; } - /** - * parseFields - * - * @param string $fullName - * @param string $tableName - * @param array $exploded - * @param string $currPath - * @return void - */ - final public function parseFields($fullName, $tableName, array $exploded, $currPath) - { - $table = $this->tables[$tableName]; - - $fields = array(); - - if(strpos($fullName, '-') === false) { - $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); - - if(isset($exploded[1])) { - if(count($exploded) > 2) { - $fields = $this->parseAggregateValues($fullName, $tableName, $exploded, $currPath); - } elseif(count($exploded) == 2) { - $fields = explode(',',substr($exploded[1],0,-1)); - } - } - } else { - if(isset($exploded[1])) { - $fetchmode = $this->parseFetchMode($exploded[1]); - } else - $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); - - if(isset($exploded[2])) { - if(substr_count($exploded[2], ')') > 1) { - - } else { - $fields = explode(',', substr($exploded[2],0,-1)); - } - } - - } - if( ! $this->aggregate) - $this->loadFields($table, $fetchmode, $fields, $currPath); - } - /** - * parseAggregateFunction - * - * @param string $func - * @param string $reference - * @return string - */ - public function parseAggregateFunction($func,$reference) - { - $pos = strpos($func, '('); - - if($pos !== false) { - $funcs = array(); - - $name = substr($func, 0, $pos); - $func = substr($func, ($pos + 1), -1); - $params = Doctrine_Query::bracketExplode($func, ',', '(', ')'); - - foreach($params as $k => $param) { - $params[$k] = $this->parseAggregateFunction($param,$reference); - } - - $funcs = $name . '(' . implode(', ', $params). ')'; - - return $funcs; - - } else { - if( ! is_numeric($func)) { - - $func = $this->getTableAlias($reference).'.'.$func; - - return $func; - } else { - - return $func; - } - } - } - /** - * parseAggregateValues - */ - public function parseAggregateValues($fullName, $tableName, array $exploded, $currPath) - { - $this->aggregate = true; - $pos = strpos($fullName, '('); - $name = substr($fullName, 0, $pos); - $string = substr($fullName, ($pos + 1), -1); - - $exploded = Doctrine_Query::bracketExplode($string, ','); - foreach($exploded as $k => $value) { - $func = $this->parseAggregateFunction($value, $currPath); - $exploded[$k] = $func; - - $this->parts['select'][] = $exploded[$k]; - } - } } diff --git a/draft/new-core/Table.php b/draft/new-core/Table.php new file mode 100644 index 000000000..5fca1cbe1 --- /dev/null +++ b/draft/new-core/Table.php @@ -0,0 +1,1499 @@ +. + */ +/** + * Doctrine_Table represents a database table + * each Doctrine_Table holds the information of foreignKeys and associations + * + * + * @author Konsta Vesterinen + * @package Doctrine + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @version $Revision: 1288 $ + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + */ +class Doctrine_Table extends Doctrine_Configurable implements Countable +{ + /** + * @var array $data temporary data which is then loaded into Doctrine_Record::$data + */ + private $data = array(); + /** + * @var array $relations an array containing all the Doctrine_Relation objects for this table + */ + private $relations = array(); + /** + * @var array $primaryKeys an array containing all primary key column names + */ + private $primaryKeys = array(); + /** + * @var mixed $identifier + */ + private $identifier; + /** + * @see Doctrine_Identifier constants + * @var integer $identifierType the type of identifier this table uses + */ + private $identifierType; + /** + * @var string $query cached simple query + */ + private $query; + /** + * @var Doctrine_Connection $conn Doctrine_Connection object that created this table + */ + private $conn; + /** + * @var string $name + */ + private $name; + /** + * @var array $identityMap first level cache + */ + private $identityMap = array(); + /** + * @var Doctrine_Table_Repository $repository record repository + */ + private $repository; + /** + * @var array $columns an array of column definitions, + * keys as column names and values as column definitions + * + * the value array has three values: + * + * the column type, eg. 'integer' + * the column length, eg. 11 + * the column options/constraints/validators. eg array('notnull' => true) + * + * so the full columns array might look something like the following: + * array( + * 'name' => array('string', 20, array('notnull' => true, 'default' => 'someone')), + * 'age' => array('integer', 11, array('notnull' => true)) + * ) + */ + protected $columns = array(); + /** + * @var array $columnAliases an array of column aliases + * keys as column aliases and values as column names + */ + protected $columnAliases = array(); + /** + * @var array $bound bound relations + */ + private $bound = array(); + /** + * @var array $boundAliases bound relation aliases + */ + private $boundAliases = array(); + /** + * @var integer $columnCount cached column count, Doctrine_Record uses this column count in when + * determining its state + */ + private $columnCount; + /** + * @var boolean $hasDefaultValues whether or not this table has default values + */ + private $hasDefaultValues; + /** + * @var array $options an array containing all options + * + * -- name name of the component, for example component name of the GroupTable is 'Group' + * + * -- parents the parent classes of this component + * + * -- declaringClass name of the table definition declaring class (when using inheritance the class + * that defines the table structure can be any class in the inheritance hierarchy, + * hence we need reflection to check out which class actually calls setTableDefinition) + * + * -- tableName database table name, in most cases this is the same as component name but in some cases + * where one-table-multi-class inheritance is used this will be the name of the inherited table + * + * -- sequenceName Some databases need sequences instead of auto incrementation primary keys, + * you can set specific sequence for your table by calling setOption('sequenceName', $seqName) + * where $seqName is the name of the desired sequence + * + * -- enumMap enum value arrays + * + * -- inheritanceMap inheritanceMap is used for inheritance mapping, keys representing columns and values + * the column values that should correspond to child classes + * + * -- type table type (mysql example: INNODB) + * + * -- charset character set + * + * -- foreignKeys the foreign keys of this table + * + * -- collation + * + * -- indexes the index definitions of this table + * + * -- treeImpl the tree implementation of this table (if any) + * + * -- treeOptions the tree options + */ + protected $options = array('name' => null, + 'tableName' => null, + 'sequenceName' => null, + 'inheritanceMap' => array(), + 'enumMap' => array(), + 'engine' => null, + 'charset' => null, + 'collation' => null, + 'treeImpl' => null, + 'treeOptions' => null, + 'indexes' => array(), + ); + /** + * @var Doctrine_Tree $tree tree object associated with this table + */ + protected $tree; + /** + * the constructor + * @throws Doctrine_Connection_Exception if there are no opened connections + * @throws Doctrine_Table_Exception if there is already an instance of this table + * @return void + */ + public function __construct($name, Doctrine_Connection $conn) + { + $this->conn = $conn; + + $this->setParent($this->conn); + + $this->options['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(); + + // set the table definition for the given tree implementation + if($this->isTree()) + $this->getTree()->setTableDefinition(); + + $this->columnCount = count($this->columns); + + if (isset($this->columns)) { + // get the declaring class of setTableDefinition method + $method = new ReflectionMethod($this->options['name'], 'setTableDefinition'); + $class = $method->getDeclaringClass(); + + $this->options['declaringClass'] = $class; + + if ( ! isset($this->options['tableName'])) { + $this->options['tableName'] = Doctrine::tableize($class->getName()); + } + switch (count($this->primaryKeys)) { + case 0: + $this->columns = array_merge(array('id' => + array('integer', + 20, + array('autoincrement' => true, + 'primary' => true, + ) + ) + ), $this->columns); + + $this->primaryKeys[] = 'id'; + $this->identifier = 'id'; + $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT; + $this->columnCount++; + break; + default: + if (count($this->primaryKeys) > 1) { + $this->identifier = $this->primaryKeys; + $this->identifierType = Doctrine_Identifier::COMPOSITE; + + } else { + foreach ($this->primaryKeys as $pk) { + $e = $this->columns[$pk][2]; + + $found = false; + + foreach ($e as $option => $value) { + if ($found) + break; + + $e2 = explode(':', $option); + + switch (strtolower($e2[0])) { + case 'autoincrement': + case 'autoinc': + $this->identifierType = Doctrine_Identifier::AUTO_INCREMENT; + $found = true; + break; + case 'seq': + case 'sequence': + $this->identifierType = Doctrine_Identifier::SEQUENCE; + $found = true; + + if ($value) { + $this->options['sequenceName'] = $value; + } else { + if (($sequence = $this->getAttribute(Doctrine::ATTR_DEFAULT_SEQUENCE)) !== null) { + $this->options['sequenceName'] = $sequence; + } else { + $this->options['sequenceName'] = $this->conn->getSequenceName($this->options['tableName']); + } + } + break; + } + } + if ( ! isset($this->identifierType)) { + $this->identifierType = Doctrine_Identifier::NORMAL; + } + $this->identifier = $pk; + } + } + }; + /** + if ( ! isset($definition['values'])) { + throw new Doctrine_Table_Exception('No values set for enum column ' . $name); + } + + if ( ! is_array($definition['values'])) { + throw new Doctrine_Table_Exception('Enum column values should be specified as an array.'); + } + + */ + + + } + } else { + throw new Doctrine_Table_Exception("Class '$name' has no table definition."); + } + + $record->setUp(); + + // if tree, set up tree + if ($this->isTree()) { + $this->getTree()->setUp(); + } + + // save parents + array_pop($names); + $this->options['parents'] = $names; + + $this->query = 'SELECT ' . implode(', ', array_keys($this->columns)) . ' FROM ' . $this->getTableName(); + + + $this->repository = new Doctrine_Table_Repository($this); + } + /** + * export + * exports this table to database based on column and option definitions + * + * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS + * occurred during the create table operation + * @return boolean whether or not the export operation was successful + * false if table already existed in the database + */ + public function export() + { + if ( ! Doctrine::isValidClassname($this->options['declaringClass']->getName())) { + throw new Doctrine_Table_Exception('Class name not valid.'); + } + + try { + $columns = array(); + $primary = array(); + + foreach ($this->columns as $name => $column) { + $definition = $column[2]; + $definition['type'] = $column[0]; + $definition['length'] = $column[1]; + + switch ($definition['type']) { + case 'enum': + if (isset($definition['default'])) { + $definition['default'] = $this->enumIndex($name, $definition['default']); + } + break; + case 'boolean': + if (isset($definition['default'])) { + $definition['default'] = $this->conn->convertBooleans($definition['default']); + } + break; + } + $columns[$name] = $definition; + + if(isset($definition['primary']) && $definition['primary']) { + $primary[] = $name; + } + } + + if ($this->getAttribute(Doctrine::ATTR_EXPORT) & Doctrine::EXPORT_CONSTRAINTS) { + + foreach ($this->getRelations() as $name => $relation) { + $fk = $relation->toArray(); + $fk['foreignTable'] = $relation->getTable()->getTableName(); + + if ($relation->getTable() === $this && in_array($relation->getLocal(), $primary)) { + continue; + } + + if ($relation->hasConstraint()) { + + $options['foreignKeys'][] = $fk; + } elseif ($relation instanceof Doctrine_Relation_LocalKey) { + $options['foreignKeys'][] = $fk; + } + } + } + + $options['primary'] = $primary; + + $this->conn->export->createTable($this->options['tableName'], $columns, array_merge($this->options, $options)); + } catch(Doctrine_Connection_Exception $e) { + // we only want to silence table already exists errors + if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { + throw $e; + } + } + } + /** + * exportConstraints + * exports the constraints of this table into database based on option definitions + * + * @throws Doctrine_Connection_Exception if something went wrong on db level + * @return void + */ + public function exportConstraints() + { + try { + $this->conn->beginTransaction(); + + foreach ($this->options['index'] as $index => $definition) { + $this->conn->export->createIndex($this->options['tableName'], $index, $definition); + } + $this->conn->commit(); + } catch(Doctrine_Connection_Exception $e) { + $this->conn->rollback(); + + throw $e; + } + } + /** + * __get + * an alias for getOption + * + * @param string $option + */ + public function __get($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return null; + } + /** + * __isset + * + * @param string $option + */ + public function __isset($option) + { + return isset($this->options[$option]); + } + /** + * addForeignKey + * + * adds a foreignKey to this table + * + * @return void + */ + public function addForeignKey(array $definition) + { + $this->options['foreignKeys'][] = $definition; + } + /** + * addIndex + * + * adds an index to this table + * + * @return void + */ + public function addIndex($index, array $definition) + { + $index = $this->conn->getIndexName($index); + $this->options['indexes'][$index] = $definition; + } + /** + * getIndex + * + * @return array|boolean array on success, FALSE on failure + */ + public function getIndex($index) + { + if (isset($this->options['indexes'][$index])) { + return $this->options['indexes'][$index]; + } + + return false; + } + /** + * createQuery + * creates a new Doctrine_Query object and adds the component name + * of this table as the query 'from' part + * + * @return Doctrine_Query + */ + public function createQuery() + { + return Doctrine_Query::create()->from($this->getComponentName()); + } + /** + * getRepository + * + * @return Doctrine_Table_Repository + */ + public function getRepository() + { + return $this->repository; + } + + public function setOption($name, $value) + { + switch ($name) { + case 'name': + case 'tableName': + break; + case 'enumMap': + case 'inheritanceMap': + case 'index': + case 'treeOptions': + if ( ! is_array($value)) { + throw new Doctrine_Table_Exception($name . ' should be an array.'); + } + break; + } + $this->options[$name] = $value; + } + public function getOption($name) + { + if (isset($this->options[$name])) { + return $this->options[$name]; + } + return null; + } + /** + * getColumnName + * + * returns a column name for column alias + * if the actual name for the alias cannot be found + * this method returns the given alias + * + * @param string $alias column alias + * @return string column name + */ + public function getColumnName($alias) + { + if(isset($this->columnAliases[$alias])) { + return $this->columnAliases[$alias]; + } + + return $alias; + } + /** + * setColumn + * + * @param string $name + * @param string $type + * @param integer $length + * @param mixed $options + * @throws Doctrine_Table_Exception if trying use wrongly typed parameter + * @return void + */ + public function setColumn($name, $type, $length = null, $options = array()) + { + if (is_string($options)) { + $options = explode('|', $options); + } + + foreach ($options as $k => $option) { + if (is_numeric($k)) { + if ( ! empty($option)) { + $options[$option] = true; + } + unset($options[$k]); + } + } + + $name = strtolower($name); + $parts = explode(' as ', $name); + + if (count($parts) > 1) { + $this->columnAliases[$parts[1]] = $parts[0]; + $name = $parts[0]; + } + + + if ($length == null) { + $length = 2147483647; + } + + if ((string) (int) $length !== (string) $length) { + throw new Doctrine_Table_Exception('Invalid argument given for column length'); + } + + $this->columns[$name] = array($type, $length, $options); + + if (isset($options['primary'])) { + $this->primaryKeys[] = $name; + } + if (isset($options['default'])) { + $this->hasDefaultValues = true; + } + } + /** + * hasDefaultValues + * returns true if this table has default values, otherwise false + * + * @return boolean + */ + public function hasDefaultValues() + { + return $this->hasDefaultValues; + } + /** + * getDefaultValueOf + * returns the default value(if any) for given column + * + * @param string $column + * @return mixed + */ + public function getDefaultValueOf($column) + { + $column = strtolower($column); + if ( ! isset($this->columns[$column])) { + throw new Doctrine_Table_Exception("Couldn't get default value. Column ".$column." doesn't exist."); + } + if (isset($this->columns[$column][2]['default'])) { + return $this->columns[$column][2]['default']; + } else { + return null; + } + } + /** + * @return mixed + */ + public function getIdentifier() + { + return $this->identifier; + } + /** + * @return integer + */ + public function getIdentifierType() + { + return $this->identifierType; + } + /** + * hasColumn + * @return boolean + */ + public function hasColumn($name) + { + return isset($this->columns[$name]); + } + /** + * @param mixed $key + * @return void + */ + public function setPrimaryKey($key) + { + switch (gettype($key)) { + case "array": + $this->primaryKeys = array_values($key); + break; + case "string": + $this->primaryKeys[] = $key; + break; + }; + } + /** + * returns all primary keys + * @return array + */ + public function getPrimaryKeys() + { + return $this->primaryKeys; + } + /** + * @return boolean + */ + public function hasPrimaryKey($key) + { + return in_array($key,$this->primaryKeys); + } + /** + * returns all bound relations + * + * @return array + */ + public function getBounds() + { + return $this->bound; + } + /** + * returns a bound relation array + * + * @param string $name + * @return array + */ + public function getBound($name) + { + if ( ! isset($this->bound[$name])) { + throw new Doctrine_Table_Exception('Unknown bound ' . $name); + } + return $this->bound[$name]; + } + /** + * returns a bound relation array + * + * @param string $name + * @return array + */ + public function getBoundForName($name, $component) + { + foreach ($this->bound as $k => $bound) { + $e = explode('.', $bound['field']); + + if ($bound['class'] == $name && $e[0] == $component) { + return $this->bound[$k]; + } + } + throw new Doctrine_Table_Exception('Unknown bound ' . $name); + } + /** + * returns the alias for given component name + * + * @param string $name + * @return string + */ + 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 + */ + public function getAliasName($alias) + { + if ($name = array_search($alias, $this->boundAliases)) { + return $name; + } + return $alias; + } + /** + * unbinds all relations + * + * @return void + */ + public function unbindAll() + { throw new Exception(); + $this->bound = array(); + $this->relations = array(); + $this->boundAliases = array(); + } + /** + * unbinds a relation + * returns true on success, false on failure + * + * @param $name + * @return boolean + */ + public function unbind($name) + { + 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 + */ + public function bind($name, $field, $type, $options = null) + { + if (isset($this->relations[$name])) { + unset($this->relations[$name]); + } + + $lower = strtolower($name); + + if (isset($this->columns[$lower])) { + throw new Doctrine_Table_Exception("Couldn't bind relation. Column with name " . $lower . ' already exists!'); + } + + $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' => $field, + 'type' => $type, + 'class' => $name, + 'alias' => $alias); + if ($options !== null) { + $opt = array(); + if (is_string($options)) { + $opt['local'] = $options; + } else { + $opt = (array) $options; + } + + $this->bound[$alias] = array_merge($this->bound[$alias], $opt); + } + } + /** + * @return Doctrine_Connection + */ + public function getConnection() + { + return $this->conn; + } + /** + * hasRelatedComponent + * @return boolean + */ + public function hasRelatedComponent($name, $component) + { + return (strpos($this->bound[$name]['field'], $component . '.') !== false); + } + /** + * @param string $name component name of which a foreign key object is bound + * @return boolean + */ + final public function hasRelation($name) + { + if (isset($this->bound[$name])) { + return true; + } + foreach ($this->bound as $k=>$v) { + if ($this->hasRelatedComponent($k, $name)) { + return true; + } + } + return false; + } + /** + * getRelation + * + * @param string $name component name of which a foreign key object is bound + * @return Doctrine_Relation + */ + public function getRelation($name, $recursive = true) + { + if (isset($this->relations[$name])) { + return $this->relations[$name]; + } + + if ( ! $this->conn->hasTable($this->options['name'])) { + $allowExport = true; + } else { + $allowExport = false; + } + + if (isset($this->bound[$name])) { + + $definition = $this->bound[$name]; + + list($component, $definition['foreign']) = explode('.', $definition['field']); + unset($definition['field']); + + $definition['table'] = $this->conn->getTable($definition['class'], $allowExport); + $definition['constraint'] = false; + + if ($component == $this->options['name'] || in_array($component, $this->options['parents'])) { + + // ONE-TO-ONE + if ($definition['type'] == Doctrine_Relation::ONE_COMPOSITE || + $definition['type'] == Doctrine_Relation::ONE_AGGREGATE) { + // tree structure parent relation found + + if ( ! isset($definition['local'])) { + $definition['local'] = $definition['foreign']; + $definition['foreign'] = $definition['table']->getIdentifier(); + } + + $relation = new Doctrine_Relation_LocalKey($definition); + + } else { + // tree structure children relation found + + if ( ! isset($definition['local'])) { + $tmp = $definition['table']->getIdentifier(); + + $definition['local'] = $tmp; + } + + //$definition['foreign'] = $tmp; + $definition['constraint'] = true; + + $relation = new Doctrine_Relation_ForeignKey($definition); + } + + } elseif ($component == $definition['class'] || + ($component == $definition['alias'])) { // && ($name == $this->options['name'] || in_array($name,$this->parents)) + + if ( ! isset($defintion['local'])) { + $definition['local'] = $this->identifier; + } + + $definition['constraint'] = true; + + // ONE-TO-MANY or ONE-TO-ONE + $relation = new Doctrine_Relation_ForeignKey($definition); + + } else { + // MANY-TO-MANY + // only aggregate relations allowed + + if ($definition['type'] != Doctrine_Relation::MANY_AGGREGATE) { + throw new Doctrine_Table_Exception("Only aggregate relations are allowed for many-to-many relations"); + } + + $classes = array_merge($this->options['parents'], array($this->options['name'])); + + foreach (array_reverse($classes) as $class) { + try { + $bound = $definition['table']->getBoundForName($class, $component); + break; + } catch(Doctrine_Table_Exception $exc) { } + } + if ( ! isset($bound)) { + throw new Doctrine_Table_Exception("Couldn't map many-to-many relation for " + . $this->options['name'] . " and $name. Components use different join tables."); + } + if ( ! isset($definition['local'])) { + $definition['local'] = $this->identifier; + } + $e2 = explode('.', $bound['field']); + $fields = explode('-', $e2[1]); + + if ($e2[0] != $component) { + throw new Doctrine_Table_Exception($e2[0] . ' doesn\'t match ' . $component); + } + $associationTable = $this->conn->getTable($e2[0], $allowExport); + + if (count($fields) > 1) { + // SELF-REFERENCING THROUGH JOIN TABLE + + $def['table'] = $associationTable; + $def['local'] = $this->identifier; + $def['foreign'] = $fields[0]; + $def['alias'] = $e2[0]; + $def['class'] = $e2[0]; + $def['type'] = Doctrine_Relation::MANY_COMPOSITE; + + $this->relations[$e2[0]] = new Doctrine_Relation_ForeignKey($def); + + $definition['assocTable'] = $associationTable; + $definition['local'] = $fields[0]; + $definition['foreign'] = $fields[1]; + $relation = new Doctrine_Relation_Association_Self($definition); + } else { + if($definition['table'] === $this) { + + } else { + // auto initialize a new one-to-one relationships for association table + $associationTable->bind($this->getComponentName(), + $associationTable->getComponentName(). '.' . $e2[1], + Doctrine_Relation::ONE_AGGREGATE + ); + + $associationTable->bind($definition['table']->getComponentName(), + $associationTable->getComponentName(). '.' . $definition['foreign'], + Doctrine_Relation::ONE_AGGREGATE + ); + + // NORMAL MANY-TO-MANY RELATIONSHIP + + $def['table'] = $associationTable; + $def['foreign'] = $e2[1]; + $def['local'] = $definition['local']; + $def['alias'] = $e2[0]; + $def['class'] = $e2[0]; + $def['type'] = Doctrine_Relation::MANY_COMPOSITE; + $this->relations[$e2[0]] = new Doctrine_Relation_ForeignKey($def); + + $definition['local'] = $e2[1]; + $definition['assocTable'] = $associationTable; + $relation = new Doctrine_Relation_Association($definition); + } + } + } + + $this->relations[$name] = $relation; + + return $this->relations[$name]; + } + + + // load all relations + $this->getRelations(); + + if ($recursive) { + return $this->getRelation($name, false); + } else { + throw new Doctrine_Table_Exception($this->options['name'] . " doesn't have a relation to " . $name); + } + + } + /** + * returns an array containing all foreign key objects + * + * @return array + */ + final public function getRelations() + { + foreach ($this->bound as $k => $v) { + $this->getRelation($k); + } + + return $this->relations; + } + /** + * 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; + $record = new $this->options['name']($this, true); + $this->data = array(); + return $record; + } + /** + * finds a record by its identifier + * + * @param $id database row id + * @return Doctrine_Record|false 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->options['inheritanceMap'])); + + $stmt = $this->conn->execute($query, $params); + + $this->data = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($this->data === false) + return false; + + return $this->getRecord(); + } + return false; + } + /** + * 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->options['inheritanceMap'])) { + $a = array(); + foreach ($this->options['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->conn); + $users = $graph->query("FROM ".$this->options['name']); + return $users; + } + /** + * findByDql + * finds records with given DQL where clause + * returns a collection of records + * + * @param string $dql DQL after WHERE clause + * @param array $params query parameters + * @return Doctrine_Collection + */ + public function findBySql($dql, array $params = array()) { + $q = new Doctrine_Query($this->conn); + $users = $q->query("FROM ".$this->options['name']." WHERE ".$dql, $params); + return $users; + } + + public function findByDql($dql, array $params = array()) { + return $this->findBySql($dql, $params); + } + /** + * 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() + { + $this->data = array_change_key_case($this->data, CASE_LOWER); + + $key = $this->getIdentifier(); + + if ( ! is_array($key)) { + $key = array($key); + } + + foreach ($key as $k) { + if ( ! isset($this->data[$k])) { + throw new Doctrine_Table_Exception("Primary key value for $k wasn't found"); + } + $id[] = $this->data[$k]; + } + + $id = implode(' ', $id); + + if (isset($this->identityMap[$id])) { + $record = $this->identityMap[$id]; + $record->hydrate($this->data); + } else { + $recordName = $this->getClassnameToReturn(); + $record = new $recordName($this); + $this->identityMap[$id] = $record; + } + $this->data = array(); + + return $record; + } + + /** + * Get the classname to return. Most often this is just the options['name'] + * + * Check the subclasses option and the inheritanceMap for each subclass to see + * if all the maps in a subclass is met. If this is the case return that + * subclass name. If no subclasses match or if there are no subclasses defined + * return the name of the class for this tables record. + * + * @todo this function could use reflection to check the first time it runs + * if the subclassing option is not set. + * + * @return string The name of the class to create + * + */ + public function getClassnameToReturn() + { + if (!isset($this->options['subclasses'])) { + return $this->options['name']; + } + foreach ($this->options['subclasses'] as $subclass) { + $table = $this->conn->getTable($subclass); + $inheritanceMap = $table->getOption('inheritanceMap'); + $nomatch = false; + foreach ($inheritanceMap as $key => $value) { + if (!isset($this->data[$key]) || $this->data[$key] != $value) { + $nomatch = true; + break; + } + } + if ( ! $nomatch) { + return $table->getComponentName(); + } + } + return $this->options['name']; + } + + /** + * @param $id database row id + * @throws Doctrine_Find_Exception + */ + 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->options['inheritanceMap'])); + + $this->data = $this->conn->execute($query,$params)->fetch(PDO::FETCH_ASSOC); + + if ($this->data === false) + return false; + } + return $this->getRecord(); + } + /** + * count + * + * @return integer + */ + public function count() + { + $a = $this->conn->getDBH()->query("SELECT COUNT(1) FROM ".$this->options['tableName'])->fetch(PDO::FETCH_NUM); + return current($a); + } + /** + * @return Doctrine_Query a Doctrine_Query object + */ + public function getQueryObject() + { + $graph = new Doctrine_Query($this->getConnection()); + $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->conn->modifyLimitQuery($query,$limit,$offset); + if ( ! empty($array)) { + $stmt = $this->conn->getDBH()->prepare($query); + $stmt->execute($array); + } else { + $stmt = $this->conn->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; + } + /** + * @param string $field + * @return array + */ + final public function getEnumValues($field) + { + if (isset($this->columns[$field][2]['values'])) { + return $this->columns[$field][2]['values']; + } else { + return array(); + } + } + /** + * enumValue + * + * @param string $field + * @param integer $index + * @return mixed + */ + public function enumValue($field, $index) + { + if ($index instanceof Doctrine_Null) + return $index; + + return isset($this->columns[$field][2]['values'][$index]) ? $this->columns[$field][2]['values'][$index] : $index; + } + /** + * enumIndex + * + * @param string $field + * @param mixed $value + * @return mixed + */ + public function enumIndex($field, $value) + { + $values = $this->getEnumValues($field); + + return array_search($value, $values); + } + /** + * invokeSet + * + * @param mixed $value + */ + public function invokeSet(Doctrine_Record $record, $name, $value) + { + if ( ! ($this->getAttribute(Doctrine::ATTR_ACCESSORS) & Doctrine::ACCESSOR_SET)) { + return $value; + } + $prefix = $this->getAttribute(Doctrine::ATTR_ACCESSOR_PREFIX_SET); + if (!$prefix) + $prefix = 'set'; + + $method = $prefix . $name; + + if (method_exists($record, $method)) { + return $record->$method($value); + } + + return $value; + } + /** + * invokeGet + * + * @param mixed $value + */ + public function invokeGet(Doctrine_Record $record, $name, $value) + { + if ( ! ($this->getAttribute(Doctrine::ATTR_ACCESSORS) & Doctrine::ACCESSOR_GET)) { + return $value; + } + $prefix = $this->getAttribute(Doctrine::ATTR_ACCESSOR_PREFIX_GET); + if (!$prefix) + $prefix = 'get'; + + $method = $prefix . $name; + + if (method_exists($record, $method)) { + return $record->$method($value); + } + + return $value; + } + + /** + * getDefinitionOf + * + * @return string ValueWrapper class name on success, false on failure + */ + public function getValueWrapperOf($column) + { + if (isset($this->columns[$column][2]['wrapper'])) { + return $this->columns[$column][2]['wrapper']; + } + return false; + } + /** + * getColumnCount + * + * @return integer the number of columns in this table + */ + 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); + } + + /** + * getDefinitionOf + * + * @return mixed array on success, false on failure + */ + public function getDefinitionOf($column) + { + if (isset($this->columns[$column])) { + return $this->columns[$column]; + } + return false; + } + /** + * getTypeOf + * + * @return mixed string on success, false on failure + */ + public function getTypeOf($column) + { + if (isset($this->columns[$column])) { + return $this->columns[$column][0]; + } + return false; + } + /** + * 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->conn->getDBH()->query($sql); + $data = $stmt->fetch(PDO::FETCH_NUM); + return isset($data[0])?$data[0]:1; + } + /** + * 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; + } + /** + * getter for associated tree + * + * @return mixed if tree return instance of Doctrine_Tree, otherwise returns false + */ + public function getTree() { + if (isset($this->options['treeImpl'])) { + if ( ! $this->tree) { + $options = isset($this->options['treeOptions']) ? $this->options['treeOptions'] : array(); + $this->tree = Doctrine_Tree::factory($this, + $this->options['treeImpl'], + $options + ); + } + return $this->tree; + } + return false; + } + public function getComponentName() + { + return $this->options['name']; + } + public function getTableName() + { + return $this->options['tableName']; + } + public function setTableName($tableName) + { + $this->options['tableName'] = $tableName; + } + /** + * determine if table acts as tree + * + * @return mixed if tree return true, otherwise returns false + */ + public function isTree() { + return ( ! is_null($this->options['treeImpl'])) ? true : false; + } + /** + * returns a string representation of this object + * + * @return string + */ + public function __toString() + { + return Doctrine_Lib::getTableAsString($this); + } +}