Passed
Pull Request — 2.7 (#7950)
by Mathieu
07:48
created

ObjectHydrator   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 560
Duplicated Lines 0 %

Test Coverage

Coverage 99.17%

Importance

Changes 0
Metric Value
wmc 92
eloc 251
c 0
b 0
f 0
dl 0
loc 560
ccs 238
cts 240
cp 0.9917
rs 2

8 Methods

Rating   Name   Duplication   Size   Complexity  
A onClear() 0 7 1
B prepare() 0 49 9
A hydrateAllData() 0 14 3
F hydrateRowData() 0 251 54
B getEntity() 0 40 8
B initRelatedCollection() 0 42 9
A cleanup() 0 16 3
A getEntityFromIdentityMap() 0 21 5

How to fix   Complexity   

Complex Class

Complex classes like ObjectHydrator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ObjectHydrator, and based on these observations, apply Extract Interface, too.

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
use function assert;
30
use function method_exists;
31
use function spl_object_hash;
32
33
/**
34
 * The ObjectHydrator constructs an object graph out of an SQL result set.
35
 *
36
 * Internal note: Highly performance-sensitive code.
37
 *
38
 * @since  2.0
39
 * @author Roman Borschel <[email protected]>
40
 * @author Guilherme Blanco <[email protected]>
41
 * @author Fabio B. Silva <[email protected]>
42
 */
43
class ObjectHydrator extends AbstractHydrator
44
{
45
    /**
46
     * @var array
47
     */
48
    private $identifierMap = [];
49
50
    /**
51
     * @var array
52
     */
53
    private $resultPointers = [];
54
55
    /**
56
     * @var array
57
     */
58
    private $idTemplate = [];
59
60
    /**
61
     * @var integer
62
     */
63
    private $resultCounter = 0;
64
65
    /**
66
     * @var array
67
     */
68
    private $rootAliases = [];
69
70
    /**
71
     * @var array
72
     */
73
    private $initializedCollections = [];
74
75
    /**
76
     * @var array
77
     */
78
    private $existingCollections = [];
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 710
    protected function prepare()
84
    {
85 710
        if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
86 607
            $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true;
87
        }
88
89 710
        foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
90 675
            $this->identifierMap[$dqlAlias] = [];
91 675
            $this->idTemplate[$dqlAlias]    = '';
92
93
            // Remember which associations are "fetch joined", so that we know where to inject
94
            // collection stubs or proxies and where not.
95 675
            if ( ! isset($this->_rsm->relationMap[$dqlAlias])) {
96 675
                continue;
97
            }
98
99 331
            $parent = $this->_rsm->parentAliasMap[$dqlAlias];
100
101 331
            if ( ! isset($this->_rsm->aliasMap[$parent])) {
102 1
                throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent);
103
            }
104
105 330
            $sourceClassName = $this->_rsm->aliasMap[$parent];
106 330
            $sourceClass     = $this->getClassMetadata($sourceClassName);
107 330
            $assoc           = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
108
109 330
            $this->_hints['fetched'][$parent][$assoc['fieldName']] = true;
110
111 330
            if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
112 35
                continue;
113
            }
114
115
            // Mark any non-collection opposite sides as fetched, too.
116 309
            if ($assoc['mappedBy']) {
117 260
                $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true;
118
119 260
                continue;
120
            }
121
122
            // handle fetch-joined owning side bi-directional one-to-one associations
123 68
            if ($assoc['inversedBy']) {
124 47
                $class        = $this->getClassMetadata($className);
125 47
                $inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
126
127 47
                if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
128 28
                    continue;
129
                }
130
131 41
                $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true;
132
            }
133
        }
134 709
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139 704
    protected function cleanup()
140
    {
141 704
        $eagerLoad = (isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] == true;
142
143 704
        parent::cleanup();
144
145 704
        $this->identifierMap =
146 704
        $this->initializedCollections =
147 704
        $this->existingCollections =
148 704
        $this->resultPointers = [];
149
150 704
        if ($eagerLoad) {
151 704
            $this->_uow->triggerEagerLoads();
152
        }
153
154 704
        $this->_uow->hydrationComplete();
155 704
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160 703
    protected function hydrateAllData()
161
    {
162 703
        $result = [];
163
164 703
        while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
165 655
            $this->hydrateRowData($row, $result);
166
        }
167
168
        // Take snapshots from all newly initialized collections
169 700
        foreach ($this->initializedCollections as $coll) {
170 71
            $coll->takeSnapshot();
171
        }
172
173 700
        return $result;
174
    }
