Source for file Hydrate.php
Documentation is available at Hydrate.php
* $Id: Hydrate.php 2234 2007-08-14 18:28:35Z romanb $
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.com>.
* Doctrine_Hydrate is a base class for Doctrine_RawSql and Doctrine_Query.
* Its purpose is to populate object graphs.
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @version $Revision: 2234 $
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* constant for SELECT queries
* constant for DELETE queries
* constant for UPDATE queries
* constant for INSERT queries
* constant for CREATE queries
* Constant for the array hydration mode.
* Constant for the record (object) hydration mode.
* @var array $params query input parameters
* @var Doctrine_Connection $conn Doctrine_Connection object
* @var Doctrine_View $_view Doctrine_View object, when set this object will use the
* the query given by the view object for object population
* @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
* @var array $aggregateMap an array containing all aggregate aliases, keys as dql aliases
* and values as sql aliases
* @var array $_options an array of options
'fetchMode' =>
Doctrine::FETCH_RECORD,
'resultSetCache' =>
false,
* @var string $_sql cached SQL query
* @var array $parts SQL query string parts
* @var integer $type the query type
* @see Doctrine_Query::* constants
protected $type =
self::SELECT;
* The current hydration mode.
* @var boolean $_expireCache a boolean value that indicates whether or not to force cache expiration
* @var array $_tableAliasSeeds A simple array keys representing table aliases and values
* as table alias seeds. The seeds are used for generating short table
* @param Doctrine_Connection|null$connection
$this->_conn =
$connection;
* returns the alias of the the root component
* returns the root declaration
* returns the root component for this object
* @return Doctrine_Table root components table
if ( ! isset
($map['table'])) {
* return the sql associated with this object
* @return string sql query string
return $this->getQuery();
* @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)
$msg =
'First argument should be instance of Doctrine_Cache_Interface or null.';
* @param boolean $expire whether or not to force cache expiration
* @return Doctrine_Hydrate this object
* @param integer $timeToLive how long the cache entry is valid
* @return Doctrine_Hydrate this object
if ($timeToLive !==
null) {
$timeToLive = (int)
$timeToLive;
* returns the cache driver associated with this object
* @return Doctrine_Cache_Interface|boolean|null cache driver
return $this->_conn->getCacheDriver();
* @param integer $fetchmode One of the Doctrine_Hydrate::HYDRATE_* constants.
* this method is automatically called when this Doctrine_Hydrate is serialized
* @return array an array of serialized properties
* this method is automatically called everytime a Doctrine_Hydrate object is unserialized
* @param string $serialized Doctrine_Record as serialized string
* 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
$name =
substr($tableAlias, 0, 1);
$i =
((int)
substr($tableAlias, 1));
return $name .
$newIndex;
* 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
* get component alias associated with given table alias
* @param string $tableAlias the table alias that identifies the component alias
* @return string component alias
* returns the alias seed for given table alias
* @param string $tableAlias table alias that identifies the alias seed
* @return integer table alias seed
* 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
* returns all table aliases
* @return array table aliases as an array
* 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
* 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)
if ($tableName ===
null) {
* 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
if ( ! isset
($this->parts[$name])) {
$this->parts[$name][] =
$part;
* 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
if ( ! isset
($this->parts[$part])) {
return $this->parts[$part];
* 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
if (isset
($this->parts[$name])) {
if ($name ==
'limit' ||
$name ==
'offset') {
$this->parts[$name] =
false;
$this->parts[$name] =
array();
* 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
if ( ! isset
($this->parts[$name])) {
if ($name !==
'limit' &&
$name !==
'offset') {
$this->parts[$name] =
$part;
$this->parts[$name] =
array($part);
$this->parts[$name] =
$part;
* whether or not this object has a declaration for given component alias
* @param string $componentAlias the component alias the retrieve the declaration from
return isset
($this->_aliasMap[$componentAlias]);
* get the declaration for given component alias
* @param string $componentAlias the component alias the retrieve the declaration from
* @return array the alias declaration
if ( ! isset
($this->_aliasMap[$componentAlias])) {
* copy aliases from another Hydrate object
* this method is needed by DQL subqueries which need the aliases
* @param Doctrine_Hydrate $query the query object from which the
* aliases are copied from
* @return Doctrine_Hydrate this object
* @return Doctrine_Hydrate
// copy the aliases to the subquery
$obj->copyAliases($this);
// this prevents the 'id' being selected, re ticket #307
* whether or not limit subquery was used
* resets all the variables
protected function clear()
$this->inheritanceApplied =
false;
* @return Doctrine_Connection
* sets a database view this query object uses
* this method should only be called internally by doctrine
* @param Doctrine_View $view database view
public function setView(Doctrine_View $view)
* returns the view associated with this query object (if any)
* @return Doctrine_View the view associated with this query object
public function setParams(array $params =
array()) {
* sets the whole component alias map
* @param array $map alias map
* @return Doctrine_Hydrate this object
* returns the component alias map
* @return array component alias map
public function getAliasMap()
* returns the cached form of this query for given resultSet
* @param array $resultSet
* @return string serialized string representation of this query
foreach ($this->getAliasMap() as $k =>
$v) {
if ( ! isset
($v['parent'])) {
$map[$k][] =
$v['table']->getComponentName();
$map[$k][] =
$v['parent'] .
'.' .
$v['relation']->getAlias();
$query =
$this->getQuery($params);
$query =
$this->_view->getSelectSql();
$this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME) !==
'mysql') {
if ($this->type !==
self::SELECT) {
* executes the query and populates the data set
* @return Doctrine_Collection the root collection
public function execute($params =
array(), $hydrationMode =
null)
// calculate hash for dql query
$cached =
($this->_expireCache) ?
null :
$cacheDriver->fetch($hash);
$array =
$this->parseData2($stmt, self::HYDRATE_ARRAY);
$cacheDriver->save($hash, $cached, $this->_timeToLive);
foreach ($cached[1] as $k =>
$v) {
$map[$k]['table'] =
$this->_conn->getTable($e[0]);
$map[$k]['parent'] =
$e[0];
$map[$k]['relation'] =
$map[$e[0]]['table']->getRelation($e[1]);
$map[$k]['table'] =
$map[$k]['relation']->getTable();
$array =
$this->parseData2($stmt, $hydrationMode);
* 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,
* @see Doctrine_Hydrate::SELECT
* @see Doctrine_Hydrate::UPDATE
* @see Doctrine_Hydrate::DELETE
* @return integer return the query type
* applies column aggregation inheritance to DQL / SQL query
// get the inheritance maps
foreach ($this->_aliasMap as $componentAlias =>
$data) {
$array[$tableAlias][] =
$data['table']->inheritanceMap;
// apply inheritance maps
foreach ($array as $tableAlias =>
$maps) {
// don't use table aliases if the query isn't a select query
foreach ($maps as $map) {
foreach ($map as $field =>
$value) {
$identifier =
$this->_conn->quoteIdentifier($tableAlias .
$field);
$b[] =
'(' .
$identifier .
' = ' .
$this->_conn->quote($value)
.
' OR ' .
$identifier .
' IS NULL)';
$b[] =
$identifier .
' = ' .
$this->_conn->quote($value);
* Convenience method to execute using array fetching as hydration mode.
return $this->execute($params, self::HYDRATE_ARRAY);
* Convenience method to execute the query and return the first item
* @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);
switch ($hydrationMode) {
case self::HYDRATE_RECORD:
if (count($collection) >
0) {
return $collection->getFirst();
case self::HYDRATE_ARRAY:
if (!empty($collection[0])) {
* 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.
$componentName =
$rootMap['table']->getComponentName();
if ($hydrationMode ===
null) {
if ($hydrationMode ===
self::HYDRATE_ARRAY) {
$array =
$driver->getElementCollection($componentName);
if ($stmt ===
false ||
$stmt ===
0) {
while ($data =
$stmt->fetch(Doctrine::FETCH_ASSOC)) {
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
if ( ! isset
($cache[$key])) {
$map =
$this->_aliasMap[$cache[$key]['alias']];
$alias =
$cache[$key]['alias'];
$field =
$cache[$key]['field'];
if (isset
($this->_aliasMap[$alias]['agg'][$field])) {
$field =
$this->_aliasMap[$alias]['agg'][$field];
$componentName =
$map['table']->getComponentName();
if (isset
($map['relation'])) {
$componentAlias =
$map['relation']->getAlias();
$componentAlias =
$map['table']->getComponentName();
if ( ! isset
($currData[$alias])) {
$currData[$alias] =
array();
if ( ! isset
($prev[$alias])) {
if (($alias !==
$lastAlias ||
$parse) &&
! empty($currData[$alias])) {
$element =
$driver->getElement($currData[$alias], $componentName);
if ($alias ===
$rootAlias) {
// dealing with root component
$index =
$driver->search($element, $array);
$parent =
$map['parent'];
$relation =
$map['relation'];
if (!isset
($prev[$parent])) {
// check the type of the relation
if ( ! $relation->isOneToOne()) {
// initialize the collection
if ($driver->initRelated($prev[$parent], $componentAlias)) {
if (isset
($identifiable[$alias])) {
$index =
$driver->search($element, $prev[$parent][$componentAlias]);
$prev[$parent][$componentAlias][] =
$element;
// register collection for later snapshots
$driver->registerCollection($prev[$parent][$componentAlias]);
if ( ! isset
($identifiable[$alias])) {
$prev[$parent][$componentAlias] =
$driver->getNullPointer();
$prev[$parent][$componentAlias] =
$element;
$coll =
& $prev[$parent][$componentAlias];
$currData[$alias] =
array();
$identifiable[$alias] =
null;
$currData[$alias][$field] =
$table->prepareValue($field, $value);
$identifiable[$alias] =
true;
foreach ($currData as $alias =>
$data) {
$componentName =
$table->getComponentName();
$element =
$driver->getElement($currData[$alias], $componentName);
if ($alias ===
$rootAlias) {
// dealing with root component
$index =
$driver->search($element, $array);
$parent =
$this->_aliasMap[$alias]['parent'];
$relation =
$this->_aliasMap[$alias]['relation'];
$componentAlias =
$relation->getAlias();
if (!isset
($prev[$parent])) {
// check the type of the relation
if ( ! $relation->isOneToOne()) {
// initialize the collection
if ($driver->initRelated($prev[$parent], $componentAlias)) {
if (isset
($identifiable[$alias])) {
$index =
$driver->search($element, $prev[$parent][$componentAlias]);
$prev[$parent][$componentAlias][] =
$element;
// register collection for later snapshots
$driver->registerCollection($prev[$parent][$componentAlias]);
if ( ! isset
($identifiable[$alias])) {
$prev[$parent][$componentAlias] =
$driver->getNullPointer();
$prev[$parent][$componentAlias] =
$element;
$coll =
& $prev[$parent][$componentAlias];
$currData[$alias] =
array();
unset
($identifiable[$alias]);
* sets the last element of given data array / collection
* @param boolean|integer$index
if ($coll ===
self::$_null) {
$prev[$alias] =
& $coll[$index];
// first check the count (we do not want to get the last element
// of an empty collection/array)
$prev[$alias] =
& $coll[key($coll)];
$prev[$alias] =
$coll->getLast();
if (isset
($prev[$alias])) {
* @return string returns a string representation of this object