From 890d7d783c9a45a4a55af0c4dda1925702cbe842 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Tue, 6 Sep 2022 16:00:51 -0400 Subject: [PATCH] Optimize hydration performance 5/5 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. --- .../Internal/Hydration/AbstractHydrator.php | 7 ++- .../ORM/Internal/Hydration/ObjectHydrator.php | 49 ++++++++----------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index f88fb4621ff..1d674fcc281 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -414,7 +414,7 @@ abstract protected function hydrateAllData(); * data: array, * newObjects?: array, * scalars?: array * } @@ -548,8 +548,10 @@ protected function gatherRowDataFromScalarsMapping(array $data) * * @psalm-return array + * + * @psalm-suppress MoreSpecificReturnType Psalm does not detect that args keys is always defined */ protected function gatherRowDataFromNewObjectsMapping(array $data) { @@ -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; } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 69235b0bd07..97c46b94bc6 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -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; @@ -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])) { @@ -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) @@ -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])) { @@ -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);