diff --git a/lib/Doctrine/Hydrate.php b/lib/Doctrine/Hydrate.php index c6832b6b9..764067027 100644 --- a/lib/Doctrine/Hydrate.php +++ b/lib/Doctrine/Hydrate.php @@ -766,6 +766,12 @@ class Doctrine_Hydrate implements Serializable * 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. + * * @param mixed $stmt * @return array */ @@ -788,9 +794,11 @@ class Doctrine_Hydrate implements Serializable } $array = $driver->getElementCollection($componentName); + $identifiable = array(); while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { $parse = true; + foreach ($data as $key => $value) { if ( ! isset($cache[$key])) { $e = explode('__', $key); @@ -808,7 +816,6 @@ class Doctrine_Hydrate implements Serializable } - $tmp = array(); $alias = $cache[$key]['alias']; $component = $cache[$key]['component']; $componentName = $this->_aliasMap[$cache[$key]['alias']]['table']->getComponentName(); @@ -818,125 +825,149 @@ class Doctrine_Hydrate implements Serializable if ( ! isset($currData[$alias])) { $currData[$alias] = array(); } - - if ( ! isset($prevData[$alias])) { - $prevData[$alias] = array(); - } - if ( ! isset($prevElement[$alias])) { - $prevElement[$alias] = array(); - } + if ( ! isset($prev[$alias])) { $prev[$alias] = array(); } - if ($alias !== $lastAlias || $parse) { + + $skip = false; + if (($alias !== $lastAlias || $parse) && ! empty($currData[$alias])) { // component changed - $identifiable = $driver->isIdentifiable($currData[$alias], $table); - $element = $driver->getElement($currData[$alias], $componentName); // map aggregate values (if any) - if ($this->mapAggregateValues($element, $currData[$alias], $alias)) { - $identifiable = true; - } - - if ($currData[$alias] !== $prevData[$alias] || $element !== $prevElement[$alias]) { - if ($identifiable) { - if ($alias === $rootAlias) { - // dealing with root component - - $array[$index] = $element; - $prev[$alias] =& $array[$index]; - - $index++; - } else { - $parent = $cache[$key]['parent']; - $relation = $this->_aliasMap[$cache[$key]['alias']]['relation']; - // check the type of the relation - if ( ! $relation->isOneToOne()) { - /*if ($prev[$parent][$component] instanceof Doctrine_Record) { - throw new Exception(); - }*/ - $prev[$parent][$component][] = $element; - - $driver->registerCollection($prev[$parent][$component]); - } else { - $prev[$parent][$component] = $element; - } - - if (is_array($prev[$parent][$component])) { - $prev[$alias] = end($prev[$parent][$component]); - } else { - $prev[$alias] = $prev[$parent][$component]->getLast(); - } - } - if (isset($currData[$alias])) { - $prevData[$alias] = $currData[$alias]; - } else { - $prevData[$alias] = array(); - } - $currData[$alias] = array(); - - $prevElement[$alias] = $element; - } - } - - - } - - $field = $cache[$key]['field']; - - $currData[$alias][$field] = $value; - //print_r($currData[$alias]); - $lastAlias = $alias; - $parse = false; - } - } - foreach ($currData as $alias => $data) { - $table = $this->_aliasMap[$alias]['table']; - $componentName = $table->getComponentName(); - // component changed - $identifiable = $driver->isIdentifiable($currData[$alias], $table); - - $element = $driver->getElement($currData[$alias], $componentName, $identifiable); - - // map aggregate values (if any) - if($this->mapAggregateValues($element, $currData[$alias], $alias)) { - $identifiable = true; - } - - if ( ! isset($prevData[$alias]) || (isset($currData[$alias]) && $currData[$alias] !== $prevData[$alias]) || $element !== $prevElement[$alias]) { - if ($identifiable) { + $this->mapAggregateValues($element, $currData[$alias], $alias); if ($alias === $rootAlias) { // dealing with root component - $array[$index] = $element; - $prev[$alias] =& $array[$index]; - $index++; - } else { - $parent = $this->_aliasMap[$alias]['parent']; - $relation = $this->_aliasMap[$alias]['relation']; - $componentAlias = $relation->getAlias(); - // check the type of the relation + + $index = $driver->search($element, $array); + if ($index === false) { + $array[] = $element; + } + $coll =& $array; + } else { + $parent = $cache[$key]['parent']; + $relation = $this->_aliasMap[$cache[$key]['alias']]['relation']; + // check the type of the relation if ( ! $relation->isOneToOne()) { - $prev[$parent][$componentAlias][] = $element; - $driver->registerCollection($prev[$parent][$componentAlias]); + // initialize the collection + + if ($driver->initRelated($prev[$parent], $component)) { + + // append element + if (isset($identifiable[$alias])) { + $index = $driver->search($element, $prev[$parent][$component]); + + if ($index === false) { + $prev[$parent][$component][] = $element; + } + } + // register collection for later snapshots + $driver->registerCollection($prev[$parent][$component]); + } } else { - $prev[$parent][$componentAlias] = $element; + $prev[$parent][$component] = $element; + } + $coll =& $prev[$parent][$component]; + } + + if ($index !== false) { + $prev[$alias] =& $coll[$index]; + } else { + // first check the count (we do not want to get the last element + // of an empty collection/array) + if (count($coll) > 0) { + // check the type + if (is_array($coll)) { + end($coll); + $prev[$alias] =& $coll[key($coll)]; + } else { + $prev[$alias] = $coll->getLast(); + } + } + } + $currData[$alias] = array(); + $identifiable[$alias] = null; + } + $field = $cache[$key]['field']; + $currData[$alias][$field] = $value; + $index = false; + if ($value !== null) { + $identifiable[$alias] = true; + } + $lastAlias = $alias; + $parse = false; + + } + } + + foreach ($currData as $alias => $data) { + $table = $this->_aliasMap[$alias]['table']; + $componentName = $table->getComponentName(); + // component changed + + $element = $driver->getElement($currData[$alias], $componentName); + + // map aggregate values (if any) + $this->mapAggregateValues($element, $currData[$alias], $alias); + + + if ($alias === $rootAlias) { + // dealing with root component + $index = $driver->search($element, $array); + if ($index === false) { + $array[] = $element; + } + $coll =& $array; + } else { + $parent = $this->_aliasMap[$alias]['parent']; + $relation = $this->_aliasMap[$alias]['relation']; + $componentAlias = $relation->getAlias(); + + // check the type of the relation + if ( ! $relation->isOneToOne()) { + // initialize the collection + + if ($driver->initRelated($prev[$parent], $componentAlias)) { + + // append element + if (isset($identifiable[$alias])) { + $index = $driver->search($element, $prev[$parent][$componentAlias]); + + if ($index === false) { + $prev[$parent][$componentAlias][] = $element; + } + } + // register collection for later snapshots + $driver->registerCollection($prev[$parent][$componentAlias]); + } + } else { + $prev[$parent][$componentAlias] = $element; + } + $coll =& $prev[$parent][$componentAlias]; + } + + if ($index !== false) { + $prev[$alias] =& $coll[$index]; + } else { + // 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)) { + end($coll); + $prev[$alias] =& $coll[key($coll)]; + } else { + $prev[$alias] = $coll->getLast(); } } } - } - if (isset($currData[$alias])) { - $prevData[$alias] = $currData[$alias]; - } else { - $prevData[$alias] = array(); - } + $index = false; $currData[$alias] = array(); - - $prevElement[$alias] = $element; + unset($identifiable[$alias]); } $driver->flush(); diff --git a/lib/Doctrine/Hydrate/Array.php b/lib/Doctrine/Hydrate/Array.php index 8ad5b59dd..f1e8f37af 100644 --- a/lib/Doctrine/Hydrate/Array.php +++ b/lib/Doctrine/Hydrate/Array.php @@ -31,7 +31,7 @@ * @version $Revision$ * @author Konsta Vesterinen */ -class Doctrine_Hydrate_Array +class Doctrine_Hydrate_Array { public function getElementCollection($component) { @@ -48,6 +48,28 @@ class Doctrine_Hydrate_Array public function registerCollection($coll) { + } + public function initRelated(array &$data, $name) + { + if ( ! isset($data[$name])) { + $data[$name] = array(); + } + return true; + } + public function search(array $element, array $data) + { + foreach ($data as $key => $val) { + $found = true; + foreach ($element as $k => $e) { + if ($val[$k] !== $e) { + $found = false; + } + } + if ($found) { + return $key; + } + } + return false; } public function flush() {