175
176
    /**
177
     * Initializes a related collection.
178
     *
179
     * @param object        $entity         The entity to which the collection belongs.
180
     * @param ClassMetadata $class
181
     * @param string        $fieldName      The name of the field on the entity that holds the collection.
182
     * @param string        $parentDqlAlias Alias of the parent fetch joining this collection.
183
     *
184
     * @return \Doctrine\ORM\PersistentCollection
185
     */
186 103
    private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
187
    {
188 103
        $oid       = spl_object_hash($entity);
189 103
        $relation  = $class->getAssociationMapping($fieldName);
190 103
        $reflField = $class->getReflectionProperty($fieldName);
191 103
        $value     = null;
192
193 103
        if (! method_exists($reflField, 'isInitialized') || $reflField->isInitialized($entity)) {
194 103
            $value = $reflField->getValue($entity);
195
        }
196
197 103
        if ($value === null || is_array($value)) {
198 67
            $value = new ArrayCollection((array) $value);
199
        }
200
201 103
        if ( ! $value instanceof PersistentCollection) {
202 67
            $value = new PersistentCollection(
203 67
                $this->_em, $this->_metadataCache[$relation['targetEntity']], $value
204
            );
205 67
            $value->setOwner($entity, $relation);
206
207 67
            $reflField->setValue($entity, $value);
208 67
            $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
209
210 67
            $this->initializedCollections[$oid . $fieldName] = $value;
211
        } else if (
212 40
            isset($this->_hints[Query::HINT_REFRESH]) ||
213 40
            isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
214 40
             ! $value->isInitialized()
215
        ) {
216
            // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
217 6
            $value->setDirty(false);
218 6
            $value->setInitialized(true);
219 6
            $value->unwrap()->clear();
220
221 6
            $this->initializedCollections[$oid . $fieldName] = $value;
222
        } else {
223
            // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
224 35
            $this->existingCollections[$oid . $fieldName] = $value;
225
        }
226
227 103
        return $value;
228
    }
229
230
    /**
231
     * Gets an entity instance.
232
     *
233
     * @param array  $data     The instance data.
234
     * @param string $dqlAlias The DQL alias of the entity's class.
235
     *
236
     * @return object The entity.
237
     *
238
     * @throws HydrationException
239
     */
240 624
    private function getEntity(array $data, $dqlAlias)
241
    {
242 624
        $className = $this->_rsm->aliasMap[$dqlAlias];
243
244 624
        if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
245 70
            $fieldName = $this->_rsm->discriminatorColumns[$dqlAlias];
246
247 70
            if ( ! isset($this->_rsm->metaMappings[$fieldName])) {
248 1
                throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $fieldName, $dqlAlias);
249
            }
250
251 69
            $discrColumn = $this->_rsm->metaMappings[$fieldName];
252
253 69
            if ( ! isset($data[$discrColumn])) {
254 1
                throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias);
255
            }
256
257 69
            if ($data[$discrColumn] === "") {
258
                throw HydrationException::emptyDiscriminatorValue($dqlAlias);
259
            }
260
261 69
            $discrMap = $this->_metadataCache[$className]->discriminatorMap;
262 69
            $discriminatorValue = (string) $data[$discrColumn];
263
264 69
            if ( ! isset($discrMap[$discriminatorValue])) {
265 1
                throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap));
266
            }
267
268 68
            $className = $discrMap[$discriminatorValue];
269
270 68
            unset($data[$discrColumn]);
271
        }
