Passed
Push — master ( ad5014...e80cd7 )
by Marco
15:42
created

ObjectHydrator::getEntityFromIdentityMap()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0144

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 11
cts 12
cp 0.9167
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 12
nc 5
nop 2
crap 5.0144
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
20
namespace Doctrine\ORM\Internal\Hydration;
21
22
use Doctrine\ORM\UnitOfWork;
23
use PDO;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\PersistentCollection;
26
use Doctrine\ORM\Query;
27
use Doctrine\Common\Collections\ArrayCollection;
28
use Doctrine\ORM\Proxy\Proxy;
29
30
/**
31
 * The ObjectHydrator constructs an object graph out of an SQL result set.
32
 *
33
 * Internal note: Highly performance-sensitive code.
34
 *
35
 * @since  2.0
36
 * @author Roman Borschel <[email protected]>
37
 * @author Guilherme Blanco <[email protected]>
38
 * @author Fabio B. Silva <[email protected]>
39
 */
40
class ObjectHydrator extends AbstractHydrator
41
{
42
    /**
43
     * @var array
44
     */
45
    private $identifierMap = [];
46
47
    /**
48
     * @var array
49
     */
50
    private $resultPointers = [];
51
52
    /**
53
     * @var array
54
     */
55
    private $idTemplate = [];
56
57
    /**
58
     * @var integer
59
     */
60
    private $resultCounter = 0;
61
62
    /**
63
     * @var array
64
     */
65
    private $rootAliases = [];
66
67
    /**
68
     * @var array
69
     */
70
    private $initializedCollections = [];
71
72
    /**
73
     * @var array
74
     */
75
    private $existingCollections = [];
76
77
    /**
78
     * {@inheritdoc}
79
     */
80 673
    protected function prepare()
81
    {
82 673
        if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
83 573
            $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true;
84
        }
85
86 673
        foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
87 639
            $this->identifierMap[$dqlAlias] = [];
88 639
            $this->idTemplate[$dqlAlias]    = '';
89
90
            // Remember which associations are "fetch joined", so that we know where to inject
91
            // collection stubs or proxies and where not.
92 639
            if ( ! isset($this->_rsm->relationMap[$dqlAlias])) {
93 639
                continue;
94
            }
95
96 326
            $parent = $this->_rsm->parentAliasMap[$dqlAlias];
97
98 326
            if ( ! isset($this->_rsm->aliasMap[$parent])) {
99 1
                throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent);
100
            }
101
102 325
            $sourceClassName = $this->_rsm->aliasMap[$parent];
103 325
            $sourceClass     = $this->getClassMetadata($sourceClassName);
104 325
            $assoc           = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
105
106 325
            $this->_hints['fetched'][$parent][$assoc['fieldName']] = true;
107
108 325
            if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
109 35
                continue;
110
            }
111
112
            // Mark any non-collection opposite sides as fetched, too.
113 304
            if ($assoc['mappedBy']) {
114 256
                $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true;
115
116 256
                continue;
117
            }
118
119
            // handle fetch-joined owning side bi-directional one-to-one associations
120 66
            if ($assoc['inversedBy']) {
121 46
                $class        = $this->getClassMetadata($className);
122 46
                $inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
123
124 46
                if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
125 28
                    continue;
126
                }
127
128 18
                $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true;
129
            }
130
        }
131 672
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 667
    protected function cleanup()
137
    {
138 667
        $eagerLoad = (isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] == true;
139
140 667
        parent::cleanup();
141
142 667
        $this->identifierMap =
143 667
        $this->initializedCollections =
144 667
        $this->existingCollections =
145 667
        $this->resultPointers = [];
146
147 667
        if ($eagerLoad) {
148 667
            $this->_uow->triggerEagerLoads();
149
        }
150
151 667
        $this->_uow->hydrationComplete();
152 667
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157 666
    protected function hydrateAllData()
158
    {
159 666
        $result = [];
160
161 666
        while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
162 628
            $this->hydrateRowData($row, $result);
163
        }
164
165
        // Take snapshots from all newly initialized collections
166 663
        foreach ($this->initializedCollections as $coll) {
167 69
            $coll->takeSnapshot();
168
        }
169
170 663
        return $result;
171
    }
