/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php

https://github.com/jaikdean/doctrine2 · PHP · 280 lines · 179 code · 59 blank · 42 comment · 35 complexity · 8d59b5fd2d00f59d9041bb2f78f8e193 MD5 · raw file

  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Internal\Hydration;
  4. use Doctrine\DBAL\FetchMode;
  5. use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
  6. use function count;
  7. use function end;
  8. use function is_array;
  9. use function key;
  10. use function reset;
  11. /**
  12. * The ArrayHydrator produces a nested array "graph" that is often (not always)
  13. * interchangeable with the corresponding object graph for read-only access.
  14. */
  15. class ArrayHydrator extends AbstractHydrator
  16. {
  17. /** @var bool[] */
  18. private $rootAliases = [];
  19. /** @var bool */
  20. private $isSimpleQuery = false;
  21. /** @var mixed[][] */
  22. private $identifierMap = [];
  23. /** @var mixed[] */
  24. private $resultPointers = [];
  25. /** @var string[] */
  26. private $idTemplate = [];
  27. /** @var int */
  28. private $resultCounter = 0;
  29. /**
  30. * {@inheritdoc}
  31. */
  32. protected function prepare()
  33. {
  34. $simpleQuery = 0;
  35. foreach ($this->rsm->aliasMap as $dqlAlias => $className) {
  36. $this->identifierMap[$dqlAlias] = [];
  37. $this->resultPointers[$dqlAlias] = [];
  38. $this->idTemplate[$dqlAlias] = '';
  39. $simpleQuery += 1; // avoiding counting the alias map
  40. }
  41. $this->isSimpleQuery = $simpleQuery < 2;
  42. }
  43. /**
  44. * {@inheritdoc}
  45. */
  46. protected function hydrateAllData()
  47. {
  48. $result = [];
  49. while ($data = $this->stmt->fetch(FetchMode::ASSOCIATIVE)) {
  50. $this->hydrateRowData($data, $result);
  51. }
  52. return $result;
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. protected function hydrateRowData(array $row, array &$result)
  58. {
  59. // 1) Initialize
  60. $id = $this->idTemplate; // initialize the id-memory
  61. $nonemptyComponents = [];
  62. $rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
  63. // 2) Now hydrate the data found in the current row.
  64. foreach ($rowData['data'] as $dqlAlias => $data) {
  65. $index = false;
  66. if (isset($this->rsm->parentAliasMap[$dqlAlias])) {
  67. // It's a joined result
  68. $parent = $this->rsm->parentAliasMap[$dqlAlias];
  69. $path = $parent . '.' . $dqlAlias;
  70. // missing parent data, skipping as RIGHT JOIN hydration is not supported.
  71. if (! isset($nonemptyComponents[$parent])) {
  72. continue;
  73. }
  74. // Get a reference to the right element in the result tree.
  75. // This element will get the associated element attached.
  76. if ($this->rsm->isMixed && isset($this->rootAliases[$parent])) {
  77. $first = reset($this->resultPointers);
  78. // TODO: Exception if $key === null ?
  79. $baseElement =& $this->resultPointers[$parent][key($first)];
  80. } elseif (isset($this->resultPointers[$parent])) {
  81. $baseElement =& $this->resultPointers[$parent];
  82. } else {
  83. unset($this->resultPointers[$dqlAlias]); // Ticket #1228
  84. continue;
  85. }
  86. $relationAlias = $this->rsm->relationMap[$dqlAlias];
  87. $parentClass = $this->metadataCache[$this->rsm->aliasMap[$parent]];
  88. $relation = $parentClass->getProperty($relationAlias);
  89. // Check the type of the relation (many or single-valued)
  90. if (! $relation instanceof ToOneAssociationMetadata) {
  91. $oneToOne = false;
  92. if (! isset($baseElement[$relationAlias])) {
  93. $baseElement[$relationAlias] = [];
  94. }
  95. if (isset($nonemptyComponents[$dqlAlias])) {
  96. $indexExists = isset($this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
  97. $index = $indexExists ? $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
  98. $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
  99. if (! $indexExists || ! $indexIsValid) {
  100. $element = $data;
  101. if (isset($this->rsm->indexByMap[$dqlAlias])) {
  102. $baseElement[$relationAlias][$row[$this->rsm->indexByMap[$dqlAlias]]] = $element;
  103. } else {
  104. $baseElement[$relationAlias][] = $element;
  105. }
  106. end($baseElement[$relationAlias]);
  107. $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
  108. }
  109. }
  110. } else {
  111. $oneToOne = true;
  112. if (! isset($nonemptyComponents[$dqlAlias]) &&
  113. ( ! isset($baseElement[$relationAlias]))
  114. ) {
  115. $baseElement[$relationAlias] = null;
  116. } elseif (! isset($baseElement[$relationAlias])) {
  117. $baseElement[$relationAlias] = $data;
  118. }
  119. }
  120. $coll =& $baseElement[$relationAlias];
  121. if (is_array($coll)) {
  122. $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
  123. }
  124. } else {
  125. // It's a root result element
  126. $this->rootAliases[$dqlAlias] = true; // Mark as root
  127. $entityKey = $this->rsm->entityMappings[$dqlAlias] ?: 0;
  128. // if this row has a NULL value for the root result id then make it a null result.
  129. if (! isset($nonemptyComponents[$dqlAlias])) {
  130. $result[] = $this->rsm->isMixed
  131. ? [$entityKey => null]
  132. : null;
  133. $resultKey = $this->resultCounter;
  134. ++$this->resultCounter;
  135. continue;
  136. }
  137. // Check for an existing element
  138. if ($this->isSimpleQuery || ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
  139. $element = $this->rsm->isMixed
  140. ? [$entityKey => $data]
  141. : $data;
  142. if (isset($this->rsm->indexByMap[$dqlAlias])) {
  143. $resultKey = $row[$this->rsm->indexByMap[$dqlAlias]];
  144. $result[$resultKey] = $element;
  145. } else {
  146. $resultKey = $this->resultCounter;
  147. $result[] = $element;
  148. ++$this->resultCounter;
  149. }
  150. $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
  151. } else {
  152. $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
  153. $resultKey = $index;
  154. }
  155. $this->updateResultPointer($result, $index, $dqlAlias, false);
  156. }
  157. }
  158. if (! isset($resultKey)) {
  159. $this->resultCounter++;
  160. }
  161. // Append scalar values to mixed result sets
  162. if (isset($rowData['scalars'])) {
  163. if (! isset($resultKey)) {
  164. // this only ever happens when no object is fetched (scalar result only)
  165. $resultKey = isset($this->rsm->indexByMap['scalars'])
  166. ? $row[$this->rsm->indexByMap['scalars']]
  167. : $this->resultCounter - 1;
  168. }
  169. foreach ($rowData['scalars'] as $name => $value) {
  170. $result[$resultKey][$name] = $value;
  171. }
  172. }
  173. // Append new object to mixed result sets
  174. if (isset($rowData['newObjects'])) {
  175. if (! isset($resultKey)) {
  176. $resultKey = $this->resultCounter - 1;
  177. }
  178. $scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0);
  179. $onlyOneRootAlias = $scalarCount === 0 && count($rowData['newObjects']) === 1;
  180. foreach ($rowData['newObjects'] as $objIndex => $newObject) {
  181. $class = $newObject['class'];
  182. $args = $newObject['args'];
  183. $obj = $class->newInstanceArgs($args);
  184. if ($onlyOneRootAlias || count($args) === $scalarCount) {
  185. $result[$resultKey] = $obj;
  186. continue;
  187. }
  188. $result[$resultKey][$objIndex] = $obj;
  189. }
  190. }
  191. }
  192. /**
  193. * Updates the result pointer for an Entity. The result pointers point to the
  194. * last seen instance of each Entity type. This is used for graph construction.
  195. *
  196. * @param mixed[] $coll The element.
  197. * @param bool|int $index Index of the element in the collection.
  198. * @param string $dqlAlias
  199. * @param bool $oneToOne Whether it is a single-valued association or not.
  200. */
  201. private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
  202. {
  203. if ($coll === null) {
  204. unset($this->resultPointers[$dqlAlias]); // Ticket #1228
  205. return;
  206. }
  207. if ($oneToOne) {
  208. $this->resultPointers[$dqlAlias] =& $coll;
  209. return;
  210. }
  211. if ($index !== false) {
  212. $this->resultPointers[$dqlAlias] =& $coll[$index];
  213. return;
  214. }
  215. if (! $coll) {
  216. return;
  217. }
  218. end($coll);
  219. $this->resultPointers[$dqlAlias] =& $coll[key($coll)];
  220. }
  221. }