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.
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>.
20 namespace Doctrine\ORM\Internal\Hydration;
22 use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
25 * The ArrayHydrator produces a nested array "graph" that is often (not always)
26 * interchangeable with the corresponding object graph for read-only access.
29 * @author Roman Borschel <roman@code-factory.org>
30 * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
32 class ArrayHydrator extends AbstractHydrator
34 private $_ce = array();
35 private $_rootAliases = array();
36 private $_isSimpleQuery = false;
37 private $_identifierMap = array();
38 private $_resultPointers = array();
39 private $_idTemplate = array();
40 private $_resultCounter = 0;
45 protected function prepare()
47 $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
48 $this->_identifierMap = array();
49 $this->_resultPointers = array();
50 $this->_idTemplate = array();
51 $this->_resultCounter = 0;
53 foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
54 $this->_identifierMap[$dqlAlias] = array();
55 $this->_resultPointers[$dqlAlias] = array();
56 $this->_idTemplate[$dqlAlias] = '';
63 protected function hydrateAllData()
68 while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
69 $this->hydrateRowData($data, $cache, $result);
78 protected function hydrateRowData(array $row, array &$cache, array &$result)
81 $id = $this->_idTemplate; // initialize the id-memory
82 $nonemptyComponents = array();
83 $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents);
85 // Extract scalar values. They're appended at the end.
86 if (isset($rowData['scalars'])) {
87 $scalars = $rowData['scalars'];
89 unset($rowData['scalars']);
91 if (empty($rowData)) {
92 ++$this->_resultCounter;
96 // 2) Now hydrate the data found in the current row.
97 foreach ($rowData as $dqlAlias => $data) {
100 if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
101 // It's a joined result
103 $parent = $this->_rsm->parentAliasMap[$dqlAlias];
104 $path = $parent . '.' . $dqlAlias;
106 // missing parent data, skipping as RIGHT JOIN hydration is not supported.
107 if ( ! isset($nonemptyComponents[$parent]) ) {
111 // Get a reference to the right element in the result tree.
112 // This element will get the associated element attached.
113 if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
114 $first = reset($this->_resultPointers);
115 // TODO: Exception if $key === null ?
116 $baseElement =& $this->_resultPointers[$parent][key($first)];
117 } else if (isset($this->_resultPointers[$parent])) {
118 $baseElement =& $this->_resultPointers[$parent];
120 unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
124 $relationAlias = $this->_rsm->relationMap[$dqlAlias];
125 $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
127 // Check the type of the relation (many or single-valued)
128 if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
131 if (isset($nonemptyComponents[$dqlAlias])) {
132 if ( ! isset($baseElement[$relationAlias])) {
133 $baseElement[$relationAlias] = array();
136 $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
137 $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
138 $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
140 if ( ! $indexExists || ! $indexIsValid) {
142 if (isset($this->_rsm->indexByMap[$dqlAlias])) {
143 $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element;
145 $baseElement[$relationAlias][] = $element;
148 end($baseElement[$relationAlias]);
150 $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
152 } else if ( ! isset($baseElement[$relationAlias])) {
153 $baseElement[$relationAlias] = array();
158 if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
159 $baseElement[$relationAlias] = null;
160 } else if ( ! isset($baseElement[$relationAlias])) {
161 $baseElement[$relationAlias] = $data;
165 $coll =& $baseElement[$relationAlias];
167 if ($coll !== null) {
168 $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
172 // It's a root result element
174 $this->_rootAliases[$dqlAlias] = true; // Mark as root
175 $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
177 // if this row has a NULL value for the root result id then make it a null result.
178 if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
179 if ($this->_rsm->isMixed) {
180 $result[] = array($entityKey => null);
184 $resultKey = $this->_resultCounter;
185 ++$this->_resultCounter;
189 // Check for an existing element
190 if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
191 $element = $rowData[$dqlAlias];
192 if ($this->_rsm->isMixed) {
193 $element = array($entityKey => $element);
196 if (isset($this->_rsm->indexByMap[$dqlAlias])) {
197 $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
198 $result[$resultKey] = $element;
200 $resultKey = $this->_resultCounter;
201 $result[] = $element;
202 ++$this->_resultCounter;
205 $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
207 $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
209 /*if ($this->_rsm->isMixed) {
210 $result[] =& $result[$index];
211 ++$this->_resultCounter;
214 $this->updateResultPointer($result, $index, $dqlAlias, false);
218 // Append scalar values to mixed result sets
219 if (isset($scalars)) {
220 if ( ! isset($resultKey) ) {
221 // this only ever happens when no object is fetched (scalar result only)
222 if (isset($this->_rsm->indexByMap['scalars'])) {
223 $resultKey = $row[$this->_rsm->indexByMap['scalars']];
225 $resultKey = $this->_resultCounter - 1;
229 foreach ($scalars as $name => $value) {
230 $result[$resultKey][$name] = $value;
236 * Updates the result pointer for an Entity. The result pointers point to the
237 * last seen instance of each Entity type. This is used for graph construction.
239 * @param array $coll The element.
240 * @param boolean|integer $index Index of the element in the collection.
241 * @param string $dqlAlias
242 * @param boolean $oneToOne Whether it is a single-valued association or not.
244 private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
246 if ($coll === null) {
247 unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
252 if ($index !== false) {
253 $this->_resultPointers[$dqlAlias] =& $coll[$index];
263 $this->_resultPointers[$dqlAlias] =& $coll;
269 $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
275 * Retrieve ClassMetadata associated to entity class name.
277 * @param string $className
279 * @return \Doctrine\ORM\Mapping\ClassMetadata
281 private function getClassMetadata($className)
283 if ( ! isset($this->_ce[$className])) {
284 $this->_ce[$className] = $this->_em->getClassMetadata($className);
287 return $this->_ce[$className];