/** * 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; }
/** * 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; }