ArrayHydrator.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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 LGPL. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\ORM\Internal\Hydration;
  20. use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
  21. /**
  22. * The ArrayHydrator produces a nested array "graph" that is often (not always)
  23. * interchangeable with the corresponding object graph for read-only access.
  24. *
  25. * @since 2.0
  26. * @author Roman Borschel <roman@code-factory.org>
  27. * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
  28. */
  29. class ArrayHydrator extends AbstractHydrator
  30. {
  31. private $_ce = array();
  32. private $_rootAliases = array();
  33. private $_isSimpleQuery = false;
  34. private $_identifierMap = array();
  35. private $_resultPointers = array();
  36. private $_idTemplate = array();
  37. private $_resultCounter = 0;
  38. /**
  39. * {@inheritdoc}
  40. */
  41. protected function prepare()
  42. {
  43. $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
  44. $this->_identifierMap = array();
  45. $this->_resultPointers = array();
  46. $this->_idTemplate = array();
  47. $this->_resultCounter = 0;
  48. foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
  49. $this->_identifierMap[$dqlAlias] = array();
  50. $this->_resultPointers[$dqlAlias] = array();
  51. $this->_idTemplate[$dqlAlias] = '';
  52. }
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. protected function hydrateAllData()
  58. {
  59. $result = array();
  60. $cache = array();
  61. while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
  62. $this->hydrateRowData($data, $cache, $result);
  63. }
  64. return $result;
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. protected function hydrateRowData(array $row, array &$cache, array &$result)
  70. {
  71. // 1) Initialize
  72. $id = $this->_idTemplate; // initialize the id-memory
  73. $nonemptyComponents = array();
  74. $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents);
  75. // Extract scalar values. They're appended at the end.
  76. if (isset($rowData['scalars'])) {
  77. $scalars = $rowData['scalars'];
  78. unset($rowData['scalars']);
  79. if (empty($rowData)) {
  80. ++$this->_resultCounter;
  81. }
  82. }
  83. // 2) Now hydrate the data found in the current row.
  84. foreach ($rowData as $dqlAlias => $data) {
  85. $index = false;
  86. if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
  87. // It's a joined result
  88. $parent = $this->_rsm->parentAliasMap[$dqlAlias];
  89. $path = $parent . '.' . $dqlAlias;
  90. // missing parent data, skipping as RIGHT JOIN hydration is not supported.
  91. if ( ! isset($nonemptyComponents[$parent]) ) {
  92. continue;
  93. }
  94. // Get a reference to the right element in the result tree.
  95. // This element will get the associated element attached.
  96. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
  97. $first = reset($this->_resultPointers);
  98. // TODO: Exception if $key === null ?
  99. $baseElement =& $this->_resultPointers[$parent][key($first)];
  100. } else if (isset($this->_resultPointers[$parent])) {
  101. $baseElement =& $this->_resultPointers[$parent];
  102. } else {
  103. unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
  104. continue;
  105. }
  106. $relationAlias = $this->_rsm->relationMap[$dqlAlias];
  107. $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
  108. // Check the type of the relation (many or single-valued)
  109. if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
  110. $oneToOne = false;
  111. if (isset($nonemptyComponents[$dqlAlias])) {
  112. if ( ! isset($baseElement[$relationAlias])) {
  113. $baseElement[$relationAlias] = array();
  114. }
  115. $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
  116. $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
  117. $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
  118. if ( ! $indexExists || ! $indexIsValid) {
  119. $element = $data;
  120. if (isset($this->_rsm->indexByMap[$dqlAlias])) {
  121. $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element;
  122. } else {
  123. $baseElement[$relationAlias][] = $element;
  124. }
  125. end($baseElement[$relationAlias]);
  126. $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
  127. }
  128. } else if ( ! isset($baseElement[$relationAlias])) {
  129. $baseElement[$relationAlias] = array();
  130. }
  131. } else {
  132. $oneToOne = true;
  133. if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
  134. $baseElement[$relationAlias] = null;
  135. } else if ( ! isset($baseElement[$relationAlias])) {
  136. $baseElement[$relationAlias] = $data;
  137. }
  138. }
  139. $coll =& $baseElement[$relationAlias];
  140. if ($coll !== null) {
  141. $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
  142. }
  143. } else {
  144. // It's a root result element
  145. $this->_rootAliases[$dqlAlias] = true; // Mark as root
  146. $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
  147. // if this row has a NULL value for the root result id then make it a null result.
  148. if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
  149. if ($this->_rsm->isMixed) {
  150. $result[] = array($entityKey => null);
  151. } else {
  152. $result[] = null;
  153. }
  154. $resultKey = $this->_resultCounter;
  155. ++$this->_resultCounter;
  156. continue;
  157. }
  158. // Check for an existing element
  159. if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
  160. $element = $rowData[$dqlAlias];
  161. if ($this->_rsm->isMixed) {
  162. $element = array($entityKey => $element);
  163. }
  164. if (isset($this->_rsm->indexByMap[$dqlAlias])) {
  165. $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
  166. $result[$resultKey] = $element;
  167. } else {
  168. $resultKey = $this->_resultCounter;
  169. $result[] = $element;
  170. ++$this->_resultCounter;
  171. }
  172. $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
  173. } else {
  174. $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
  175. $resultKey = $index;
  176. /*if ($this->_rsm->isMixed) {
  177. $result[] =& $result[$index];
  178. ++$this->_resultCounter;
  179. }*/
  180. }
  181. $this->updateResultPointer($result, $index, $dqlAlias, false);
  182. }
  183. }
  184. // Append scalar values to mixed result sets
  185. if (isset($scalars)) {
  186. if ( ! isset($resultKey) ) {
  187. // this only ever happens when no object is fetched (scalar result only)
  188. if (isset($this->_rsm->indexByMap['scalars'])) {
  189. $resultKey = $row[$this->_rsm->indexByMap['scalars']];
  190. } else {
  191. $resultKey = $this->_resultCounter - 1;
  192. }
  193. }
  194. foreach ($scalars as $name => $value) {
  195. $result[$resultKey][$name] = $value;
  196. }
  197. }
  198. }
  199. /**
  200. * Updates the result pointer for an Entity. The result pointers point to the
  201. * last seen instance of each Entity type. This is used for graph construction.
  202. *
  203. * @param array $coll The element.
  204. * @param boolean|integer $index Index of the element in the collection.
  205. * @param string $dqlAlias
  206. * @param boolean $oneToOne Whether it is a single-valued association or not.
  207. */
  208. private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
  209. {
  210. if ($coll === null) {
  211. unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
  212. return;
  213. }
  214. if ($index !== false) {
  215. $this->_resultPointers[$dqlAlias] =& $coll[$index];
  216. return;
  217. }
  218. if ( ! $coll) {
  219. return;
  220. }
  221. if ($oneToOne) {
  222. $this->_resultPointers[$dqlAlias] =& $coll;
  223. return;
  224. }
  225. end($coll);
  226. $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
  227. return;
  228. }
  229. /**
  230. * Retrieve ClassMetadata associated to entity class name.
  231. *
  232. * @param string $className
  233. *
  234. * @return \Doctrine\ORM\Mapping\ClassMetadata
  235. */
  236. private function getClassMetadata($className)
  237. {
  238. if ( ! isset($this->_ce[$className])) {
  239. $this->_ce[$className] = $this->_em->getClassMetadata($className);
  240. }
  241. return $this->_ce[$className];
  242. }
  243. }