Example #1
0
 /**
  * hydrateResultSet
  * parses the data returned by statement object
  *
  * This is method defines the core of Doctrine's 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
  * @param array $tableAliases  Array that maps table aliases (SQL alias => DQL alias)
  * @param array $aliasMap  Array that maps DQL aliases to their components
  *                         (DQL alias => array(
  *                              'table' => Table object,
  *                              'parent' => Parent DQL alias (if any),
  *                              'relation' => Relation object (if any),
  *                              'map' => Custom index to use as the key in the result (if any)
  *                              )
  *                         )
  * @return array
  */
 public function hydrateResultSet($stmt, $tableAliases, $hydrationMode = null)
 {
     //$s = microtime(true);
     $this->_tableAliases = $tableAliases;
     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_ArrayDriver();
     } else {
         $driver = new Doctrine_Hydrator_RecordDriver();
     }
     $event = new Doctrine_Event(null, Doctrine_Event::HYDRATE, null);
     // Used variables during hydration
     reset($this->_queryComponents);
     $rootAlias = key($this->_queryComponents);
     $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getComponentName();
     // if only one component is involved we can make our lives easier
     $isSimpleQuery = count($this->_queryComponents) <= 1;
     // Holds the resulting hydrated data structure
     $result = array();
     // Holds hydration listeners that get called during hydration
     $listeners = array();
     // Lookup map to quickly discover/lookup existing records in the result
     $identifierMap = array();
     // Holds for each component the last previously seen element in the result set
     $prev = array();
     // holds the values of the identifier/primary key fields of components,
     // separated by a pipe '|' and grouped by component alias (r, u, i, ... whatever)
     $id = array();
     $result = $driver->getElementCollection($rootComponentName);
     if ($stmt === false || $stmt === 0) {
         return $result;
     }
     // Initialize
     foreach ($this->_queryComponents as $dqlAlias => $data) {
         $componentName = $data['table']->getComponentName();
         $listeners[$componentName] = $data['table']->getRecordListener();
         $identifierMap[$dqlAlias] = array();
         $prev[$dqlAlias] = array();
         $id[$dqlAlias] = '';
     }
     // Process result set
     $cache = array();
     while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) {
         $nonemptyComponents = array();
         $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
         //
         // hydrate the data of the root component from the current row
         //
         $table = $this->_queryComponents[$rootAlias]['table'];
         $componentName = $table->getComponentName();
         $event->set('data', $rowData[$rootAlias]);
         $listeners[$componentName]->preHydrate($event);
         $element = $driver->getElement($rowData[$rootAlias], $componentName);
         $index = false;
         // Check for an existing element
         if ($isSimpleQuery || !isset($identifierMap[$rootAlias][$id[$rootAlias]])) {
             $event->set('data', $element);
             $listeners[$componentName]->postHydrate($event);
             // do we need to index by a custom field?
             if ($field = $this->_getCustomIndexField($rootAlias)) {
                 if (isset($result[$field])) {
                     throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found non-unique key mapping.");
                 } else {
                     if (!isset($element[$field])) {
                         throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found a non-existent key.");
                     }
                 }
                 $result[$element[$field]] = $element;
             } else {
                 $result[] = $element;
             }
             $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($result);
         } else {
             $index = $identifierMap[$rootAlias][$id[$rootAlias]];
         }
         $this->_setLastElement($prev, $result, $index, $rootAlias, false);
         unset($rowData[$rootAlias]);
         // end hydrate data of the root component for the current row
         // $prev[$rootAlias] now points to the last element in $result.
         // now hydrate the rest of the data found in the current row, that belongs to other
         // (related) components.
         $oneToOne = false;
         foreach ($rowData as $dqlAlias => $data) {
             $index = false;
             $map = $this->_queryComponents[$dqlAlias];
             $table = $map['table'];
             $componentName = $table->getComponentName();
             $event->set('data', $data);
             $listeners[$componentName]->preHydrate($event);
             $element = $driver->getElement($data, $componentName);
             $parent = $map['parent'];
             $relation = $map['relation'];
             $relationAlias = $map['relation']->getAlias();
             $path = $parent . '.' . $dqlAlias;
             if (!isset($prev[$parent])) {
                 break;
             }
             // check the type of the relation
             if (!$relation->isOneToOne() && $driver->initRelated($prev[$parent], $relationAlias)) {
                 // append element
                 if (isset($nonemptyComponents[$dqlAlias])) {
                     if ($isSimpleQuery || !isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]])) {
                         $event->set('data', $element);
                         $listeners[$componentName]->postHydrate($event);
                         if ($field = $this->_getCustomIndexField($dqlAlias)) {
                             if (isset($prev[$parent][$relationAlias][$field])) {
                                 throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found non-unique key mapping.");
                             } else {
                                 if (!isset($element[$field])) {
                                     throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found a non-existent key.");
                                 }
                             }
                             $prev[$parent][$relationAlias][$element[$field]] = $element;
                         } else {
                             $prev[$parent][$relationAlias][] = $element;
                         }
                         $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey($prev[$parent][$relationAlias]);
                     } else {
                         $index = $identifierMap[$path][$id[$parent]][$id[$dqlAlias]];
                     }
                 }
                 // register collection for later snapshots
                 $driver->registerCollection($prev[$parent][$relationAlias]);
             } else {
                 // 1-1 relation
                 $oneToOne = true;
                 if (!isset($nonemptyComponents[$dqlAlias])) {
                     $prev[$parent][$relationAlias] = $driver->getNullPointer();
                 } else {
                     $prev[$parent][$relationAlias] = $element;
                 }
             }
             $coll =& $prev[$parent][$relationAlias];
             $this->_setLastElement($prev, $coll, $index, $dqlAlias, $oneToOne);
             $id[$dqlAlias] = '';
         }
         $id[$rootAlias] = '';
     }
     $stmt->closeCursor();
     $driver->flush();
     //$e = microtime(true);
     //echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records<br />';
     return $result;
 }