272
273 622
        if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->rootAliases[$dqlAlias])) {
274 25
            $this->registerManaged($this->_metadataCache[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
275
        }
276
277 622
        $this->_hints['fetchAlias'] = $dqlAlias;
278
279 622
        return $this->_uow->createEntity($className, $data, $this->_hints);
280
    }
281
282
    /**
283
     * @param string $className
284
     * @param array  $data
285
     *
286
     * @return mixed
287
     */
288 35
    private function getEntityFromIdentityMap($className, array $data)
289
    {
290
        // TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
291 35
        $class = $this->_metadataCache[$className];
292
293
        /* @var $class ClassMetadata */
294 35
        if ($class->isIdentifierComposite) {
295 1
            $idHash = '';
296
297 1
            foreach ($class->identifier as $fieldName) {
298 1
                $idHash .= ' ' . (isset($class->associationMappings[$fieldName])
299 1
                    ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
300 1
                    : $data[$fieldName]);
301
            }
302
303 1
            return $this->_uow->tryGetByIdHash(ltrim($idHash), $class->rootEntityName);
304 34
        } else if (isset($class->associationMappings[$class->identifier[0]])) {
305
            return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName);
306
        }
307
308 34
        return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName);
309
    }
310
311
    /**
312
     * Hydrates a single row in an SQL result set.
313
     *
314
     * @internal
315
     * First, the data of the row is split into chunks where each chunk contains data
316
     * that belongs to a particular component/class. Afterwards, all these chunks
317
     * are processed, one after the other. For each chunk of class data only one of the
318
     * following code paths is executed:
319
     *
320
     * Path A: The data chunk belongs to a joined/associated object and the association
321
     *         is collection-valued.
322
     * Path B: The data chunk belongs to a joined/associated object and the association
323
     *         is single-valued.
324
     * Path C: The data chunk belongs to a root result element/object that appears in the topmost
325
     *         level of the hydrated result. A typical example are the objects of the type
326
     *         specified by the FROM clause in a DQL query.
327
     *
328
     * @param array $row    The data of the row to process.
329
     * @param array $result The result array to fill.
330
     *
331
     * @return void
332
     */
333 660
    protected function hydrateRowData(array $row, array &$result)
334
    {
335
        // Initialize
336 660
        $id = $this->idTemplate; // initialize the id-memory
337 660
        $nonemptyComponents = [];
338
        // Split the row data into chunks of class data.
339 660
        $rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
340
341
        // reset result pointers for each data row
342 660
        $this->resultPointers = [];
343
344
        // Hydrate the data chunks
345 660
        foreach ($rowData['data'] as $dqlAlias => $data) {
346 624
            $entityName = $this->_rsm->aliasMap[$dqlAlias];
347
348 624
            if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
349
                // It's a joined result
350
351 322
                $parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
352
                // we need the $path to save into the identifier map which entities were already
353
                // seen for this parent-child relationship
354 322
                $path = $parentAlias . '.' . $dqlAlias;
355
356
                // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
357 322
                if ( ! isset($nonemptyComponents[$parentAlias])) {
358
                    // TODO: Add special case code where we hydrate the right join objects into identity map at least
359 2
                    continue;
360
                }
361
362 322
                $parentClass    = $this->_metadataCache[$this->_rsm->aliasMap[$parentAlias]];
363 322
                assert($parentClass instanceof ClassMetadata);
364 322
                $relationField = $this->_rsm->relationMap[$dqlAlias];
365 322
                $relation      = $parentClass->getAssociationMapping($relationField);
366 322
                $reflField     = $parentClass->getReflectionProperty($relationField);
367
368
                // Get a reference to the parent object to which the joined element belongs.
369 322
                if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) {
370 18
                    $objectClass = $this->resultPointers[$parentAlias];
371 18
                    $parentObject = $objectClass[key($objectClass)];
372 306
                } else if (isset($this->resultPointers[$parentAlias])) {
373 306
                    $parentObject = $this->resultPointers[$parentAlias];
374
                } else {
375
                    // Parent object of relation not found, mark as not-fetched again
376 2
                    $element = $this->getEntity($data, $dqlAlias);
377
378
                    // Update result pointer and provide initial fetch data for parent
379 2
                    $this->resultPointers[$dqlAlias] = $element;
380 2
                    $rowData['data'][$parentAlias][$relationField] = $element;
381
382
                    // Mark as not-fetched again
383 2
                    unset($this->_hints['fetched'][$parentAlias][$relationField]);
384 2
                    continue;
385
                }
386
387 322
                $oid            = spl_object_hash($parentObject);
388 322
                $reflFieldValue = null;
389
390 322
                if (! method_exists($reflField, 'isInitialized') || $reflField->isInitialized($parentObject)) {
391 322
                    $reflFieldValue = $reflField->getValue($parentObject);
392
                }
393
394
                // Check the type of the relation (many or single-valued)
395 322
                if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
396
                    // PATH A: Collection-valued association
397 104
                    if (isset($nonemptyComponents[$dqlAlias])) {
398 100
                        $collKey = $oid . $relationField;
399 100
                        if (isset($this->initializedCollections[$collKey])) {
400 47
                            $reflFieldValue = $this->initializedCollections[$collKey];
401 100
                        } else if ( ! isset($this->existingCollections[$collKey])) {
402 100
                            $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
403
                        }
404
405 100
                        $indexExists    = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
406 100
                        $index          = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
407 100
                        $indexIsValid   = $index !== false ? isset($reflFieldValue[$index]) : false;
408
409 100
                        if ( ! $indexExists || ! $indexIsValid) {
410 100
                            if (isset($this->existingCollections[$collKey])) {
411
                                // Collection exists, only look for the element in the identity map.
412 35
                                if ($element = $this->getEntityFromIdentityMap($entityName, $data)) {
413 35
                                    $this->resultPointers[$dqlAlias] = $element;
414
                                } else {
415 35
                                    unset($this->resultPointers[$dqlAlias]);
416
                                }
417
                            } else {
418 68
                                $element = $this->getEntity($data, $dqlAlias);
419
420 68
                                if (isset($this->_rsm->indexByMap[$dqlAlias])) {
421 12
                                    $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]];
422 12
                                    $reflFieldValue->hydrateSet($indexValue, $element);
423 12
                                    $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
424
                                } else {
425 56
                                    $reflFieldValue->hydrateAdd($element);
426 56
                                    $reflFieldValue->last();
427 56
                                    $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
428
                                }
429
                                // Update result pointer
430 100
                                $this->resultPointers[$dqlAlias] = $element;
431
                            }
432
                        } else {
433
                            // Update result pointer
434 100
                            $this->resultPointers[$dqlAlias] = $reflFieldValue[$index];
435
                        }
