vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php line 73

Open in your IDE?
  1. <?php
  2. /*
  3.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14.  *
  15.  * This software consists of voluntary contributions made by many individuals
  16.  * and is licensed under the MIT license. For more information, see
  17.  * <http://www.doctrine-project.org>.
  18.  */
  19. namespace Doctrine\ORM\Internal\Hydration;
  20. use Doctrine\ORM\Mapping\ClassMetadata;
  21. use PDO;
  22. use function count;
  23. use function end;
  24. use function is_array;
  25. use function key;
  26. use function reset;
  27. /**
  28.  * The ArrayHydrator produces a nested array "graph" that is often (not always)
  29.  * interchangeable with the corresponding object graph for read-only access.
  30.  */
  31. class ArrayHydrator extends AbstractHydrator
  32. {
  33.     /** @var array<string,bool> */
  34.     private $_rootAliases = [];
  35.     /** @var bool */
  36.     private $_isSimpleQuery false;
  37.     /** @var mixed[] */
  38.     private $_identifierMap = [];
  39.     /** @var mixed[] */
  40.     private $_resultPointers = [];
  41.     /** @var array<string,string> */
  42.     private $_idTemplate = [];
  43.     /** @var int */
  44.     private $_resultCounter 0;
  45.     /**
  46.      * {@inheritdoc}
  47.      */
  48.     protected function prepare()
  49.     {
  50.         $this->_isSimpleQuery count($this->_rsm->aliasMap) <= 1;
  51.         foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
  52.             $this->_identifierMap[$dqlAlias]  = [];
  53.             $this->_resultPointers[$dqlAlias] = [];
  54.             $this->_idTemplate[$dqlAlias]     = '';
  55.         }
  56.     }
  57.     /**
  58.      * {@inheritdoc}
  59.      */
  60.     protected function hydrateAllData()
  61.     {
  62.         $result = [];
  63.         while ($data $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
  64.             $this->hydrateRowData($data$result);
  65.         }
  66.         return $result;
  67.     }
  68.     /**
  69.      * {@inheritdoc}
  70.      */
  71.     protected function hydrateRowData(array $row, array &$result)
  72.     {
  73.         // 1) Initialize
  74.         $id                 $this->_idTemplate// initialize the id-memory
  75.         $nonemptyComponents = [];
  76.         $rowData            $this->gatherRowData($row$id$nonemptyComponents);
  77.         // 2) Now hydrate the data found in the current row.
  78.         foreach ($rowData['data'] as $dqlAlias => $data) {
  79.             $index false;
  80.             if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
  81.                 // It's a joined result
  82.                 $parent $this->_rsm->parentAliasMap[$dqlAlias];
  83.                 $path   $parent '.' $dqlAlias;
  84.                 // missing parent data, skipping as RIGHT JOIN hydration is not supported.
  85.                 if (! isset($nonemptyComponents[$parent])) {
  86.                     continue;
  87.                 }
  88.                 // Get a reference to the right element in the result tree.
  89.                 // This element will get the associated element attached.
  90.                 if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
  91.                     $first reset($this->_resultPointers);
  92.                     // TODO: Exception if $key === null ?
  93.                     $baseElement =& $this->_resultPointers[$parent][key($first)];
  94.                 } elseif (isset($this->_resultPointers[$parent])) {
  95.                     $baseElement =& $this->_resultPointers[$parent];
  96.                 } else {
  97.                     unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
  98.                     continue;
  99.                 }
  100.                 $relationAlias $this->_rsm->relationMap[$dqlAlias];
  101.                 $parentClass   $this->_metadataCache[$this->_rsm->aliasMap[$parent]];
  102.                 $relation      $parentClass->associationMappings[$relationAlias];
  103.                 // Check the type of the relation (many or single-valued)
  104.                 if (! ($relation['type'] & ClassMetadata::TO_ONE)) {
  105.                     $oneToOne false;
  106.                     if (! isset($baseElement[$relationAlias])) {
  107.                         $baseElement[$relationAlias] = [];
  108.                     }
  109.                     if (isset($nonemptyComponents[$dqlAlias])) {
  110.                         $indexExists  = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
  111.                         $index        $indexExists $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
  112.                         $indexIsValid $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
  113.                         if (! $indexExists || ! $indexIsValid) {
  114.                             $element $data;
  115.                             if (isset($this->_rsm->indexByMap[$dqlAlias])) {
  116.                                 $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element;
  117.                             } else {
  118.                                 $baseElement[$relationAlias][] = $element;
  119.                             }
  120.                             end($baseElement[$relationAlias]);
  121.                             $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
  122.                         }
  123.                     }
  124.                 } else {
  125.                     $oneToOne true;
  126.                     if (
  127.                         ! isset($nonemptyComponents[$dqlAlias]) &&
  128.                         ( ! isset($baseElement[$relationAlias]))
  129.                     ) {
  130.                         $baseElement[$relationAlias] = null;
  131.                     } elseif (! isset($baseElement[$relationAlias])) {
  132.                         $baseElement[$relationAlias] = $data;
  133.                     }
  134.                 }
  135.                 $coll =& $baseElement[$relationAlias];
  136.                 if (is_array($coll)) {
  137.                     $this->updateResultPointer($coll$index$dqlAlias$oneToOne);
  138.                 }
  139.             } else {
  140.                 // It's a root result element
  141.                 $this->_rootAliases[$dqlAlias] = true// Mark as root
  142.                 $entityKey                     $this->_rsm->entityMappings[$dqlAlias] ?: 0;
  143.                 // if this row has a NULL value for the root result id then make it a null result.
  144.                 if (! isset($nonemptyComponents[$dqlAlias])) {
  145.                     $result[] = $this->_rsm->isMixed
  146.                         ? [$entityKey => null]
  147.                         : null;
  148.                     $resultKey $this->_resultCounter;
  149.                     ++$this->_resultCounter;
  150.                     continue;
  151.                 }
  152.                 // Check for an existing element
  153.                 if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
  154.                     $element $this->_rsm->isMixed
  155.                         ? [$entityKey => $data]
  156.                         : $data;
  157.                     if (isset($this->_rsm->indexByMap[$dqlAlias])) {
  158.                         $resultKey          $row[$this->_rsm->indexByMap[$dqlAlias]];
  159.                         $result[$resultKey] = $element;
  160.                     } else {
  161.                         $resultKey $this->_resultCounter;
  162.                         $result[]  = $element;
  163.                         ++$this->_resultCounter;
  164.                     }
  165.                     $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
  166.                 } else {
  167.                     $index     $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
  168.                     $resultKey $index;
  169.                 }
  170.                 $this->updateResultPointer($result$index$dqlAliasfalse);
  171.             }
  172.         }
  173.         if (! isset($resultKey)) {
  174.             $this->_resultCounter++;
  175.         }
  176.         // Append scalar values to mixed result sets
  177.         if (isset($rowData['scalars'])) {
  178.             if (! isset($resultKey)) {
  179.                 // this only ever happens when no object is fetched (scalar result only)
  180.                 $resultKey = isset($this->_rsm->indexByMap['scalars'])
  181.                     ? $row[$this->_rsm->indexByMap['scalars']]
  182.                     : $this->_resultCounter 1;
  183.             }
  184.             foreach ($rowData['scalars'] as $name => $value) {
  185.                 $result[$resultKey][$name] = $value;
  186.             }
  187.         }
  188.         // Append new object to mixed result sets
  189.         if (isset($rowData['newObjects'])) {
  190.             if (! isset($resultKey)) {
  191.                 $resultKey $this->_resultCounter 1;
  192.             }
  193.             $scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0);
  194.             foreach ($rowData['newObjects'] as $objIndex => $newObject) {
  195.                 $class $newObject['class'];
  196.                 $args  $newObject['args'];
  197.                 $obj   $class->newInstanceArgs($args);
  198.                 if (count($args) === $scalarCount || ($scalarCount === && count($rowData['newObjects']) === 1)) {
  199.                     $result[$resultKey] = $obj;
  200.                     continue;
  201.                 }
  202.                 $result[$resultKey][$objIndex] = $obj;
  203.             }
  204.         }
  205.     }
  206.     /**
  207.      * Updates the result pointer for an Entity. The result pointers point to the
  208.      * last seen instance of each Entity type. This is used for graph construction.
  209.      *
  210.      * @param mixed[]|null $coll     The element.
  211.      * @param bool|int     $index    Index of the element in the collection.
  212.      * @param bool         $oneToOne Whether it is a single-valued association or not.
  213.      */
  214.     private function updateResultPointer(
  215.         ?array &$coll,
  216.         $index,
  217.         string $dqlAlias,
  218.         bool $oneToOne
  219.     ): void {
  220.         if ($coll === null) {
  221.             unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
  222.             return;
  223.         }
  224.         if ($oneToOne) {
  225.             $this->_resultPointers[$dqlAlias] =& $coll;
  226.             return;
  227.         }
  228.         if ($index !== false) {
  229.             $this->_resultPointers[$dqlAlias] =& $coll[$index];
  230.             return;
  231.         }
  232.         if (! $coll) {
  233.             return;
  234.         }
  235.         end($coll);
  236.         $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
  237.         return;
  238.     }
  239. }