Skip to content

Commit

Permalink
Optimize hydration performance 5/5
Browse files Browse the repository at this point in the history
The current implementation uses collections object being hydrated as array, this introduces several calls to ArrayAccess implementation
causing a noticeable overhead especially on big collections.
While those checks would seem necessary in a regular use of collections they seem a bit too much in the hydration process where we can check differently the integrity of the collection.
  • Loading branch information
tucksaun committed Nov 15, 2022
1 parent c3274c2 commit 890d7d7
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 31 deletions.
7 changes: 5 additions & 2 deletions lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ abstract protected function hydrateAllData();
* data: array<array-key, array>,
* newObjects?: array<array-key, array{
* class: mixed,
* args?: array
* args: array
* }>,
* scalars?: array
* }
Expand Down Expand Up @@ -548,8 +548,10 @@ protected function gatherRowDataFromScalarsMapping(array $data)
*
* @psalm-return array<array-key, array{
* class: mixed,
* args?: array
* args: array
* }>
*
* @psalm-suppress MoreSpecificReturnType Psalm does not detect that args keys is always defined
*/
protected function gatherRowDataFromNewObjectsMapping(array $data)
{
Expand All @@ -569,6 +571,7 @@ protected function gatherRowDataFromNewObjectsMapping(array $data)
$newObjectsData[$objIndex]['args'][$argIndex] = $value;
}

/** @psalm-suppress LessSpecificReturnStatement Psalm does not detect that args keys is always defined */
return $newObjectsData;
}

Expand Down
49 changes: 20 additions & 29 deletions lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use function array_keys;
use function assert;
use function count;
use function end;
use function is_array;
use function is_object;
use function key;
Expand Down Expand Up @@ -357,9 +358,6 @@ protected function hydrateRowData(array $row, array &$result)
// It's a joined result

$parentAlias = $resultSetMapping->parentAliasMap[$dqlAlias];
// we need the $path to save into the identifier map which entities were already
// seen for this parent-child relationship
$path = $parentAlias . '.' . $dqlAlias;

// We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
if (! isset($nonemptyComponents[$parentAlias])) {
Expand Down Expand Up @@ -395,6 +393,10 @@ protected function hydrateRowData(array $row, array &$result)
$reflField = $parentClass->reflFields[$relationField];
assert($reflField instanceof ReflectionProperty);

// we need the $path to save into the identifier map which entities were already
// seen for this parent-child relationship
$path = $reflField->class . '.' . $reflField->name;

$oid = spl_object_id($parentObject);

// Check the type of the relation (many or single-valued)
Expand All @@ -403,6 +405,7 @@ protected function hydrateRowData(array $row, array &$result)

if (isset($nonemptyComponents[$dqlAlias])) {
$collKey = $oid . $relationField;

if (isset($this->initializedCollections[$collKey])) {
$reflFieldValue = $this->initializedCollections[$collKey];
} elseif (isset($this->existingCollections[$collKey])) {
Expand All @@ -411,42 +414,30 @@ protected function hydrateRowData(array $row, array &$result)
$reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
}

$indexExists = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
$index = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;

if (! $indexExists || ! $indexIsValid) {
if (isset($this->existingCollections[$collKey])) {
// Collection exists, only look for the element in the identity map.
$element = $this->getEntityFromIdentityMap($entityName, $rowData['ids'][$dqlAlias]);
if ($element) {
$resultPointers[$dqlAlias] = $element;
} else {
unset($resultPointers[$dqlAlias]);
}
if (isset($this->existingCollections[$collKey])) {
// Collection exists, only look for the element in the identity map.
$element = $this->getEntityFromIdentityMap($entityName, $rowData['ids'][$dqlAlias]);
} else {
if (isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]])) {
$element = $reflFieldValue[$this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]];
} else {
$element = $this->getEntity($row, $rowData, $dqlAlias);

if (isset($resultSetMapping->indexByMap[$dqlAlias])) {
$indexValue = $row[$resultSetMapping->indexByMap[$dqlAlias]];
$reflFieldValue->hydrateSet($indexValue, $element);
$this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
} elseif (isset($this->identifierMap[$path][$id[$parentAlias]])) {
$indexValue = end($this->identifierMap[$path][$id[$parentAlias]]) + 1;
} else {
if (! $reflFieldValue->contains($element)) {
$reflFieldValue->hydrateAdd($element);
$reflFieldValue->last();
}

$this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
$indexValue = 0;
}

// Update result pointer
$resultPointers[$dqlAlias] = $element;
$reflFieldValue->hydrateSet($indexValue, $element);
$this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
}
} else {
// Update result pointer
$resultPointers[$dqlAlias] = $reflFieldValue[$index];
}

// Update result pointer
$resultPointers[$dqlAlias] = $element;
// phpcs:ignore SlevomatCodingStandard.ControlStructures.AssignmentInCondition.AssignmentInCondition
} elseif (! ($reflFieldValue = $reflField->getValue($parentObject))) {
$this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
Expand Down

0 comments on commit 890d7d7

Please sign in to comment.