172
173
    /**
174
     * Initializes a related collection.
175
     *
176
     * @param object        $entity         The entity to which the collection belongs.
177
     * @param ClassMetadata $class
178
     * @param string        $fieldName      The name of the field on the entity that holds the collection.
179
     * @param string        $parentDqlAlias Alias of the parent fetch joining this collection.
180
     *
181
     * @return \Doctrine\ORM\PersistentCollection
182
     */
183 101
    private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
184
    {
185 101
        $oid      = spl_object_hash($entity);
186 101
        $relation = $class->associationMappings[$fieldName];
187 101
        $value    = $class->reflFields[$fieldName]->getValue($entity);
188
189 101
        if ($value === null || is_array($value)) {
190 65
            $value = new ArrayCollection((array) $value);
191
        }
192
193 101
        if ( ! $value instanceof PersistentCollection) {
194 65
            $value = new PersistentCollection(
195 65
                $this->_em, $this->_metadataCache[$relation['targetEntity']], $value
196
            );
197 65
            $value->setOwner($entity, $relation);
198
199 65
            $class->reflFields[$fieldName]->setValue($entity, $value);
200 65
            $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
201
202 65
            $this->initializedCollections[$oid . $fieldName] = $value;
203
        } else if (
204 39
            isset($this->_hints[Query::HINT_REFRESH]) ||
205 39
            isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
206 39
             ! $value->isInitialized()
207
        ) {
208
            // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
209 6
            $value->setDirty(false);
210 6
            $value->setInitialized(true);
211 6
            $value->unwrap()->clear();
212
213 6
            $this->initializedCollections[$oid . $fieldName] = $value;
214
        } else {
215
            // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
216 34
            $this->existingCollections[$oid . $fieldName] = $value;
217
        }
218
219 101
        return $value;
220
    }
221
222
    /**
223
     * Gets an entity instance.
224
     *
225
     * @param array  $data     The instance data.
226
     * @param string $dqlAlias The DQL alias of the entity's class.
227
     *
228
     * @return object The entity.
229
     *
230
     * @throws HydrationException
231
     */
232 598
    private function getEntity(array $data, $dqlAlias)
233
    {
234 598
        $className = $this->_rsm->aliasMap[$dqlAlias];
235
236 598
        if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
237 62
            $fieldName = $this->_rsm->discriminatorColumns[$dqlAlias];
238
239 62
            if ( ! isset($this->_rsm->metaMappings[$fieldName])) {
240 1
                throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $fieldName, $dqlAlias);
241
            }
242
243 61
            $discrColumn = $this->_rsm->metaMappings[$fieldName];
244
245 61
            if ( ! isset($data[$discrColumn])) {
246 1
                throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias);
247
            }
248
249 61
            if ($data[$discrColumn] === "") {
250
                throw HydrationException::emptyDiscriminatorValue($dqlAlias);
251
            }
252
253 61
            $discrMap = $this->_metadataCache[$className]->discriminatorMap;
254 61
            $discriminatorValue = (string) $data[$discrColumn];
255
256 61
            if ( ! isset($discrMap[$discriminatorValue])) {
257 1
                throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap));
258
            }
259
260 60
            $className = $discrMap[$discriminatorValue];
261
262 60
            unset($data[$discrColumn]);
263
        }
264
265 596
        if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->rootAliases[$dqlAlias])) {
266 24
            $this->registerManaged($this->_metadataCache[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
267
        }
268
269 596
        $this->_hints['fetchAlias'] = $dqlAlias;
270
271 596
        return $this->_uow->createEntity($className, $data, $this->_hints);
272
    }
273
274
    /**
275
     * @param string $className
276
     * @param array  $data
277
     *
278
     * @return mixed
279
     */
280 34
    private function getEntityFromIdentityMap($className, array $data)
281
    {
282
        // TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
283 34
        $class = $this->_metadataCache[$className];
284
285
        /* @var $class ClassMetadata */
286 34
        if ($class->isIdentifierComposite) {
287 1
            $idHash = '';
288
289 1
            foreach ($class->identifier as $fieldName) {
290 1
                $idHash .= ' ' . (isset($class->associationMappings[$fieldName])
291 1
                    ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
292 1
                    : $data[$fieldName]);
293
            }
294
295 1
            return $this->_uow->tryGetByIdHash(ltrim($idHash), $class->rootEntityName);
296 33
        } else if (isset($class->associationMappings[$class->identifier[0]])) {
297
            return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName);
298
        }
299
300 33
        return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName);