436 9
                    } else if ( ! $reflFieldValue) {
437 5
                        $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
438 6
                    } else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
439 104
                        $reflFieldValue->setInitialized(true);
440
                    }
441
442
                } else {
443
                    // PATH B: Single-valued association
444 240
                    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?
Loading history...
445
                        // we only need to take action if this value is null,
446
                        // we refresh the entity or its an uninitialized proxy.
447 225
                        if (isset($nonemptyComponents[$dqlAlias])) {
448 120
                            $element = $this->getEntity($data, $dqlAlias);
449 119
                            $reflField->setValue($parentObject, $element);
450 119
                            $this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
451 119
                            $targetClass = $this->_metadataCache[$relation['targetEntity']];
452 119
                            assert($targetClass instanceof ClassMetadata);
453
454 119
                            if ($relation['isOwningSide']) {
455
                                // TODO: Just check hints['fetched'] here?
456
                                // If there is an inverse mapping on the target class its bidirectional
457 53
                                if ($relation['inversedBy']) {
458 36
                                    $inverseAssoc = $targetClass->getAssociationMapping($relation['inversedBy']);
459 36
                                    if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
460 13
                                        $targetClass->getReflectionProperty($inverseAssoc['fieldName'])->setValue($element, $parentObject);
461 36
                                        $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject);
462
                                    }
463 18
                                } else if ($parentClass === $targetClass && $relation['mappedBy']) {
464
                                    // Special case: bi-directional self-referencing one-one on the same class
465 53
                                    $targetClass->getReflectionProperty($relationField)->setValue($element, $parentObject);
466
                                }
467
                            } else {
468
                                // For sure bidirectional, as there is no inverse side in unidirectional mappings
469 69
                                $targetClass->getReflectionProperty($relation['mappedBy'])->setValue($element, $parentObject);
470 69
                                $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject);
471
                            }
472
                            // Update result pointer
473 119
                            $this->resultPointers[$dqlAlias] = $element;
474
                        } else {
475 125
                            $this->_uow->setOriginalEntityProperty($oid, $relationField, null);
476 224
                            $reflField->setValue($parentObject, null);
477
                        }
478
                        // else leave $reflFieldValue null for single-valued associations