Example #2
0
 /**
  * hydrateResultSet
  * parses the data returned by statement object
  *
  * This is method defines the core of Doctrine's 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
  * @param array $tableAliases  Array that maps table aliases (SQL alias => DQL alias)
  * @param array $aliasMap  Array that maps DQL aliases to their components
  *                         (DQL alias => array(
  *                              'table' => Table object,
  *                              'parent' => Parent DQL alias (if any),
  *                              'relation' => Relation object (if any),
  *                              'map' => Custom index to use as the key in the result (if any)
  *                              )
  *                         )
  * @return array
  */
 public function hydrateResultSet($stmt, $tableAliases)
 {
     $hydrationMode = $this->_hydrationMode;
     $this->_tableAliases = $tableAliases;
     if ($hydrationMode == Doctrine::HYDRATE_NONE) {
         return $stmt->fetchAll(PDO::FETCH_NUM);
     }
     if ($hydrationMode == Doctrine::HYDRATE_ARRAY) {
         $driver = new Doctrine_Hydrator_ArrayDriver();
     } else {
         $driver = new Doctrine_Hydrator_RecordDriver();
     }
     // Used variables during hydration
     reset($this->_queryComponents);
     $rootAlias = key($this->_queryComponents);
     $this->_rootAlias = $rootAlias;
     $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getComponentName();
     // if only one component is involved we can make our lives easier
     $isSimpleQuery = count($this->_queryComponents) <= 1;
     // Holds the resulting hydrated data structure
     $result = array();
     // Holds hydration listeners that get called during hydration
     $listeners = array();
     // Lookup map to quickly discover/lookup existing records in the result
     $identifierMap = array();
     // Holds for each component the last previously seen element in the result set
     $prev = array();
     // holds the values of the identifier/primary key fields of components,
     // separated by a pipe '|' and grouped by component alias (r, u, i, ... whatever)
     // the $idTemplate is a prepared template. $id is set to a fresh template when
     // starting to process a row.
     $id = array();
     $idTemplate = array();
     $result = $driver->getElementCollection($rootComponentName);
     if ($stmt === false || $stmt === 0) {
         return $result;
     }
     // Initialize
     foreach ($this->_queryComponents as $dqlAlias => $data) {
         $componentName = $data['table']->getComponentName();
         $listeners[$componentName] = $data['table']->getRecordListener();
         $identifierMap[$dqlAlias] = array();
         $prev[$dqlAlias] = null;
         $idTemplate[$dqlAlias] = '';
     }
     $event = new Doctrine_Event(null, Doctrine_Event::HYDRATE, null);
     // Process result set
     $cache = array();
     while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) {
         $id = $idTemplate;
         // initialize the id-memory
         $nonemptyComponents = array();
         $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
         //
         // hydrate the data of the root component from the current row
         //
         $table = $this->_queryComponents[$rootAlias]['table'];
         $componentName = $table->getComponentName();
         // Ticket #1115 (getInvoker() should return the component that has addEventListener)
         $event->setInvoker($table);
         $event->set('data', $rowData[$rootAlias]);
         $listeners[$componentName]->preHydrate($event);
         $index = false;
         // Check for an existing element
         if ($isSimpleQuery || !isset($identifierMap[$rootAlias][$id[$rootAlias]])) {
             $element = $driver->getElement($rowData[$rootAlias], $componentName);
             $event->set('data', $element);
             $listeners[$componentName]->postHydrate($event);
             // do we need to index by a custom field?
             if ($field = $this->_getCustomIndexField($rootAlias)) {
                 if (isset($result[$field])) {
                     throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found non-unique key mapping named '{$field}'.");
                 } else {
                     if (!isset($element[$field])) {
                         throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found a non-existent key named '{$field}'.");
                     }
                 }
                 $result[$element[$field]] = $element;
             } else {
                 $result[] = $element;
             }
             $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($result);
         } else {
             $index = $identifierMap[$rootAlias][$id[$rootAlias]];
         }
         $driver->setLastElement($prev, $result, $index, $rootAlias, false);
         unset($rowData[$rootAlias]);
         // end hydrate data of the root component for the current row
         // $prev[$rootAlias] now points to the last element in $result.
         // now hydrate the rest of the data found in the current row, that belongs to other
         // (related) components.
         foreach ($rowData as $dqlAlias => $data) {
             $index = false;
             $map = $this->_queryComponents[$dqlAlias];
             $table = $map['table'];
             $componentName = $table->getComponentName();
             $event->set('data', $data);
             $event->setInvoker($table);
             $listeners[$componentName]->preHydrate($event);
             // It would be nice if this could be moved to the query parser but I could not find a good place to implement it
             if (!isset($map['parent'])) {
                 throw new Doctrine_Hydrator_Exception('"' . $componentName . '" with an alias of "' . $dqlAlias . '"' . ' in your query does not reference the parent component it is related to.');
             }
             $parent = $map['parent'];
             $relation = $map['relation'];
             $relationAlias = $map['relation']->getAlias();
             $path = $parent . '.' . $dqlAlias;
             if (!isset($prev[$parent])) {
                 unset($prev[$dqlAlias]);
                 // Ticket #1228
                 continue;
             }
             // check the type of the relation
             if (!$relation->isOneToOne() && $driver->initRelated($prev[$parent], $relationAlias)) {
                 $oneToOne = false;
                 // append element
                 if (isset($nonemptyComponents[$dqlAlias])) {
                     $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
                     $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
                     $indexIsValid = $index !== false ? isset($prev[$parent][$relationAlias][$index]) : false;
                     if (!$indexExists || !$indexIsValid) {
                         $element = $driver->getElement($data, $componentName);
                         $event->set('data', $element);
                         $listeners[$componentName]->postHydrate($event);
                         if ($field = $this->_getCustomIndexField($dqlAlias)) {
                             if (isset($prev[$parent][$relationAlias][$element[$field]])) {
                                 throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found non-unique key mapping named '{$field}'.");
                             } else {
                                 if (!isset($element[$field])) {
                                     throw new Doctrine_Hydrator_Exception("Couldn't hydrate. Found a non-existent key named '{$field}'.");
                                 }
                             }
                             $prev[$parent][$relationAlias][$element[$field]] = $element;
                         } else {
                             $prev[$parent][$relationAlias][] = $element;
                         }
                         $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey($prev[$parent][$relationAlias]);
                     }
                     // register collection for later snapshots
                     $driver->registerCollection($prev[$parent][$relationAlias]);
                 }
             } else {
                 // 1-1 relation
                 $oneToOne = true;
                 if (!isset($nonemptyComponents[$dqlAlias]) && !isset($prev[$parent][$relationAlias])) {
                     $prev[$parent][$relationAlias] = $driver->getNullPointer();
                 } else {
                     if (!isset($prev[$parent][$relationAlias])) {
                         $element = $driver->getElement($data, $componentName);
                         // [FIX] Tickets #1205 and #1237
                         $event->set('data', $element);
                         $listeners[$componentName]->postHydrate($event);
                         $prev[$parent][$relationAlias] = $element;
                     }
                 }
             }
             if ($prev[$parent][$relationAlias] !== null) {
                 $coll =& $prev[$parent][$relationAlias];
                 $driver->setLastElement($prev, $coll, $index, $dqlAlias, $oneToOne);
             }
         }
     }
     $stmt->closeCursor();
     $driver->flush();
     //$e = microtime(true);
     //echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records<br />';
     return $result;
 }
 public function hydrateResultSet($stmt)
 {
     return parent::hydrateResultSet($stmt)->toHierarchy();
 }