301
    }
302
303
    /**
304
     * Hydrates a single row in an SQL result set.
305
     *
306
     * @internal
307
     * First, the data of the row is split into chunks where each chunk contains data
308
     * that belongs to a particular component/class. Afterwards, all these chunks
309
     * are processed, one after the other. For each chunk of class data only one of the
310
     * following code paths is executed:
311
     *
312
     * Path A: The data chunk belongs to a joined/associated object and the association
313
     *         is collection-valued.
314
     * Path B: The data chunk belongs to a joined/associated object and the association
315
     *         is single-valued.
316
     * Path C: The data chunk belongs to a root result element/object that appears in the topmost
317
     *         level of the hydrated result. A typical example are the objects of the type
318
     *         specified by the FROM clause in a DQL query.
319
     *
320
     * @param array $row    The data of the row to process.
321
     * @param array $result The result array to fill.
322
     *
323
     * @return void
324
     */
325 633
    protected function hydrateRowData(array $row, array &$result)
326
    {
327
        // Initialize
328 633
        $id = $this->idTemplate; // initialize the id-memory
329 633
        $nonemptyComponents = [];
330
        // Split the row data into chunks of class data.
331 633
        $rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
332
333
        // reset result pointers for each data row
334 633
        $this->resultPointers = [];
335
336
        // Hydrate the data chunks
337 633
        foreach ($rowData['data'] as $dqlAlias => $data) {
338 598
            $entityName = $this->_rsm->aliasMap[$dqlAlias];
339
340 598
            if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
341
                // It's a joined result
342
343 317
                $parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
344
                // we need the $path to save into the identifier map which entities were already
345
                // seen for this parent-child relationship
346 317
                $path = $parentAlias . '.' . $dqlAlias;
347
348
                // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
349 317
                if ( ! isset($nonemptyComponents[$parentAlias])) {
350
                    // TODO: Add special case code where we hydrate the right join objects into identity map at least
351 2
                    continue;
352
                }
353
354 317
                $parentClass    = $this->_metadataCache[$this->_rsm->aliasMap[$parentAlias]];
355 317
                $relationField  = $this->_rsm->relationMap[$dqlAlias];
356 317
                $relation       = $parentClass->associationMappings[$relationField];
357 317
                $reflField      = $parentClass->reflFields[$relationField];
358
359
                // Get a reference to the parent object to which the joined element belongs.
360 317
                if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) {
361 18
                    $objectClass = $this->resultPointers[$parentAlias];
362 18
                    $parentObject = $objectClass[key($objectClass)];
363 301
                } else if (isset($this->resultPointers[$parentAlias])) {
364 301
                    $parentObject = $this->resultPointers[$parentAlias];
365
                } else {
366
                    // Parent object of relation not found, mark as not-fetched again
367 2
                    $element = $this->getEntity($data, $dqlAlias);
368
369
                    // Update result pointer and provide initial fetch data for parent
370 2
                    $this->resultPointers[$dqlAlias] = $element;
371 2
                    $rowData['data'][$parentAlias][$relationField] = $element;
372
373
                    // Mark as not-fetched again
374 2
                    unset($this->_hints['fetched'][$parentAlias][$relationField]);
375 2
                    continue;
376
                }
377
378 317
                $oid = spl_object_hash($parentObject);
379
380
                // Check the type of the relation (many or single-valued)
381 317
                if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
382
                    // PATH A: Collection-valued association
383 102
                    $reflFieldValue = $reflField->getValue($parentObject);
384
385 102
                    if (isset($nonemptyComponents[$dqlAlias])) {
386 98
                        $collKey = $oid . $relationField;
387 98
                        if (isset($this->initializedCollections[$collKey])) {
388 45
                            $reflFieldValue = $this->initializedCollections[$collKey];
389 98
                        } else if ( ! isset($this->existingCollections[$collKey])) {
390 98
                            $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
391
                        }
392
393 98
                        $indexExists    = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
394 98
                        $index          = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
395 98
                        $indexIsValid   = $index !== false ? isset($reflFieldValue[$index]) : false;
396
397 98
                        if ( ! $indexExists || ! $indexIsValid) {
398 98
                            if (isset($this->existingCollections[$collKey])) {
399
                                // Collection exists, only look for the element in the identity map.
400 34
                                if ($element = $this->getEntityFromIdentityMap($entityName, $data)) {
401 34
                                    $this->resultPointers[$dqlAlias] = $element;
402
                                } else {
403 2
                                    unset($this->resultPointers[$dqlAlias]);
404
                                }
405
                            } else {
406 66
                                $element = $this->getEntity($data, $dqlAlias);
407
408 66
                                if (isset($this->_rsm->indexByMap[$dqlAlias])) {
409 11
                                    $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]];
410 11
                                    $reflFieldValue->hydrateSet($indexValue, $element);
411 11
                                    $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
412
                                } else {
413 55
                                    $reflFieldValue->hydrateAdd($element);
414 55
                                    $reflFieldValue->last();
415 55
                                    $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
416
                                }
417
                                // Update result pointer
418 66
                                $this->resultPointers[$dqlAlias] = $element;
419
                            }
420
                        } else {
421
                            // Update result pointer
422 7
                            $this->resultPointers[$dqlAlias] = $reflFieldValue[$index];
423
                        }
