diff --git a/lib/Doctrine/Connection/UnitOfWork.php b/lib/Doctrine/Connection/UnitOfWork.php index ad266b953..422b9729c 100644 --- a/lib/Doctrine/Connection/UnitOfWork.php +++ b/lib/Doctrine/Connection/UnitOfWork.php @@ -278,7 +278,7 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module if ( ! $event->skipOperation) { $record->state(Doctrine_Record::STATE_TDIRTY); - if (count($table->getOption('joinedParents')) > 0) { + if ($table->getOption('joinedParents')) { foreach ($table->getOption('joinedParents') as $parent) { $parentTable = $table->getConnection()->getTable($parent); @@ -413,6 +413,7 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module // Protection against infinite function recursion before attempting to save if ($obj instanceof Doctrine_Record && $obj->isModified()) { $obj->save($this->conn); + /** Can this be removed? $id = array_values($obj->identifier()); @@ -461,8 +462,8 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module foreach ($v->getInsertDiff() as $r) { $assocRecord = $assocTable->create(); - $assocRecord->set($rel->getForeign(), $r); - $assocRecord->set($rel->getLocal(), $record); + $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r); + $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record); $this->saveGraph($assocRecord); } @@ -540,7 +541,7 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module if ( ! $event->skipOperation) { $identifier = $record->identifier(); - if (count($table->getOption('joinedParents')) > 0) { + if ($table->getOption('joinedParents')) { $dataSet = $this->formatDataSet($record); $component = $table->getComponentName(); @@ -595,7 +596,7 @@ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module $table->getRecordListener()->preInsert($event); if ( ! $event->skipOperation) { - if (count($table->getOption('joinedParents')) > 0) { + if ($table->getOption('joinedParents')) { $dataSet = $this->formatDataSet($record); $component = $table->getComponentName(); diff --git a/lib/Doctrine/Hydrate.php b/lib/Doctrine/Hydrate.php deleted file mode 100644 index 0af95c916..000000000 --- a/lib/Doctrine/Hydrate.php +++ /dev/null @@ -1,1289 +0,0 @@ -. - */ - -/** - * Doctrine_Hydrate is a base class for Doctrine_RawSql and Doctrine_Query. - * Its purpose is to populate object graphs. - * - * - * @package Doctrine - * @subpackage Hydrate - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.phpdoctrine.com - * @since 1.0 - * @version $Revision$ - * @author Konsta Vesterinen - */ -abstract class Doctrine_Hydrate extends Doctrine_Locator_Injectable implements Serializable -{ - /** - * QUERY TYPE CONSTANTS - */ - - /** - * constant for SELECT queries - */ - const SELECT = 0; - - /** - * constant for DELETE queries - */ - const DELETE = 1; - - /** - * constant for UPDATE queries - */ - const UPDATE = 2; - - /** - * constant for INSERT queries - */ - const INSERT = 3; - - /** - * constant for CREATE queries - */ - const CREATE = 4; - - /** - * @var array $params query input parameters - */ - protected $_params = array('where' => array(), - 'set' => array(), - 'having' => array()); - - /** - * @var Doctrine_Connection $conn Doctrine_Connection object - */ - protected $_conn; - - /** - * @var Doctrine_View $_view Doctrine_View object, when set this object will use the - * the query given by the view object for object population - */ - protected $_view; - - /** - * @var array $_aliasMap two dimensional array containing the map for query aliases - * Main keys are component aliases - * - * table table object associated with given alias - * - * relation the relation object owned by the parent - * - * parent the alias of the parent - * - * agg the aggregates of this component - * - * map the name of the column / aggregate value this - * component is mapped to a collection - */ - protected $_aliasMap = array(); - - /** - * - */ - protected $pendingAggregates = array(); - - /** - * @var array $aggregateMap an array containing all aggregate aliases, keys as dql aliases - * and values as sql aliases - */ - protected $aggregateMap = array(); - - /** - * @var array $_options an array of options - */ - protected $_options = array( - 'fetchMode' => Doctrine::FETCH_RECORD, - 'parserCache' => false, - 'resultSetCache' => false, - ); - - /** - * @var string $_sql cached SQL query - */ - protected $_sql; - - /** - * @var array $parts SQL query string parts - */ - protected $parts = array( - 'select' => array(), - 'distinct' => false, - 'forUpdate' => false, - 'from' => array(), - 'set' => array(), - 'join' => array(), - 'where' => array(), - 'groupby' => array(), - 'having' => array(), - 'orderby' => array(), - 'limit' => false, - 'offset' => false, - ); - - /** - * @var integer $type the query type - * - * @see Doctrine_Query::* constants - */ - protected $type = self::SELECT; - - /** - * @var array - */ - protected $_cache; - - /** - * The current hydration mode. - */ - protected $_hydrationMode = Doctrine::HYDRATE_RECORD; - - /** - * @var boolean $_expireCache a boolean value that indicates whether or not to force cache expiration - */ - protected $_expireCache = false; - - protected $_timeToLive; - - protected $_tableAliases = array(); - - /** - * @var array $_tableAliasSeeds A simple array keys representing table aliases and values - * as table alias seeds. The seeds are used for generating short table - * aliases. - */ - protected $_tableAliasSeeds = array(); - - /** - * constructor - * - * @param Doctrine_Connection|null $connection - */ - public function __construct($connection = null) - { - if ( ! ($connection instanceof Doctrine_Connection)) { - $connection = Doctrine_Manager::getInstance()->getCurrentConnection(); - } - $this->_conn = $connection; - } - - /** - * getRootAlias - * returns the alias of the the root component - * - * @return array - */ - public function getRootAlias() - { - if ( ! $this->_aliasMap) { - $this->getSql(); - } - - reset($this->_aliasMap); - - return key($this->_aliasMap); - } - - /** - * getRootDeclaration - * returns the root declaration - * - * @return array - */ - public function getRootDeclaration() - { - $map = reset($this->_aliasMap); - - return $map; - } - - /** - * getRoot - * returns the root component for this object - * - * @return Doctrine_Table root components table - */ - public function getRoot() - { - $map = reset($this->_aliasMap); - - if ( ! isset($map['table'])) { - throw new Doctrine_Hydrate_Exception('Root component not initialized.'); - } - - return $map['table']; - } - - /** - * getSql - * return the sql associated with this object - * - * @return string sql query string - */ - public function getSql() - { - return $this->getQuery(); - } - - /** - * useCache - * - * @param Doctrine_Cache_Interface|bool $driver cache driver - * @param integer $timeToLive how long the cache entry is valid - * @return Doctrine_Hydrate this object - */ - public function useCache($driver = true, $timeToLive = null) - { - if($driver !== null && $driver !== true && !($driver instanceOf Doctrine_Cache_Interface)){ - $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.'; - throw new Doctrine_Hydrate_Exception($msg); - } - $this->_cache = $driver; - - return $this->setCacheLifeSpan($timeToLive); - } - - /** - * expireCache - * - * @param boolean $expire whether or not to force cache expiration - * @return Doctrine_Hydrate this object - */ - public function expireCache($expire = true) - { - $this->_expireCache = true; - - return $this; - } - - /** - * setCacheLifeSpan - * - * @param integer $timeToLive how long the cache entry is valid - * @return Doctrine_Hydrate this object - */ - public function setCacheLifeSpan($timeToLive) - { - if ($timeToLive !== null) { - $timeToLive = (int) $timeToLive; - } - $this->_timeToLive = $timeToLive; - - return $this; - } - - /** - * getCacheDriver - * returns the cache driver associated with this object - * - * @return Doctrine_Cache_Interface|boolean|null cache driver - */ - public function getCacheDriver() - { - if ($this->_cache instanceof Doctrine_Cache_Interface) { - return $this->_cache; - } else { - return $this->_conn->getCacheDriver(); - } - } - - /** - * Sets the fetchmode. - * - * @param integer $fetchmode One of the Doctrine::HYDRATE_* constants. - */ - public function setHydrationMode($hydrationMode) - { - $this->_hydrationMode = $hydrationMode; - return $this; - } - - /** - * serialize - * this method is automatically called when this Doctrine_Hydrate is serialized - * - * @return array an array of serialized properties - */ - public function serialize() - { - $vars = get_object_vars($this); - - } - - /** - * unseralize - * this method is automatically called everytime a Doctrine_Hydrate object is unserialized - * - * @param string $serialized Doctrine_Record as serialized string - * @return void - */ - public function unserialize($serialized) - { - - } - - /** - * generateNewTableAlias - * generates a new alias from given table alias - * - * @param string $tableAlias table alias from which to generate the new alias from - * @return string the created table alias - */ - public function generateNewTableAlias($tableAlias) - { - if (isset($this->_tableAliases[$tableAlias])) { - // generate a new alias - $name = substr($tableAlias, 0, 1); - $i = ((int) substr($tableAlias, 1)); - - if ($i == 0) { - $i = 1; - } - - $newIndex = ($this->_tableAliasSeeds[$name] + $i); - - return $name . $newIndex; - } - - return $tableAlias; - } - - /** - * hasTableAlias - * whether or not this object has given tableAlias - * - * @param string $tableAlias the table alias to be checked - * @return boolean true if this object has given alias, otherwise false - */ - public function hasTableAlias($tableAlias) - { - return (isset($this->_tableAliases[$tableAlias])); - } - - /** - * getComponentAlias - * get component alias associated with given table alias - * - * @param string $tableAlias the table alias that identifies the component alias - * @return string component alias - */ - public function getComponentAlias($tableAlias) - { - if ( ! isset($this->_tableAliases[$tableAlias])) { - throw new Doctrine_Hydrate_Exception('Unknown table alias ' . $tableAlias); - } - return $this->_tableAliases[$tableAlias]; - } - - /** - * getTableAliasSeed - * returns the alias seed for given table alias - * - * @param string $tableAlias table alias that identifies the alias seed - * @return integer table alias seed - */ - public function getTableAliasSeed($tableAlias) - { - if ( ! isset($this->_tableAliasSeeds[$tableAlias])) { - return 0; - } - return $this->_tableAliasSeeds[$tableAlias]; - } - - /** - * generateTableAlias - * generates a table alias from given table name and associates - * it with given component alias - * - * @param string $componentAlias the component alias to be associated with generated table alias - * @param string $tableName the table name from which to generate the table alias - * @return string the generated table alias - */ - public function generateTableAlias($componentAlias, $tableName) - { - $char = strtolower(substr($tableName, 0, 1)); - - $alias = $char; - - if ( ! isset($this->_tableAliasSeeds[$alias])) { - $this->_tableAliasSeeds[$alias] = 1; - } - - while (isset($this->_tableAliases[$alias])) { - if ( ! isset($this->_tableAliasSeeds[$alias])) { - $this->_tableAliasSeeds[$alias] = 1; - } - $alias = $char . ++$this->_tableAliasSeeds[$alias]; - } - - $this->_tableAliases[$alias] = $componentAlias; - - return $alias; - } - - /** - * getTableAliases - * returns all table aliases - * - * @return array table aliases as an array - */ - public function getTableAliases() - { - return $this->_tableAliases; - } - - /** - * addTableAlias - * adds an alias for table and associates it with given component alias - * - * @param string $componentAlias the alias for the query component associated with given tableAlias - * @param string $tableAlias the table alias to be added - * @return Doctrine_Hydrate - */ - public function addTableAlias($tableAlias, $componentAlias) - { - $this->_tableAliases[$tableAlias] = $componentAlias; - - return $this; - } - - /** - * getTableAlias - * some database such as Oracle need the identifier lengths to be < ~30 chars - * hence Doctrine creates as short identifier aliases as possible - * - * this method is used for the creation of short table aliases, its also - * smart enough to check if an alias already exists for given component (componentAlias) - * - * @param string $componentAlias the alias for the query component to search table alias for - * @param string $tableName the table name from which the table alias is being created - * @return string the generated / fetched short alias - */ - public function getTableAlias($componentAlias, $tableName = null) - { - $alias = array_search($componentAlias, $this->_tableAliases); - - if ($alias !== false) { - return $alias; - } - - if ($tableName === null) { - throw new Doctrine_Hydrate_Exception("Couldn't get short alias for " . $componentAlias); - } - - return $this->generateTableAlias($componentAlias, $tableName); - } - - /** - * addQueryPart - * adds a query part in the query part array - * - * @param string $name the name of the query part to be added - * @param string $part query part string - * @throws Doctrine_Hydrate_Exception if trying to add unknown query part - * @return Doctrine_Hydrate this object - */ - public function addQueryPart($name, $part) - { - if ( ! isset($this->parts[$name])) { - throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name); - } - if (is_array($part)) { - $this->parts[$name] = array_merge($this->parts[$name], $part); - } else { - $this->parts[$name][] = $part; - } - return $this; - } - - /** - * setQueryPart - * sets a query part in the query part array - * - * @param string $name the name of the query part to be set - * @param string $part query part string - * @throws Doctrine_Hydrate_Exception if trying to set unknown query part - * @return Doctrine_Hydrate this object - */ - public function getQueryPart($part) - { - if ( ! isset($this->parts[$part])) { - throw new Doctrine_Hydrate_Exception('Unknown query part ' . $part); - } - - return $this->parts[$part]; - } - - /** - * removeQueryPart - * removes a query part from the query part array - * - * @param string $name the name of the query part to be removed - * @throws Doctrine_Hydrate_Exception if trying to remove unknown query part - * @return Doctrine_Hydrate this object - */ - public function removeQueryPart($name) - { - if ( ! isset($this->parts[$name])) { - throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name); - } - - if ($name == 'limit' || $name == 'offset') { - $this->parts[$name] = false; - } else { - $this->parts[$name] = array(); - } - return $this; - } - - /** - * setQueryPart - * sets a query part in the query part array - * - * @param string $name the name of the query part to be set - * @param string $part query part string - * @throws Doctrine_Hydrate_Exception if trying to set unknown query part - * @return Doctrine_Hydrate this object - */ - public function setQueryPart($name, $part) - { - if ( ! isset($this->parts[$name])) { - throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name); - } - - if ($name !== 'limit' && $name !== 'offset') { - if (is_array($part)) { - $this->parts[$name] = $part; - } else { - $this->parts[$name] = array($part); - } - } else { - $this->parts[$name] = $part; - } - - return $this; - } - - /** - * hasAliasDeclaration - * whether or not this object has a declaration for given component alias - * - * @param string $componentAlias the component alias the retrieve the declaration from - * @return boolean - */ - public function hasAliasDeclaration($componentAlias) - { - return isset($this->_aliasMap[$componentAlias]); - } - - /** - * getAliasDeclaration - * get the declaration for given component alias - * - * @param string $componentAlias the component alias the retrieve the declaration from - * @return array the alias declaration - */ - public function getAliasDeclaration($componentAlias) - { - if ( ! isset($this->_aliasMap[$componentAlias])) { - throw new Doctrine_Hydrate_Exception('Unknown component alias ' . $componentAlias); - } - - return $this->_aliasMap[$componentAlias]; - } - - /** - * copyAliases - * copy aliases from another Hydrate object - * - * this method is needed by DQL subqueries which need the aliases - * of the parent query - * - * @param Doctrine_Hydrate $query the query object from which the - * aliases are copied from - * @return Doctrine_Hydrate this object - */ - public function copyAliases(Doctrine_Hydrate $query) - { - $this->_tableAliases = $query->_tableAliases; - $this->_aliasMap = $query->_aliasMap; - $this->_tableAliasSeeds = $query->_tableAliasSeeds; - return $this; - } - - /** - * createSubquery - * creates a subquery - * - * @return Doctrine_Hydrate - */ - public function createSubquery() - { - $class = get_class($this); - $obj = new $class(); - - // copy the aliases to the subquery - $obj->copyAliases($this); - - // this prevents the 'id' being selected, re ticket #307 - $obj->isSubquery(true); - - return $obj; - } - - /** - * limitSubqueryUsed - * whether or not limit subquery was used - * - * @return boolean - */ - public function isLimitSubqueryUsed() - { - return false; - } - - /** - * clear - * resets all the variables - * - * @return void - */ - protected function clear() - { - $this->parts = array( - 'select' => array(), - 'distinct' => false, - 'forUpdate' => false, - 'from' => array(), - 'set' => array(), - 'join' => array(), - 'where' => array(), - 'groupby' => array(), - 'having' => array(), - 'orderby' => array(), - 'limit' => false, - 'offset' => false, - ); - $this->inheritanceApplied = false; - } - - /** - * getConnection - * - * @return Doctrine_Connection - */ - public function getConnection() - { - return $this->_conn; - } - - /** - * setView - * sets a database view this query object uses - * this method should only be called internally by doctrine - * - * @param Doctrine_View $view database view - * @return void - */ - public function setView(Doctrine_View $view) - { - $this->_view = $view; - } - - /** - * getView - * returns the view associated with this query object (if any) - * - * @return Doctrine_View the view associated with this query object - */ - public function getView() - { - return $this->_view; - } - - /** - * getParams - * - * @return array - */ - public function getParams() - { - return array_merge($this->_params['set'], $this->_params['where'], $this->_params['having']); - } - - /** - * setParams - * - * @param array $params - */ - public function setParams(array $params = array()) { - $this->_params = $params; - } - public function convertEnums($params) - { - return $params; - } - - /** - * setAliasMap - * sets the whole component alias map - * - * @param array $map alias map - * @return Doctrine_Hydrate this object - */ - public function setAliasMap(array $map) - { - $this->_aliasMap = $map; - - return $this; - } - - /** - * getAliasMap - * returns the component alias map - * - * @return array component alias map - */ - public function getAliasMap() - { - return $this->_aliasMap; - } - - /** - * getCachedForm - * returns the cached form of this query for given resultSet - * - * @param array $resultSet - * @return string serialized string representation of this query - */ - public function getCachedForm(array $resultSet) - { - $map = ''; - - foreach ($this->getAliasMap() as $k => $v) { - if ( ! isset($v['parent'])) { - $map[$k][] = $v['table']->getComponentName(); - } else { - $map[$k][] = $v['parent'] . '.' . $v['relation']->getAlias(); - } - if (isset($v['agg'])) { - $map[$k][] = $v['agg']; - } - } - - return serialize(array($resultSet, $map, $this->getTableAliases())); - } - - /** - * _execute - * - * @param array $params - * @return void - */ - public function _execute($params) - { - $params = $this->_conn->convertBooleans($params); - - if ( ! $this->_view) { - $query = $this->getQuery($params); - } else { - $query = $this->_view->getSelectSql(); - } - - $params = $this->convertEnums($params); - - if ($this->isLimitSubqueryUsed() && - $this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME) !== 'mysql') { - - $params = array_merge($params, $params); - } - - if ($this->type !== self::SELECT) { - return $this->_conn->exec($query, $params); - } - //echo $query . "

"; - $stmt = $this->_conn->execute($query, $params); - return $stmt; - } - - /** - * execute - * executes the query and populates the data set - * - * @param string $params - * @return Doctrine_Collection the root collection - */ - public function execute($params = array(), $hydrationMode = null) - { - $params = array_merge($this->_params['set'], - $this->_params['where'], - $this->_params['having'], - $params); - if ($this->_cache) { - $cacheDriver = $this->getCacheDriver(); - - $dql = $this->getDql(); - // calculate hash for dql query - $hash = md5($dql . var_export($params, true)); - - $cached = ($this->_expireCache) ? false : $cacheDriver->fetch($hash); - - - if ($cached === false) { - // cache miss - $stmt = $this->_execute($params); - $array = $this->hydrateResultSet($stmt, Doctrine::HYDRATE_ARRAY); - - $cached = $this->getCachedForm($array); - - $cacheDriver->save($hash, $cached, $this->_timeToLive); - } else { - $cached = unserialize($cached); - $this->_tableAliases = $cached[2]; - $array = $cached[0]; - - $map = array(); - foreach ($cached[1] as $k => $v) { - $e = explode('.', $v[0]); - if (count($e) === 1) { - $map[$k]['table'] = $this->_conn->getTable($e[0]); - } else { - $map[$k]['parent'] = $e[0]; - $map[$k]['relation'] = $map[$e[0]]['table']->getRelation($e[1]); - $map[$k]['table'] = $map[$k]['relation']->getTable(); - } - if (isset($v[1])) { - $map[$k]['agg'] = $v[1]; - } - } - $this->_aliasMap = $map; - } - } else { - $stmt = $this->_execute($params); - - if (is_integer($stmt)) { - return $stmt; - } - - $array = $this->hydrateResultSet($stmt, $hydrationMode); - } - return $array; - } - - /** - * getType - * - * returns the type of this query object - * by default the type is Doctrine_Hydrate::SELECT but if update() or delete() - * are being called the type is Doctrine_Hydrate::UPDATE and Doctrine_Hydrate::DELETE, - * respectively - * - * @see Doctrine_Hydrate::SELECT - * @see Doctrine_Hydrate::UPDATE - * @see Doctrine_Hydrate::DELETE - * - * @return integer return the query type - */ - public function getType() - { - return $this->type; - } - - /** - * applyInheritance - * applies column aggregation inheritance to DQL / SQL query - * - * @return string - */ - public function applyInheritance() - { - // get the inheritance maps - $array = array(); - - foreach ($this->_aliasMap as $componentAlias => $data) { - $tableAlias = $this->getTableAlias($componentAlias); - $array[$tableAlias][] = $data['table']->inheritanceMap; - } - - // apply inheritance maps - $str = ''; - $c = array(); - - $index = 0; - foreach ($array as $tableAlias => $maps) { - $a = array(); - - // don't use table aliases if the query isn't a select query - if ($this->type !== Doctrine_Query::SELECT) { - $tableAlias = ''; - } else { - $tableAlias .= '.'; - } - - foreach ($maps as $map) { - $b = array(); - foreach ($map as $field => $value) { - $identifier = $this->_conn->quoteIdentifier($tableAlias . $field); - - if ($index > 0) { - $b[] = '(' . $identifier . ' = ' . $this->_conn->quote($value) - . ' OR ' . $identifier . ' IS NULL)'; - } else { - $b[] = $identifier . ' = ' . $this->_conn->quote($value); - } - } - - if ( ! empty($b)) { - $a[] = implode(' AND ', $b); - } - } - - if ( ! empty($a)) { - $c[] = implode(' AND ', $a); - } - $index++; - } - - $str .= implode(' AND ', $c); - - return $str; - } - - /** - * fetchArray - * Convenience method to execute using array fetching as hydration mode. - * - * @param string $params - * @return array - */ - public function fetchArray($params = array()) { - return $this->execute($params, Doctrine::HYDRATE_ARRAY); - } - - /** - * fetchOne - * Convenience method to execute the query and return the first item - * of the collection. - * - * @param string $params Parameters - * @param int $hydrationMode Hydration mode - * @return mixed Array or Doctrine_Collection or false if no result. - */ - public function fetchOne($params = array(), $hydrationMode = null) - { - if (is_null($hydrationMode)) { - $hydrationMode = $this->_hydrationMode; - } - - $collection = $this->execute($params, $hydrationMode); - - if (count($collection) === 0) { - return false; - } - - switch ($hydrationMode) { - case Doctrine::HYDRATE_RECORD: - return $collection->getFirst(); - case Doctrine::HYDRATE_ARRAY: - return array_shift($collection); - } - - return false; - } - - /** - * parseData - * parses the data returned by statement object - * - * This is method defines the core of Doctrine object population algorithm - * hence this method strives to be as fast as possible - * - * The key idea is the loop over the rowset only once doing all the needed operations - * within this massive loop. - * - * @todo: Can we refactor this function so that it is not so long and - * nested? - * - * @param mixed $stmt - * @return array - */ - public function hydrateResultSet($stmt, $hydrationMode) - { - if ($hydrationMode == Doctrine::HYDRATE_NONE) { - return $stmt->fetchAll(PDO::FETCH_NUM); - } - - $cache = array(); - $rootMap = reset($this->_aliasMap); - $rootAlias = key($this->_aliasMap); - $componentName = $rootMap['table']->getComponentName(); - $isSimpleQuery = count($this->_aliasMap) <= 1; - - if ($hydrationMode === null) { - $hydrationMode = $this->_hydrationMode; - } - - if ($hydrationMode === Doctrine::HYDRATE_ARRAY) { - $driver = new Doctrine_Hydrate_Array(); - } else { - $driver = new Doctrine_Hydrate_Record(); - } - - $array = $driver->getElementCollection($componentName); - - if ($stmt === false || $stmt === 0) { - return $array; - } - - $event = new Doctrine_Event(null, Doctrine_Event::HYDRATE, null); - - // for every getRecordListener() there is a little bit - // logic behind it, hence calling it multiple times on - // large result sets can be quite expensive. - // So for efficiency we use little listener caching here - foreach ($this->_aliasMap as $alias => $data) { - $componentName = $data['table']->getComponentName(); - $listeners[$componentName] = $data['table']->getRecordListener(); - $identifierMap[$alias] = array(); - $currData[$alias] = array(); - $prev[$alias] = array(); - $id[$alias] = ''; - } - - while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) { - $currData = array(); - $identifiable = array(); - - foreach ($data as $key => $value) { - - // The following little cache solution ensures that field aliases are - // parsed only once. This increases speed on large result sets by an order - // of magnitude. - if ( ! isset($cache[$key])) { - $e = explode('__', $key); - $last = strtolower(array_pop($e)); - $cache[$key]['alias'] = $this->_tableAliases[strtolower(implode('__', $e))]; - $fieldName = $this->_aliasMap[$cache[$key]['alias']]['table']->getFieldName($last); - $cache[$key]['fieldName'] = $fieldName; - } - - $map = $this->_aliasMap[$cache[$key]['alias']]; - $table = $map['table']; - $alias = $cache[$key]['alias']; - $fieldName = $cache[$key]['fieldName']; - - if (isset($this->_aliasMap[$alias]['agg'][$fieldName])) { - $fieldName = $this->_aliasMap[$alias]['agg'][$fieldName]; - } - - if ($table->isIdentifier($fieldName)) { - $id[$alias] .= '|' . $value; - } - - $currData[$alias][$fieldName] = $table->prepareValue($fieldName, $value); - - if ($value !== null) { - $identifiable[$alias] = true; - } - - } - - // dealing with root component - $table = $this->_aliasMap[$rootAlias]['table']; - $componentName = $table->getComponentName(); - $event->set('data', $currData[$rootAlias]); - $listeners[$componentName]->preHydrate($event); - $element = $driver->getElement($currData[$rootAlias], $componentName); - - $oneToOne = false; - - if ($isSimpleQuery) { - $index = false; - } else { - $index = isset($identifierMap[$rootAlias][$id[$rootAlias]]) ? - $identifierMap[$rootAlias][$id[$rootAlias]] : false; - } - - if ($index === false) { - $event->set('data', $element); - $listeners[$componentName]->postHydrate($event); - - if (isset($this->_aliasMap[$rootAlias]['map'])) { - $key = $this->_aliasMap[$rootAlias]['map']; - - if (isset($array[$key])) { - throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping."); - } - - if ( ! isset($element[$key])) { - throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key."); - } - - $array[$element[$key]] = $element; - } else { - $array[] = $element; - } - - $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($array); - } - - $this->_setLastElement($prev, $array, $index, $rootAlias, $oneToOne); - unset($currData[$rootAlias]); - - foreach ($currData as $alias => $data) { - $index = false; - $map = $this->_aliasMap[$alias]; - $table = $this->_aliasMap[$alias]['table']; - $componentName = $table->getComponentName(); - $event->set('data', $data); - $listeners[$componentName]->preHydrate($event); - - $element = $driver->getElement($data, $componentName); - - $parent = $map['parent']; - $relation = $map['relation']; - $componentAlias = $map['relation']->getAlias(); - - $path = $parent . '.' . $alias; - - if ( ! isset($prev[$parent])) { - break; - } - - // check the type of the relation - if ( ! $relation->isOneToOne()) { - // initialize the collection - - if ($driver->initRelated($prev[$parent], $componentAlias)) { - - // append element - if (isset($identifiable[$alias])) { - if ($isSimpleQuery) { - $index = false; - } else { - $index = isset($identifierMap[$path][$id[$parent]][$id[$alias]]) ? - $identifierMap[$path][$id[$parent]][$id[$alias]] : false; - } - - if ($index === false) { - $event->set('data', $element); - $listeners[$componentName]->postHydrate($event); - - if (isset($map['map'])) { - $key = $map['map']; - if (isset($prev[$parent][$componentAlias][$key])) { - throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping."); - } - if ( ! isset($element[$key])) { - throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key."); - } - $prev[$parent][$componentAlias][$element[$key]] = $element; - } else { - $prev[$parent][$componentAlias][] = $element; - } - - $identifierMap[$path][$id[$parent]][$id[$alias]] = $driver->getLastKey($prev[$parent][$componentAlias]); - } - } - // register collection for later snapshots - $driver->registerCollection($prev[$parent][$componentAlias]); - } - } else { - if ( ! isset($identifiable[$alias])) { - $prev[$parent][$componentAlias] = $driver->getNullPointer(); - } else { - $prev[$parent][$componentAlias] = $element; - } - $oneToOne = true; - } - $coll =& $prev[$parent][$componentAlias]; - $this->_setLastElement($prev, $coll, $index, $alias, $oneToOne); - $id[$alias] = ''; - } - $id[$rootAlias] = ''; - } - - $driver->flush(); - - $stmt->closeCursor(); - return $array; - } - - /** - * _setLastElement - * - * sets the last element of given data array / collection - * as previous element - * - * @param boolean|integer $index - * @return void - */ - public function _setLastElement(&$prev, &$coll, $index, $alias, $oneToOne) - { - if ($coll === self::$_null) { - return false; - } - if ($index !== false) { - $prev[$alias] =& $coll[$index]; - return; - } - // first check the count (we do not want to get the last element - // of an empty collection/array) - if (count($coll) > 0) { - if (is_array($coll)) { - if ($oneToOne) { - $prev[$alias] =& $coll; - } else { - end($coll); - $prev[$alias] =& $coll[key($coll)]; - } - } else { - $prev[$alias] = $coll->getLast(); - } - } else { - if (isset($prev[$alias])) { - unset($prev[$alias]); - } - } - } - - /** - * @return string returns a string representation of this object - */ - public function __toString() - { - return Doctrine_Lib::formatSql($this->getQuery()); - } - - /** - * Return the parts - * - * @return array The parts - */ - public function getParts() - { - return $this->parts; - } - - abstract public function getQuery($params = array()); -} diff --git a/lib/Doctrine/Hydrator/Abstract.php b/lib/Doctrine/Hydrator/Abstract.php new file mode 100644 index 000000000..2cfb4a326 --- /dev/null +++ b/lib/Doctrine/Hydrator/Abstract.php @@ -0,0 +1,119 @@ +. + */ + +/** + * Doctrine_Hydrator_Abstract + * + * @package Doctrine + * @subpackage Hydrate + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision: 3192 $ + * @author Konsta Vesterinen + */ +abstract class Doctrine_Hydrator_Abstract extends Doctrine_Locator_Injectable +{ + /** + * @var array $_aliasMap two dimensional array containing the map for query aliases + * Main keys are component aliases + * + * table table object associated with given alias + * + * relation the relation object owned by the parent + * + * parent the alias of the parent + * + * agg the aggregates of this component + * + * map the name of the column / aggregate value this + * component is mapped to a collection + */ + protected $_aliasMap = array(); + + /** + * The current hydration mode. + */ + protected $_hydrationMode = Doctrine::HYDRATE_RECORD; + + /** + * constructor + * + * @param Doctrine_Connection|null $connection + */ + public function __construct() + { + + } + + /** + * Sets the fetchmode. + * + * @param integer $fetchmode One of the Doctrine::HYDRATE_* constants. + */ + public function setHydrationMode($hydrationMode) + { + $this->_hydrationMode = $hydrationMode; + } + + /** + * setAliasMap + * sets the whole component alias map + * + * @param array $map alias map + * @return Doctrine_Hydrate this object + */ + public function setAliasMap(array $map) + { + $this->_aliasMap = $map; + return $this; + } + + /** + * getAliasMap + * returns the component alias map + * + * @return array component alias map + */ + public function getAliasMap() + { + return $this->_aliasMap; + } + + /** + * parseData + * parses the data returned by statement object + * + * This is method defines the core of Doctrine object population algorithm + * hence this method strives to be as fast as possible + * + * The key idea is the loop over the rowset only once doing all the needed operations + * within this massive loop. + * + * @todo: Can we refactor this function so that it is not so long and + * nested? + * + * @param mixed $stmt + * @return array + */ + abstract public function hydrateResultSet($stmt, $aliasMap, $tableAliases, $hydrationMode = null); + +} diff --git a/lib/Doctrine/Hydrator/Default.php b/lib/Doctrine/Hydrator/Default.php new file mode 100644 index 000000000..5a6636c8e --- /dev/null +++ b/lib/Doctrine/Hydrator/Default.php @@ -0,0 +1,309 @@ +. + */ + +/** + * Doctrine_Hydrate is a base class for Doctrine_RawSql and Doctrine_Query. + * Its purpose is to populate object graphs. + * + * + * @package Doctrine + * @subpackage Hydrate + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision: 3192 $ + * @author Konsta Vesterinen + */ +class Doctrine_Hydrator_Default extends Doctrine_Hydrator_Abstract +{ + /** + * hydrateResultSet + * parses the data returned by statement object + * + * This is method defines the core of Doctrine object population algorithm + * hence this method strives to be as fast as possible + * + * The key idea is the loop over the rowset only once doing all the needed operations + * within this massive loop. + * + * @todo: Detailed documentation. Refactor (too long & nesting level). + * + * @param mixed $stmt + * @return array + */ + public function hydrateResultSet($stmt, $aliasMap, $tableAliases, $hydrationMode = null) + { + $this->_aliasMap = $aliasMap; + //echo "aliasmap:
"; + /*foreach ($this->_aliasMap as $map) { + Doctrine::dump($map['map']); + }*/ + //Doctrine::dump($tableAliases); + //echo "

"; + + if ($hydrationMode == Doctrine::HYDRATE_NONE) { + return $stmt->fetchAll(PDO::FETCH_NUM); + } + + if ($hydrationMode === null) { + $hydrationMode = $this->_hydrationMode; + } + + if ($hydrationMode === Doctrine::HYDRATE_ARRAY) { + $driver = new Doctrine_Hydrator_Default_FetchModeDriver_Array(); + } else { + $driver = new Doctrine_Hydrator_Default_FetchModeDriver_Record(); + } + + $event = new Doctrine_Event(null, Doctrine_Event::HYDRATE, null); + + + // Used variables during hydration + $rootMap = reset($this->_aliasMap); + $rootAlias = key($this->_aliasMap); + $componentName = $rootMap['table']->getComponentName(); + $isSimpleQuery = count($this->_aliasMap) <= 1; + $array = array(); // Holds the resulting hydrated data structure + $cache = array(); // Temporarily holds results of some operations to improve performance + $listeners = array(); // Holds hydration listeners that get called during hydration + $identifierMap = array(); // ??? + $prev = array(); // ??? + $id = array(); // ??? + $currData = array(); // ??? + $identifiable = array(); // ??? + + $array = $driver->getElementCollection($componentName); + + if ($stmt === false || $stmt === 0) { + return $array; + } + + // Initialize the variables + foreach ($this->_aliasMap as $alias => $data) { + $componentName = $data['table']->getComponentName(); + $listeners[$componentName] = $data['table']->getRecordListener(); + $identifierMap[$alias] = array(); + $currData[$alias] = array(); + $prev[$alias] = array(); + $id[$alias] = ''; + } + + // Hydrate + while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) { + $currData = array(); + $identifiable = array(); + + foreach ($data as $key => $value) { + + if ( ! isset($cache[$key])) { + $e = explode('__', $key); + $last = strtolower(array_pop($e)); + $cache[$key]['alias'] = $tableAliases[strtolower(implode('__', $e))]; + $fieldName = $this->_aliasMap[$cache[$key]['alias']]['table']->getFieldName($last); + $cache[$key]['fieldName'] = $fieldName; + } + + $map = $this->_aliasMap[$cache[$key]['alias']]; + $table = $map['table']; + $alias = $cache[$key]['alias']; + $fieldName = $cache[$key]['fieldName']; + + if (isset($this->_aliasMap[$alias]['agg'][$fieldName])) { + $fieldName = $this->_aliasMap[$alias]['agg'][$fieldName]; + } + + if ($table->isIdentifier($fieldName)) { + $id[$alias] .= '|' . $value; + } + + $currData[$alias][$fieldName] = $table->prepareValue($fieldName, $value); + + if ($value !== null) { + $identifiable[$alias] = true; + } + } + + //echo "currdata of row:
"; + //Doctrine::dump($currData); + //echo "

"; + + // dealing with root component + $table = $this->_aliasMap[$rootAlias]['table']; + $componentName = $table->getComponentName(); + $event->set('data', $currData[$rootAlias]); + $listeners[$componentName]->preHydrate($event); + $element = $driver->getElement($currData[$rootAlias], $componentName); + + $oneToOne = false; + + if ($isSimpleQuery) { + $index = false; + } else { + $index = isset($identifierMap[$rootAlias][$id[$rootAlias]]) ? + $identifierMap[$rootAlias][$id[$rootAlias]] : false; + } + + if ($index === false) { + $event->set('data', $element); + $listeners[$componentName]->postHydrate($event); + + if (isset($this->_aliasMap[$rootAlias]['map'])) { + $key = $this->_aliasMap[$rootAlias]['map']; + + if (isset($array[$key])) { + throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping."); + } + + if ( ! isset($element[$key])) { + throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key."); + } + + $array[$element[$key]] = $element; + } else { + $array[] = $element; + } + + $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($array); + } + + $this->_setLastElement($prev, $array, $index, $rootAlias, $oneToOne); + unset($currData[$rootAlias]); + + foreach ($currData as $alias => $data) { + $index = false; + $map = $this->_aliasMap[$alias]; + $table = $this->_aliasMap[$alias]['table']; + $componentName = $table->getComponentName(); + $event->set('data', $data); + $listeners[$componentName]->preHydrate($event); + + $element = $driver->getElement($data, $componentName); + + $parent = $map['parent']; + $relation = $map['relation']; + $componentAlias = $map['relation']->getAlias(); + + $path = $parent . '.' . $alias; + + if ( ! isset($prev[$parent])) { + break; + } + + // check the type of the relation + if ( ! $relation->isOneToOne()) { + // initialize the collection + + if ($driver->initRelated($prev[$parent], $componentAlias)) { + + // append element + if (isset($identifiable[$alias])) { + if ($isSimpleQuery) { + $index = false; + } else { + $index = isset($identifierMap[$path][$id[$parent]][$id[$alias]]) ? + $identifierMap[$path][$id[$parent]][$id[$alias]] : false; + } + + if ($index === false) { + $event->set('data', $element); + $listeners[$componentName]->postHydrate($event); + + if (isset($map['map'])) { + $key = $map['map']; + if (isset($prev[$parent][$componentAlias][$key])) { + throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping."); + } + if ( ! isset($element[$key])) { + throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key."); + } + $prev[$parent][$componentAlias][$element[$key]] = $element; + } else { + $prev[$parent][$componentAlias][] = $element; + } + + $identifierMap[$path][$id[$parent]][$id[$alias]] = $driver->getLastKey($prev[$parent][$componentAlias]); + } + } + // register collection for later snapshots + $driver->registerCollection($prev[$parent][$componentAlias]); + } + } else { + if ( ! isset($identifiable[$alias])) { + $prev[$parent][$componentAlias] = $driver->getNullPointer(); + } else { + $prev[$parent][$componentAlias] = $element; + } + $oneToOne = true; + } + $coll =& $prev[$parent][$componentAlias]; + $this->_setLastElement($prev, $coll, $index, $alias, $oneToOne); + $id[$alias] = ''; + } + $id[$rootAlias] = ''; + } + + $driver->flush(); + + $stmt->closeCursor(); + + return $array; + } + + /** + * _setLastElement + * + * sets the last element of given data array / collection + * as previous element + * + * @param boolean|integer $index + * @return void + * @todo Detailed documentation + */ + protected function _setLastElement(&$prev, &$coll, $index, $alias, $oneToOne) + { + if ($coll === self::$_null) { + return false; + } + if ($index !== false) { + $prev[$alias] =& $coll[$index]; + return; + } + // first check the count (we do not want to get the last element + // of an empty collection/array) + if (count($coll) > 0) { + if (is_array($coll)) { + if ($oneToOne) { + $prev[$alias] =& $coll; + } else { + end($coll); + $prev[$alias] =& $coll[key($coll)]; + } + } else { + $prev[$alias] = $coll->getLast(); + } + } else { + if (isset($prev[$alias])) { + unset($prev[$alias]); + } + } + } + +} diff --git a/lib/Doctrine/Hydrator/Default/FetchModeDriver/Array.php b/lib/Doctrine/Hydrator/Default/FetchModeDriver/Array.php new file mode 100644 index 000000000..299e294d0 --- /dev/null +++ b/lib/Doctrine/Hydrator/Default/FetchModeDriver/Array.php @@ -0,0 +1,73 @@ +. + */ + +/** + * Doctrine_Hydrate_Array + * defines an array fetching strategy for Doctrine_Hydrate + * + * @package Doctrine + * @subpackage Hydrate + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision$ + * @author Konsta Vesterinen + */ +class Doctrine_Hydrator_Default_FetchModeDriver_Array +{ + public function getElementCollection($component) + { + return array(); + } + public function getElement(array $data, $component) + { + return $data; + } + public function isIdentifiable(array $data, Doctrine_Table $table) + { + return ( ! empty($data)); + } + public function registerCollection($coll) + { + + } + public function initRelated(array &$data, $name) + { + if ( ! isset($data[$name])) { + $data[$name] = array(); + } + return true; + } + public function getNullPointer() + { + return null; + } + public function getLastKey(&$data) + { + end($data); + return key($data); + } + + public function flush() + { + + } +} diff --git a/lib/Doctrine/Hydrator/Default/FetchModeDriver/Record.php b/lib/Doctrine/Hydrator/Default/FetchModeDriver/Record.php new file mode 100644 index 000000000..dcb4b0016 --- /dev/null +++ b/lib/Doctrine/Hydrator/Default/FetchModeDriver/Record.php @@ -0,0 +1,132 @@ +. + */ + +/** + * Doctrine_Hydrate_Record + * defines a record fetching strategy for Doctrine_Hydrate + * + * @package Doctrine + * @subpackage Hydrate + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision$ + * @author Konsta Vesterinen + */ +class Doctrine_Hydrator_Default_FetchModeDriver_Record extends Doctrine_Locator_Injectable +{ + protected $_collections = array(); + + protected $_records = array(); + + protected $_tables = array(); + + public function getElementCollection($component) + { + $coll = new Doctrine_Collection($component); + $this->_collections[] = $coll; + + return $coll; + } + + public function getLastKey($coll) + { + $coll->end(); + + return $coll->key(); + } + + public function initRelated($record, $name) + { + if ( ! is_array($record)) { + $record[$name]; + + return true; + } + return false; + } + + public function registerCollection(Doctrine_Collection $coll) + { + $this->_collections[] = $coll; + } + + /** + * isIdentifiable + * returns whether or not a given data row is identifiable (it contains + * all primary key fields specified in the second argument) + * + * @param array $row + * @param Doctrine_Table $table + * @return boolean + */ + public function isIdentifiable(array $row, Doctrine_Table $table) + { + $primaryKeys = $table->getIdentifierColumnNames(); + + if (is_array($primaryKeys)) { + foreach ($primaryKeys as $id) { + if ( ! isset($row[$id])) { + return false; + } + } + } else { + if ( ! isset($row[$primaryKeys])) { + return false; + } + } + return true; + } + + public function getNullPointer() + { + return self::$_null; + } + + public function getElement(array $data, $component) + { + if ( ! isset($this->_tables[$component])) { + $this->_tables[$component] = Doctrine_Manager::getInstance()->getTable($component); + $this->_tables[$component]->setAttribute(Doctrine::ATTR_LOAD_REFERENCES, false); + } + + $this->_tables[$component]->setData($data); + $record = $this->_tables[$component]->getRecord(); + + if ( ! isset($this->_records[$record->getOid()]) ) { + $record->clearRelated(); + $this->_records[$record->getOid()] = $record; + } + + return $record; + } + + public function flush() + { + // take snapshots from all initialized collections + foreach ($this->_collections as $key => $coll) { + $coll->takeSnapshot(); + } + foreach ($this->_tables as $table) { + $table->setAttribute(Doctrine::ATTR_LOAD_REFERENCES, true); + } + } +} diff --git a/lib/Doctrine/Query.php b/lib/Doctrine/Query.php index 2097503f0..d143fb0ba 100644 --- a/lib/Doctrine/Query.php +++ b/lib/Doctrine/Query.php @@ -30,75 +30,14 @@ Doctrine::autoload('Doctrine_Query_Abstract'); * @version $Revision$ * @author Konsta Vesterinen */ -class Doctrine_Query extends Doctrine_Query_Abstract implements Countable +class Doctrine_Query extends Doctrine_Query_Abstract implements Countable, Serializable { - /** @todo document the query states (and the transitions between them). */ - const STATE_CLEAN = 1; - - const STATE_DIRTY = 2; - - const STATE_DIRECT = 3; - - const STATE_LOCKED = 4; - - protected static $_keywords = array('ALL', - 'AND', - 'ANY', - 'AS', - 'ASC', - 'AVG', - 'BETWEEN', - 'BIT_LENGTH', - 'BY', - 'CHARACTER_LENGTH', - 'CHAR_LENGTH', - 'CURRENT_DATE', - 'CURRENT_TIME', - 'CURRENT_TIMESTAMP', - 'DELETE', - 'DESC', - 'DISTINCT', - 'EMPTY', - 'EXISTS', - 'FALSE', - 'FETCH', - 'FROM', - 'GROUP', - 'HAVING', - 'IN', - 'INDEXBY', - 'INNER', - 'IS', - 'JOIN', - 'LEFT', - 'LIKE', - 'LOWER', - 'MEMBER', - 'MOD', - 'NEW', - 'NOT', - 'NULL', - 'OBJECT', - 'OF', - 'OR', - 'ORDER', - 'OUTER', - 'POSITION', - 'SELECT', - 'SOME', - 'TRIM', - 'TRUE', - 'UNKNOWN', - 'UPDATE', - 'WHERE'); - - - protected $subqueryAliases = array(); + protected $subqueryAliases = array(); /** * @param boolean $needsSubquery */ - protected $needsSubquery = false; + protected $needsSubquery = false; /** * @param boolean $isSubquery whether or not this query object is a subquery of another @@ -106,12 +45,10 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable */ protected $isSubquery; - protected $isLimitSubqueryUsed = false; - /** * @var array $_neededTables an array containing the needed table aliases */ - protected $_neededTables = array(); + protected $_neededTables = array(); /** * @var array $pendingSubqueries SELECT part subqueries, these are called pending subqueries since @@ -122,17 +59,12 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable /** * @var array $_pendingFields an array of pending fields (fields waiting to be parsed) */ - protected $_pendingFields = array(); + protected $_pendingFields = array(); /** * @var array $_parsers an array of parser objects, each DQL query part has its own parser */ - protected $_parsers = array(); - - /** - * @var array $_enumParams an array containing the keys of the parameters that should be enumerated - */ - protected $_enumParams = array(); + protected $_parsers = array(); /** * @var array $_dqlParts an array containing all DQL query parts @@ -162,6 +94,12 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable * @var integer $_state The current state of this query. */ protected $_state = Doctrine_Query::STATE_CLEAN; + + /** + * @var string $_sql cached SQL query + */ + protected $_sql; + /** * create @@ -191,18 +129,23 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable } /** - * setOption + * createSubquery + * creates a subquery * - * @param string $name option name - * @param string $value option value - * @return Doctrine_Query this object + * @return Doctrine_Hydrate */ - public function setOption($name, $value) + public function createSubquery() { - if ( ! isset($this->_options[$name])) { - throw new Doctrine_Query_Exception('Unknown option ' . $name); - } - $this->_options[$name] = $value; + $class = get_class($this); + $obj = new $class(); + + // copy the aliases to the subquery + $obj->copyAliases($this); + + // this prevents the 'id' being selected, re ticket #307 + $obj->isSubquery(true); + + return $obj; } /** @@ -245,33 +188,86 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable { return $this->_enumParams; } - + /** - * limitSubqueryUsed + * getParams * - * @return boolean + * @return array */ - public function isLimitSubqueryUsed() + public function getParams() { - return $this->isLimitSubqueryUsed; + return array_merge($this->_params['set'], $this->_params['where'], $this->_params['having']); } - + /** - * convertEnums - * convert enum parameters to their integer equivalents + * setParams * - * @return array converted parameter array + * @param array $params */ - public function convertEnums($params) + public function setParams(array $params = array()) { + $this->_params = $params; + } + + /** + * getCachedForm + * returns the cached form of this query for given resultSet + * + * @param array $resultSet + * @return string serialized string representation of this query + */ + public function getCachedForm(array $resultSet) { - foreach ($this->_enumParams as $key => $values) { - if (isset($params[$key])) { - if ( ! empty($values)) { - $params[$key] = $values[0]->enumIndex($values[1], $params[$key]); - } + $map = ''; + + foreach ($this->getAliasMap() as $k => $v) { + if ( ! isset($v['parent'])) { + $map[$k][] = $v['table']->getComponentName(); + } else { + $map[$k][] = $v['parent'] . '.' . $v['relation']->getAlias(); + } + if (isset($v['agg'])) { + $map[$k][] = $v['agg']; } } - return $params; + + return serialize(array($resultSet, $map, $this->getTableAliases())); + } + + /** + * fetchArray + * Convenience method to execute using array fetching as hydration mode. + * + * @param string $params + * @return array + */ + public function fetchArray($params = array()) { + return $this->execute($params, Doctrine::HYDRATE_ARRAY); + } + + /** + * fetchOne + * Convenience method to execute the query and return the first item + * of the collection. + * + * @param string $params Parameters + * @param int $hydrationMode Hydration mode + * @return mixed Array or Doctrine_Collection or false if no result. + */ + public function fetchOne($params = array(), $hydrationMode = null) + { + $collection = $this->execute($params, $hydrationMode); + + if (count($collection) === 0) { + return false; + } + + if ($collection instanceof Doctrine_Collection) { + return $collection->getFirst(); + } else if (is_array($collection)) { + return array_shift($collection); + } + + return false; } /** @@ -1090,6 +1086,8 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable * @param string $queryPartName the name of the query part * @param array $queryParts an array containing the query part data * @return Doctrine_Query this object + * @todo Better description. "parses given query part" ??? Then wheres the difference + * between process/parseQueryPart? I suppose this does something different. */ public function processQueryPart($queryPartName, $queryParts) { @@ -1199,7 +1197,6 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable } } - $modifyLimit = true; if ( ! empty($this->parts['limit']) || ! empty($this->parts['offset'])) { @@ -1270,14 +1267,14 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable // get short alias $alias = $this->getTableAlias($componentAlias); - $primaryKey = $alias . '.' . $table->getIdentifier(); + // what about composite keys? + $primaryKey = $alias . '.' . $table->getColumnName($table->getIdentifier()); // initialize the base of the subquery $subquery = 'SELECT DISTINCT ' . $this->_conn->quoteIdentifier($primaryKey); $driverName = $this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME); - // pgsql needs the order by fields to be preserved in select clause if ($driverName == 'pgsql') { foreach ($this->parts['orderby'] as $part) { @@ -1309,10 +1306,8 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable } } - $subquery .= ' FROM'; - foreach ($this->parts['from'] as $part) { // preserve LEFT JOINs only if needed if (substr($part, 0, 9) === 'LEFT JOIN') { @@ -1921,7 +1916,7 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable $new = new Doctrine_Query(); $new->_dqlParts = $query->_dqlParts; $new->_params = $query->_params; - $new->_hydrationMode = $query->_hydrationMode; + $new->_hydrator = $query->_hydrator; return $new; } @@ -1942,4 +1937,27 @@ class Doctrine_Query extends Doctrine_Query_Abstract implements Countable $this->_dqlParts = array(); $this->_enumParams = array(); } + + /** + * serialize + * this method is automatically called when this Doctrine_Hydrate is serialized + * + * @return array an array of serialized properties + */ + public function serialize() + { + $vars = get_object_vars($this); + } + + /** + * unseralize + * this method is automatically called everytime a Doctrine_Hydrate object is unserialized + * + * @param string $serialized Doctrine_Record as serialized string + * @return void + */ + public function unserialize($serialized) + { + + } } diff --git a/lib/Doctrine/Query/Abstract.php b/lib/Doctrine/Query/Abstract.php index 06ce35de2..f6b39af6d 100644 --- a/lib/Doctrine/Query/Abstract.php +++ b/lib/Doctrine/Query/Abstract.php @@ -30,8 +30,769 @@ Doctrine::autoload('Doctrine_Hydrate'); * @version $Revision: 1393 $ * @author Konsta Vesterinen */ -abstract class Doctrine_Query_Abstract extends Doctrine_Hydrate +abstract class Doctrine_Query_Abstract { + /** + * QUERY TYPE CONSTANTS + */ + + /** + * constant for SELECT queries + */ + const SELECT = 0; + + /** + * constant for DELETE queries + */ + const DELETE = 1; + + /** + * constant for UPDATE queries + */ + const UPDATE = 2; + + /** + * constant for INSERT queries + */ + const INSERT = 3; + + /** + * constant for CREATE queries + */ + const CREATE = 4; + + /** @todo document the query states (and the transitions between them). */ + const STATE_CLEAN = 1; + + const STATE_DIRTY = 2; + + const STATE_DIRECT = 3; + + const STATE_LOCKED = 4; + + + protected $_tableAliases = array(); + + protected $_view; + + /** + * @var array $params query input parameters + */ + protected $_params = array('where' => array(), + 'set' => array(), + 'having' => array()); + + /* Caching properties */ + /** + * @var array + */ + protected $_cache; + + /** + * @var boolean $_expireCache A boolean value that indicates whether or not to force cache expiration. + */ + protected $_expireCache = false; + protected $_timeToLive; + + /** + * @var Doctrine_Connection The connection used by this query object. + */ + protected $_conn; + + /** + * @var array The DQL keywords. + */ + protected static $_keywords = array('ALL', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AVG', + 'BETWEEN', + 'BIT_LENGTH', + 'BY', + 'CHARACTER_LENGTH', + 'CHAR_LENGTH', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'DELETE', + 'DESC', + 'DISTINCT', + 'EMPTY', + 'EXISTS', + 'FALSE', + 'FETCH', + 'FROM', + 'GROUP', + 'HAVING', + 'IN', + 'INDEXBY', + 'INNER', + 'IS', + 'JOIN', + 'LEFT', + 'LIKE', + 'LOWER', + 'MEMBER', + 'MOD', + 'NEW', + 'NOT', + 'NULL', + 'OBJECT', + 'OF', + 'OR', + 'ORDER', + 'OUTER', + 'POSITION', + 'SELECT', + 'SOME', + 'TRIM', + 'TRUE', + 'UNKNOWN', + 'UPDATE', + 'WHERE'); + + /** + * @var array $parts The SQL query string parts. Filled during the DQL parsing process. + */ + protected $parts = array( + 'select' => array(), + 'distinct' => false, + 'forUpdate' => false, + 'from' => array(), + 'set' => array(), + 'join' => array(), + 'where' => array(), + 'groupby' => array(), + 'having' => array(), + 'orderby' => array(), + 'limit' => false, + 'offset' => false, + ); + + + /** + * @var array $_aliasMap two dimensional array containing the map for query aliases + * Main keys are component aliases + * + * table table object associated with given alias + * + * relation the relation object owned by the parent + * + * parent the alias of the parent + * + * agg the aggregates of this component + * + * map the name of the column / aggregate value this + * component is mapped to a collection + */ + protected $_aliasMap = array(); + + /** + * @var integer $type the query type + * + * @see Doctrine_Query::* constants + */ + protected $type = self::SELECT; + + /** + * @var Doctrine_Hydrator The hydrator object used to hydrate query results. + */ + protected $_hydrator; + + /** + * @var array $_tableAliasSeeds A simple array keys representing table aliases and values + * as table alias seeds. The seeds are used for generating short table + * aliases. + */ + protected $_tableAliasSeeds = array(); + + /** + * @var array $_options an array of options + */ + protected $_options = array( + 'fetchMode' => Doctrine::FETCH_RECORD, + 'parserCache' => false, + 'resultSetCache' => false, + ); + + /** + * @var array $aggregateMap an array containing all aggregate aliases, keys as dql aliases + * and values as sql aliases + */ + protected $aggregateMap = array(); + + protected $pendingAggregates = array(); + + protected $inheritanceApplied = false; + + /** + * @var array $_enumParams an array containing the keys of the parameters that should be enumerated + */ + protected $_enumParams = array(); + + protected $isLimitSubqueryUsed = false; + + + public function __construct(Doctrine_Connection $connection = null, + Doctrine_Hydrator_Abstract $hydrator = null) + { + if ($connection === null) { + $connection = Doctrine_Manager::getInstance()->getCurrentConnection(); + } + if ($hydrator === null) { + $hydrator = new Doctrine_Hydrator_Default(); + } + $this->_conn = $connection; + $this->_hydrator = $hydrator; + } + + /** + * setOption + * + * @param string $name option name + * @param string $value option value + * @return Doctrine_Query this object + */ + public function setOption($name, $value) + { + if ( ! isset($this->_options[$name])) { + throw new Doctrine_Query_Exception('Unknown option ' . $name); + } + $this->_options[$name] = $value; + } + + /** + * hasTableAlias + * whether or not this object has given tableAlias + * + * @param string $tableAlias the table alias to be checked + * @return boolean true if this object has given alias, otherwise false + */ + public function hasTableAlias($tableAlias) + { + return (isset($this->_tableAliases[$tableAlias])); + } + + /** + * getTableAliases + * returns all table aliases + * + * @return array table aliases as an array + */ + public function getTableAliases() + { + return $this->_tableAliases; + } + + /** + * getQueryPart + * gets a query part from the query part array + * + * @param string $name the name of the query part to be set + * @param string $part query part string + * @throws Doctrine_Hydrate_Exception if trying to set unknown query part + * @return Doctrine_Hydrate this object + */ + public function getQueryPart($part) + { + if ( ! isset($this->parts[$part])) { + throw new Doctrine_Hydrate_Exception('Unknown query part ' . $part); + } + + return $this->parts[$part]; + } + + /** + * setQueryPart + * sets a query part in the query part array + * + * @param string $name the name of the query part to be set + * @param string $part query part string + * @throws Doctrine_Hydrate_Exception if trying to set unknown query part + * @return Doctrine_Hydrate this object + */ + public function setQueryPart($name, $part) + { + if ( ! isset($this->parts[$name])) { + throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name); + } + + if ($name !== 'limit' && $name !== 'offset') { + if (is_array($part)) { + $this->parts[$name] = $part; + } else { + $this->parts[$name] = array($part); + } + } else { + $this->parts[$name] = $part; + } + + return $this; + } + + /** + * addQueryPart + * adds a query part in the query part array + * + * @param string $name the name of the query part to be added + * @param string $part query part string + * @throws Doctrine_Hydrate_Exception if trying to add unknown query part + * @return Doctrine_Hydrate this object + */ + public function addQueryPart($name, $part) + { + if ( ! isset($this->parts[$name])) { + throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name); + } + if (is_array($part)) { + $this->parts[$name] = array_merge($this->parts[$name], $part); + } else { + $this->parts[$name][] = $part; + } + return $this; + } + + /** + * removeQueryPart + * removes a query part from the query part array + * + * @param string $name the name of the query part to be removed + * @throws Doctrine_Hydrate_Exception if trying to remove unknown query part + * @return Doctrine_Hydrate this object + */ + public function removeQueryPart($name) + { + if ( ! isset($this->parts[$name])) { + throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name); + } + + if ($name == 'limit' || $name == 'offset') { + $this->parts[$name] = false; + } else { + $this->parts[$name] = array(); + } + return $this; + } + + /** + * setView + * sets a database view this query object uses + * this method should only be called internally by doctrine + * + * @param Doctrine_View $view database view + * @return void + */ + public function setView(Doctrine_View $view) + { + $this->_view = $view; + } + + /** + * getView + * returns the view associated with this query object (if any) + * + * @return Doctrine_View the view associated with this query object + */ + public function getView() + { + return $this->_view; + } + + /** + * limitSubqueryUsed + * + * @return boolean + */ + public function isLimitSubqueryUsed() + { + return $this->isLimitSubqueryUsed; + } + + /** + * convertEnums + * convert enum parameters to their integer equivalents + * + * @return array converted parameter array + */ + public function convertEnums($params) + { + foreach ($this->_enumParams as $key => $values) { + if (isset($params[$key])) { + if ( ! empty($values)) { + $params[$key] = $values[0]->enumIndex($values[1], $params[$key]); + } + } + } + return $params; + } + + /** + * applyInheritance + * applies column aggregation inheritance to DQL / SQL query + * + * @return string + */ + public function applyInheritance() + { + // get the inheritance maps + $array = array(); + + foreach ($this->_aliasMap as $componentAlias => $data) { + $tableAlias = $this->getTableAlias($componentAlias); + $array[$tableAlias][] = $data['table']->inheritanceMap; + } + + // apply inheritance maps + $str = ''; + $c = array(); + + $index = 0; + foreach ($array as $tableAlias => $maps) { + $a = array(); + + // don't use table aliases if the query isn't a select query + if ($this->type !== Doctrine_Query::SELECT) { + $tableAlias = ''; + } else { + $tableAlias .= '.'; + } + + foreach ($maps as $map) { + $b = array(); + foreach ($map as $field => $value) { + $identifier = $this->_conn->quoteIdentifier($tableAlias . $field); + + if ($index > 0) { + $b[] = '(' . $identifier . ' = ' . $this->_conn->quote($value) + . ' OR ' . $identifier . ' IS NULL)'; + } else { + $b[] = $identifier . ' = ' . $this->_conn->quote($value); + } + } + + if ( ! empty($b)) { + $a[] = implode(' AND ', $b); + } + } + + if ( ! empty($a)) { + $c[] = implode(' AND ', $a); + } + $index++; + } + + $str .= implode(' AND ', $c); + + return $str; + } + + /** + * getTableAlias + * some database such as Oracle need the identifier lengths to be < ~30 chars + * hence Doctrine creates as short identifier aliases as possible + * + * this method is used for the creation of short table aliases, its also + * smart enough to check if an alias already exists for given component (componentAlias) + * + * @param string $componentAlias the alias for the query component to search table alias for + * @param string $tableName the table name from which the table alias is being created + * @return string the generated / fetched short alias + */ + public function getTableAlias($componentAlias, $tableName = null) + { + $alias = array_search($componentAlias, $this->_tableAliases); + + if ($alias !== false) { + return $alias; + } + + if ($tableName === null) { + throw new Doctrine_Hydrate_Exception("Couldn't get short alias for " . $componentAlias); + } + + return $this->generateTableAlias($componentAlias, $tableName); + } + + /** + * generateNewTableAlias + * generates a new alias from given table alias + * + * @param string $tableAlias table alias from which to generate the new alias from + * @return string the created table alias + */ + public function generateNewTableAlias($tableAlias) + { + if (isset($this->_tableAliases[$tableAlias])) { + // generate a new alias + $name = substr($tableAlias, 0, 1); + $i = ((int) substr($tableAlias, 1)); + + if ($i == 0) { + $i = 1; + } + + $newIndex = ($this->_tableAliasSeeds[$name] + $i); + + return $name . $newIndex; + } + + return $tableAlias; + } + + /** + * getTableAliasSeed + * returns the alias seed for given table alias + * + * @param string $tableAlias table alias that identifies the alias seed + * @return integer table alias seed + */ + public function getTableAliasSeed($tableAlias) + { + if ( ! isset($this->_tableAliasSeeds[$tableAlias])) { + return 0; + } + return $this->_tableAliasSeeds[$tableAlias]; + } + + /** + * hasAliasDeclaration + * whether or not this object has a declaration for given component alias + * + * @param string $componentAlias the component alias the retrieve the declaration from + * @return boolean + */ + public function hasAliasDeclaration($componentAlias) + { + return isset($this->_aliasMap[$componentAlias]); + } + + /** + * getAliasDeclaration + * get the declaration for given component alias + * + * @param string $componentAlias the component alias the retrieve the declaration from + * @return array the alias declaration + */ + public function getAliasDeclaration($componentAlias) + { + if ( ! isset($this->_aliasMap[$componentAlias])) { + throw new Doctrine_Hydrate_Exception('Unknown component alias ' . $componentAlias); + } + + return $this->_aliasMap[$componentAlias]; + } + + /** + * copyAliases + * copy aliases from another Hydrate object + * + * this method is needed by DQL subqueries which need the aliases + * of the parent query + * + * @param Doctrine_Hydrate $query the query object from which the + * aliases are copied from + * @return Doctrine_Hydrate this object + */ + public function copyAliases(Doctrine_Query_Abstract $query) + { + $this->_tableAliases = $query->_tableAliases; + $this->_aliasMap = $query->_aliasMap; + $this->_tableAliasSeeds = $query->_tableAliasSeeds; + return $this; + } + + /** + * getRootAlias + * returns the alias of the the root component + * + * @return array + */ + public function getRootAlias() + { + if ( ! $this->_aliasMap) { + $this->getSql(); + } + reset($this->_aliasMap); + + return key($this->_aliasMap); + } + + /** + * getRootDeclaration + * returns the root declaration + * + * @return array + */ + public function getRootDeclaration() + { + $map = reset($this->_aliasMap); + return $map; + } + + /** + * getRoot + * returns the root component for this object + * + * @return Doctrine_Table root components table + */ + public function getRoot() + { + $map = reset($this->_aliasMap); + + if ( ! isset($map['table'])) { + throw new Doctrine_Hydrate_Exception('Root component not initialized.'); + } + + return $map['table']; + } + + /** + * generateTableAlias + * generates a table alias from given table name and associates + * it with given component alias + * + * @param string $componentAlias the component alias to be associated with generated table alias + * @param string $tableName the table name from which to generate the table alias + * @return string the generated table alias + */ + public function generateTableAlias($componentAlias, $tableName) + { + $char = strtolower(substr($tableName, 0, 1)); + + $alias = $char; + + if ( ! isset($this->_tableAliasSeeds[$alias])) { + $this->_tableAliasSeeds[$alias] = 1; + } + + while (isset($this->_tableAliases[$alias])) { + if ( ! isset($this->_tableAliasSeeds[$alias])) { + $this->_tableAliasSeeds[$alias] = 1; + } + $alias = $char . ++$this->_tableAliasSeeds[$alias]; + } + + $this->_tableAliases[$alias] = $componentAlias; + + return $alias; + } + + /** + * getComponentAlias + * get component alias associated with given table alias + * + * @param string $tableAlias the table alias that identifies the component alias + * @return string component alias + */ + public function getComponentAlias($tableAlias) + { + if ( ! isset($this->_tableAliases[$tableAlias])) { + throw new Doctrine_Hydrate_Exception('Unknown table alias ' . $tableAlias); + } + return $this->_tableAliases[$tableAlias]; + } + + /** + * _execute + * + * @param array $params + * @return void + */ + public function _execute($params) + { + $params = $this->_conn->convertBooleans($params); + + if ( ! $this->_view) { + $query = $this->getQuery($params); + } else { + $query = $this->_view->getSelectSql(); + } + + $params = $this->convertEnums($params); + + if ($this->isLimitSubqueryUsed() && + $this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME) !== 'mysql') { + + $params = array_merge($params, $params); + } + + if ($this->type !== self::SELECT) { + return $this->_conn->exec($query, $params); + } + //echo $query . "

"; + $stmt = $this->_conn->execute($query, $params); + return $stmt; + } + + /** + * execute + * executes the query and populates the data set + * + * @param string $params + * @return Doctrine_Collection the root collection + */ + public function execute($params = array(), $hydrationMode = null) + { + $params = array_merge($this->_params['set'], + $this->_params['where'], + $this->_params['having'], + $params); + if ($this->_cache) { + $cacheDriver = $this->getCacheDriver(); + + $dql = $this->getDql(); + // calculate hash for dql query + $hash = md5($dql . var_export($params, true)); + + $cached = ($this->_expireCache) ? false : $cacheDriver->fetch($hash); + + + if ($cached === false) { + // cache miss + $stmt = $this->_execute($params); + $array = $this->_hydrator->hydrateResultSet($stmt, $this->_aliasMap, + $this->_tableAliases, Doctrine::HYDRATE_ARRAY); + + $cached = $this->getCachedForm($array); + + $cacheDriver->save($hash, $cached, $this->_timeToLive); + } else { + $cached = unserialize($cached); + $this->_tableAliases = $cached[2]; + $array = $cached[0]; + + $map = array(); + foreach ($cached[1] as $k => $v) { + $e = explode('.', $v[0]); + if (count($e) === 1) { + $map[$k]['table'] = $this->_conn->getTable($e[0]); + } else { + $map[$k]['parent'] = $e[0]; + $map[$k]['relation'] = $map[$e[0]]['table']->getRelation($e[1]); + $map[$k]['table'] = $map[$k]['relation']->getTable(); + } + if (isset($v[1])) { + $map[$k]['agg'] = $v[1]; + } + } + $this->_aliasMap = $map; + } + } else { + $stmt = $this->_execute($params); + + if (is_integer($stmt)) { + return $stmt; + } + + $array = $this->_hydrator->hydrateResultSet($stmt, $this->_aliasMap, + $this->_tableAliases, $hydrationMode); + } + return $array; + } + + /** * addSelect * adds fields to the SELECT part of the query @@ -43,6 +804,21 @@ abstract class Doctrine_Query_Abstract extends Doctrine_Hydrate { return $this->parseQueryPart('select', $select, true); } + + /** + * addTableAlias + * adds an alias for table and associates it with given component alias + * + * @param string $componentAlias the alias for the query component associated with given tableAlias + * @param string $tableAlias the table alias to be added + * @return Doctrine_Hydrate + */ + public function addTableAlias($tableAlias, $componentAlias) + { + $this->_tableAliases[$tableAlias] = $componentAlias; + + return $this; + } /** * addFrom @@ -376,6 +1152,154 @@ abstract class Doctrine_Query_Abstract extends Doctrine_Hydrate { return $this->parseQueryPart('offset', $offset); } + + /** + * getSql + * return the sql associated with this object + * + * @return string sql query string + */ + public function getSql() + { + return $this->getQuery(); + } + + /** + * clear + * resets all the variables + * + * @return void + */ + protected function clear() + { + $this->parts = array( + 'select' => array(), + 'distinct' => false, + 'forUpdate' => false, + 'from' => array(), + 'set' => array(), + 'join' => array(), + 'where' => array(), + 'groupby' => array(), + 'having' => array(), + 'orderby' => array(), + 'limit' => false, + 'offset' => false, + ); + $this->inheritanceApplied = false; + } + + public function setHydrationMode($hydrationMode) + { + $this->_hydrator->setHydrationMode($hydrationMode); + return $this; + } + + public function getAliasMap() + { + return $this->_aliasMap; + } + + /** + * Return the SQL parts. + * + * @return array The parts + */ + public function getParts() + { + return $this->parts; + } + + /** + * getType + * + * returns the type of this query object + * by default the type is Doctrine_Hydrate::SELECT but if update() or delete() + * are being called the type is Doctrine_Hydrate::UPDATE and Doctrine_Hydrate::DELETE, + * respectively + * + * @see Doctrine_Hydrate::SELECT + * @see Doctrine_Hydrate::UPDATE + * @see Doctrine_Hydrate::DELETE + * + * @return integer return the query type + */ + public function getType() + { + return $this->type; + } + + /** + * useCache + * + * @param Doctrine_Cache_Interface|bool $driver cache driver + * @param integer $timeToLive how long the cache entry is valid + * @return Doctrine_Hydrate this object + */ + public function useCache($driver = true, $timeToLive = null) + { + if($driver !== null && $driver !== true && ! ($driver instanceOf Doctrine_Cache_Interface)){ + $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.'; + throw new Doctrine_Hydrate_Exception($msg); + } + $this->_cache = $driver; + + return $this->setCacheLifeSpan($timeToLive); + } + + /** + * expireCache + * + * @param boolean $expire whether or not to force cache expiration + * @return Doctrine_Hydrate this object + */ + public function expireCache($expire = true) + { + $this->_expireCache = true; + + return $this; + } + + /** + * setCacheLifeSpan + * + * @param integer $timeToLive how long the cache entry is valid + * @return Doctrine_Hydrate this object + */ + public function setCacheLifeSpan($timeToLive) + { + if ($timeToLive !== null) { + $timeToLive = (int) $timeToLive; + } + $this->_timeToLive = $timeToLive; + + return $this; + } + + /** + * getCacheDriver + * returns the cache driver associated with this object + * + * @return Doctrine_Cache_Interface|boolean|null cache driver + */ + public function getCacheDriver() + { + if ($this->_cache instanceof Doctrine_Cache_Interface) { + return $this->_cache; + } else { + return $this->_conn->getCacheDriver(); + } + } + + /** + * getConnection + * + * @return Doctrine_Connection + */ + public function getConnection() + { + return $this->_conn; + } /** * parseQueryPart @@ -389,4 +1313,10 @@ abstract class Doctrine_Query_Abstract extends Doctrine_Hydrate * @return Doctrine_Query this object */ abstract public function parseQueryPart($queryPartName, $queryPart, $append = false); + + /** + * + * + */ + abstract public function getQuery($params = array()); } diff --git a/lib/Doctrine/Query/From.php b/lib/Doctrine/Query/From.php index 15902e141..a76323626 100644 --- a/lib/Doctrine/Query/From.php +++ b/lib/Doctrine/Query/From.php @@ -35,7 +35,7 @@ class Doctrine_Query_From extends Doctrine_Query_Part /** * DQL FROM PARSER * parses the from part of the query string - + * * @param string $str * @return void */ diff --git a/tests/HydrateTestCase.php b/tests/HydrateTestCase.php index 0de22e76b..14614a431 100644 --- a/tests/HydrateTestCase.php +++ b/tests/HydrateTestCase.php @@ -89,7 +89,7 @@ class HydrationListener extends Doctrine_Record_Listener } } } -class Doctrine_Hydrate_Mock extends Doctrine_Hydrate +class Doctrine_Hydrate_Mock extends Doctrine_Hydrator_Abstract { protected $data; @@ -97,12 +97,9 @@ class Doctrine_Hydrate_Mock extends Doctrine_Hydrate { $this->data = $data; } - public function getQuery($params = array()) + + public function hydrateResultSet($stmt, $aliasMap, $tableAliases, $hydrationMode = null) { - - } - public function execute($params = array(), $hydrationMode = null) - { - return $this->data; + return true; } } diff --git a/tests/run.php b/tests/run.php index 0a028effb..b6f7669b1 100644 --- a/tests/run.php +++ b/tests/run.php @@ -281,3 +281,4 @@ $test->addTestCase($data); $test->run(); +echo memory_get_peak_usage() / 1024; \ No newline at end of file