479
                    } else {
480
                        // Update result pointer
481 321
                        $this->resultPointers[$dqlAlias] = $reflFieldValue;
482
                    }
483
                }
484
            } else {
485
                // PATH C: Its a root result element
486 624
                $this->rootAliases[$dqlAlias] = true; // Mark as root alias
487 624
                $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
488
489
                // if this row has a NULL value for the root result id then make it a null result.
490 624
                if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
491 3
                    if ($this->_rsm->isMixed) {
492 2
                        $result[] = [$entityKey => null];
493
                    } else {
494 1
                        $result[] = null;
495
                    }
496 3
                    $resultKey = $this->resultCounter;
497 3
                    ++$this->resultCounter;
498 3
                    continue;
499
                }
500
501
                // check for existing result from the iterations before
502 624
                if ( ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
503 624
                    $element = $this->getEntity($data, $dqlAlias);
504
505 622
                    if ($this->_rsm->isMixed) {
506 41
                        $element = [$entityKey => $element];
507
                    }
508
509 622
                    if (isset($this->_rsm->indexByMap[$dqlAlias])) {
510 27
                        $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
511
512 27
                        if (isset($this->_hints['collection'])) {
513 10
                            $this->_hints['collection']->hydrateSet($resultKey, $element);
514
                        }
515
516 27
                        $result[$resultKey] = $element;
517
                    } else {
518 605
                        $resultKey = $this->resultCounter;
519 605
                        ++$this->resultCounter;
520
521 605
                        if (isset($this->_hints['collection'])) {
522 112
                            $this->_hints['collection']->hydrateAdd($element);
523
                        }
524
525 605
                        $result[] = $element;
526
                    }
527
528 622
                    $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
529
530
                    // Update result pointer
531 622
                    $this->resultPointers[$dqlAlias] = $element;
532
533
                } else {
534
                    // Update result pointer
535 78
                    $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
536 78
                    $this->resultPointers[$dqlAlias] = $result[$index];
537 78
                    $resultKey = $index;
538
                }
539
            }
540
541 622
            if (isset($this->_hints[Query::HINT_INTERNAL_ITERATION]) && $this->_hints[Query::HINT_INTERNAL_ITERATION]) {
542 622
                $this->_uow->hydrationComplete();
543
            }
544
        }
545
546 657
        if ( ! isset($resultKey) ) {
547 36
            $this->resultCounter++;
548
        }
549
550
        // Append scalar values to mixed result sets
551 657
        if (isset($rowData['scalars'])) {
552 55
            if ( ! isset($resultKey) ) {
553 23
                $resultKey = (isset($this->_rsm->indexByMap['scalars']))
554 2
                    ? $row[$this->_rsm->indexByMap['scalars']]
555 23
                    : $this->resultCounter - 1;
556
            }
557
558 55
            foreach ($rowData['scalars'] as $name => $value) {
559 55
                $result[$resultKey][$name] = $value;
560
            }
561
        }
562
563
        // Append new object to mixed result sets
564 657
        if (isset($rowData['newObjects'])) {
565 19
            if ( ! isset($resultKey) ) {
566 13
                $resultKey = $this->resultCounter - 1;
567
            }
568
569
570 19
            $scalarCount = (isset($rowData['scalars'])? count($rowData['scalars']): 0);
571
572 19
            foreach ($rowData['newObjects'] as $objIndex => $newObject) {
573 19
                $class  = $newObject['class'];
574 19
                $args   = $newObject['args'];
575 19
                $obj    = $class->newInstanceArgs($args);
576
577 19
                if ($scalarCount == 0 && count($rowData['newObjects']) == 1 ) {
578 10
                    $result[$resultKey] = $obj;
579
580 10
                    continue;
581
                }
582
583 9
                $result[$resultKey][$objIndex] = $obj;
584
            }
585
        }
586 657
    }
587
588
    /**
589
     * When executed in a hydrate() loop we may have to clear internal state to
590
     * decrease memory consumption.
591
     *
592
     * @param mixed $eventArgs
593
     *
594
     * @return void
595
     */
596 4
    public function onClear($eventArgs)
597
    {
598 4
        parent::onClear($eventArgs);
599
600 4
        $aliases             = array_keys($this->identifierMap);
601
602 4
        $this->identifierMap = array_fill_keys($aliases, []);
603 4
    }
604
}
605