424 9
                    } else if ( ! $reflFieldValue) {
425 5
                        $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
0 ignored issues
show
Unused Code introduced by
$reflFieldValue is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
426 6
                    } else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
427 1
                        $reflFieldValue->setInitialized(true);
428
                    }
429
430
                } else {
431
                    // PATH B: Single-valued association
432 236
                    $reflFieldValue = $reflField->getValue($parentObject);
433
434 236
                    if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ORM\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
435
                        // we only need to take action if this value is null,
436
                        // we refresh the entity or its an uninitialized proxy.
437 221
                        if (isset($nonemptyComponents[$dqlAlias])) {
438 116
                            $element = $this->getEntity($data, $dqlAlias);
439 115
                            $reflField->setValue($parentObject, $element);
440 115
                            $this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
441 115
                            $targetClass = $this->_metadataCache[$relation['targetEntity']];
442
443 115
                            if ($relation['isOwningSide']) {
444
                                // TODO: Just check hints['fetched'] here?
445
                                // If there is an inverse mapping on the target class its bidirectional
446 51
                                if ($relation['inversedBy']) {
447 35
                                    $inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']];
448 35
                                    if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
449 12
                                        $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
450 12
                                        $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject);
451
                                    }
452 17
                                } else if ($parentClass === $targetClass && $relation['mappedBy']) {
453
                                    // Special case: bi-directional self-referencing one-one on the same class
454
                                    $targetClass->reflFields[$relationField]->setValue($element, $parentObject);
455
                                }
456
                            } else {
457
                                // For sure bidirectional, as there is no inverse side in unidirectional mappings
458 67
                                $targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject);
459 67
                                $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject);
460
                            }
461
                            // Update result pointer
462 115
                            $this->resultPointers[$dqlAlias] = $element;
463
                        } else {
464 125
                            $this->_uow->setOriginalEntityProperty($oid, $relationField, null);
465 125
                            $reflField->setValue($parentObject, null);
466
                        }
467
                        // else leave $reflFieldValue null for single-valued associations
468
                    } else {
469
                        // Update result pointer
470 23
                        $this->resultPointers[$dqlAlias] = $reflFieldValue;
471
                    }
472
                }
473
            } else {
474
                // PATH C: Its a root result element
475 598
                $this->rootAliases[$dqlAlias] = true; // Mark as root alias
476 598
                $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
477
478
                // if this row has a NULL value for the root result id then make it a null result.
479 598
                if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
480 3
                    if ($this->_rsm->isMixed) {
481 2
                        $result[] = [$entityKey => null];
482
                    } else {
483 1
                        $result[] = null;
484
                    }
485 3
                    $resultKey = $this->resultCounter;
486 3
                    ++$this->resultCounter;
487 3
                    continue;
488
                }
489
490
                // check for existing result from the iterations before
491 598
                if ( ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
492 598
                    $element = $this->getEntity($data, $dqlAlias);
493
494 596
                    if ($this->_rsm->isMixed) {
495 41
                        $element = [$entityKey => $element];
496
                    }
497
498 596
                    if (isset($this->_rsm->indexByMap[$dqlAlias])) {
499 26
                        $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
500
501 26
                        if (isset($this->_hints['collection'])) {
502 10
                            $this->_hints['collection']->hydrateSet($resultKey, $element);
503
                        }
504
505 26
                        $result[$resultKey] = $element;
506
                    } else {
507 579
                        $resultKey = $this->resultCounter;
508 579
                        ++$this->resultCounter;
509
510 579
                        if (isset($this->_hints['collection'])) {
511 110
                            $this->_hints['collection']->hydrateAdd($element);
512
                        }
513
514 579
                        $result[] = $element;
515
                    }
516
517 596
                    $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
518
519
                    // Update result pointer
520 596
                    $this->resultPointers[$dqlAlias] = $element;
521
522
                } else {
523
                    // Update result pointer
524 76
                    $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
525 76
                    $this->resultPointers[$dqlAlias] = $result[$index];
526 76
                    $resultKey = $index;
527
                }
528
            }
529
530 596
            if (isset($this->_hints[Query::HINT_INTERNAL_ITERATION]) && $this->_hints[Query::HINT_INTERNAL_ITERATION]) {
531 4
                $this->_uow->hydrationComplete();
532
            }
533
        }
534
535 630
        if ( ! isset($resultKey) ) {
536 35
            $this->resultCounter++;
537
        }
538
539
        // Append scalar values to mixed result sets
540 630
        if (isset($rowData['scalars'])) {
541 54
            if ( ! isset($resultKey) ) {
542 22
                $resultKey = (isset($this->_rsm->indexByMap['scalars']))
543 2
                    ? $row[$this->_rsm->indexByMap['scalars']]
544 22
                    : $this->resultCounter - 1;
545
            }
546
547 54
            foreach ($rowData['scalars'] as $name => $value) {
548 54
                $result[$resultKey][$name] = $value;
549
            }
550
        }
551
552
        // Append new object to mixed result sets
553 630
        if (isset($rowData['newObjects'])) {
554 19
            if ( ! isset($resultKey) ) {
555 13
                $resultKey = $this->resultCounter - 1;
556
            }
557
558
559 19
            $scalarCount = (isset($rowData['scalars'])? count($rowData['scalars']): 0);
560
561 19
            foreach ($rowData['newObjects'] as $objIndex => $newObject) {
562 19
                $class  = $newObject['class'];
563 19
                $args   = $newObject['args'];
564 19
                $obj    = $class->newInstanceArgs($args);
565
566 19
                if ($scalarCount == 0 && count($rowData['newObjects']) == 1 ) {
567 10
                    $result[$resultKey] = $obj;
568
569 10
                    continue;
570
                }
571
572 9
                $result[$resultKey][$objIndex] = $obj;
573
            }
574
        }
575 630
    }
576
577
    /**
578
     * When executed in a hydrate() loop we may have to clear internal state to
579
     * decrease memory consumption.
580
     *
581
     * @param mixed $eventArgs
582
     *
583
     * @return void
584
     */
585 4
    public function onClear($eventArgs)
586
    {
587 4
        parent::onClear($eventArgs);
588
589 4
        $aliases             = array_keys($this->identifierMap);
590
591 4
        $this->identifierMap = array_fill_keys($aliases, []);
592 4
    }
593
}
594