Failed Conditions
Pull Request — master (#6649)
by Marco
140:45 queued 75:42
created

ObjectHydrator::buildEntityTryGet()   C

Complexity

Conditions 9
Paths 4

Size

Total Lines 49
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 49
ccs 24
cts 24
cp 1
rs 5.7446
c 0
b 0
f 0
cc 9
eloc 22
nc 4
nop 1
crap 9
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\EntityManagerInterface;
23
use Doctrine\ORM\Internal\Hydration\Cache\LazyPropertyMap;
24
use Doctrine\ORM\UnitOfWork;
25
use PDO;
26
use Doctrine\ORM\Mapping\ClassMetadata;
27
use Doctrine\ORM\PersistentCollection;
28
use Doctrine\ORM\Query;
29
use Doctrine\Common\Collections\ArrayCollection;
30
use Doctrine\ORM\Proxy\Proxy;
31
32
/**
33
 * The ObjectHydrator constructs an object graph out of an SQL result set.
34
 *
35
 * Internal note: Highly performance-sensitive code.
36
 *
37
 * @since  2.0
38
 * @author Roman Borschel <[email protected]>
39
 * @author Guilherme Blanco <[email protected]>
40
 * @author Fabio B. Silva <[email protected]>
41
 */
42
class ObjectHydrator extends AbstractHydrator
43
{
44
    /**
45
     * @var array
46
     */
47
    private $identifierMap = [];
48
49
    /**
50
     * @var object[] indexed by oid
51
     */
52
    private $trackedWritableEntities = [];
53
54
    /**
55
     * @var array
56
     */
57
    private $resultPointers = [];
58
59
    /**
60
     * @var array
61
     */
62
    private $idTemplate = [];
63
64
    /**
65
     * @var integer
66
     */
67
    private $resultCounter = 0;
68
69
    /**
70
     * @var array
71
     */
72
    private $rootAliases = [];
73
74
    /**
75
     * @var array
76
     */
77
    private $initializedCollections = [];
78
79
    /**
80 684
     * @var array
81
     */
82 684
    private $existingCollections = [];
83 583
84
    /**
85
     * @var LazyPropertyMap|callable[]
86 684
     */
87 650
    private $entityTryGetCallbacksByEntityName;
88 650
89
    public function __construct(EntityManagerInterface $em)
90
    {
91
        parent::__construct($em);
92 650
93 650
        $this->entityTryGetCallbacksByEntityName = new LazyPropertyMap([$this, 'buildEntityTryGet']);
94
    }
95
96 329
    /**
97
     * {@inheritdoc}
98 329
     */
99 1
    protected function prepare()
100
    {
101
        if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
102 328
            $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true;
103 328
        }
104 328
105
        foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
106 328
            $this->identifierMap[$dqlAlias] = [];
107
            $this->idTemplate[$dqlAlias]    = '';
108 328
109 35
            // Remember which associations are "fetch joined", so that we know where to inject
110
            // collection stubs or proxies and where not.
111
            if ( ! isset($this->_rsm->relationMap[$dqlAlias])) {
112
                continue;
113 307
            }
114 258
115
            $parent = $this->_rsm->parentAliasMap[$dqlAlias];
116 258
117
            if ( ! isset($this->_rsm->aliasMap[$parent])) {
118
                throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent);
119
            }
120 68
121 47
            /* @var $sourceClass ClassMetadata */
122 47
            $sourceClass = $this->_metadataCache->{$this->_rsm->aliasMap[$parent]};
123
            $assoc       = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
124 47
125 28
            $this->_hints['fetched'][$parent][$assoc['fieldName']] = true;
126
127
            if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
128 41
                continue;
129
            }
130
131 683
            // Mark any non-collection opposite sides as fetched, too.
132
            if ($assoc['mappedBy']) {
133
                $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true;
134
135
                continue;
136 678
            }
137
138 678
            // handle fetch-joined owning side bi-directional one-to-one associations
139
            if ($assoc['inversedBy']) {
140 678
                /* @var $class ClassMetadata */
141
                $class        = $this->_metadataCache->{$className};
142 678
                $inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
143 678
144 678
                if ( ! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) {
145 678
                    continue;
146
                }
147 678
148 678
                $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true;
149
            }
150
        }
151 678
    }
152 678
153
    /**
154
     * {@inheritdoc}
155
     */
156
    protected function cleanup()
157 677
    {
158
        $eagerLoad = (isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] == true;
159 677
160
        parent::cleanup();
161 677
162 638
        $this->identifierMap =
163
        $this->initializedCollections =
164
        $this->existingCollections =
165
        $this->resultPointers = [];
166 674
167 71
        if ($eagerLoad) {
168
            $this->_uow->triggerEagerLoads();
169
        }
170 674
171
        $this->_uow->hydrationComplete();
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177 View Code Duplication
    protected function hydrateAllData()
178
    {
179
        $result = [];
180
181
        while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
182
            $this->hydrateRowData($row, $result);
183 103
        }
184
185 103
        // Take snapshots from all newly initialized collections
186 103
        foreach ($this->initializedCollections as $coll) {
187 103
            $coll->takeSnapshot();
188
        }
189 103
190 67
        return $result;
191
    }
192
193 103
    /**
194 67
     * Initializes a related collection.
195 67
     *
196
     * @param object        $entity         The entity to which the collection belongs.
197 67
     * @param ClassMetadata $class
198
     * @param string        $fieldName      The name of the field on the entity that holds the collection.
199 67
     * @param string        $parentDqlAlias Alias of the parent fetch joining this collection.
200 67
     *
201
     * @return \Doctrine\ORM\PersistentCollection
202 67
     */
203
    private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
204 40
    {
205 40
        $oid      = spl_object_hash($entity);
206 40
        $relation = $class->associationMappings[$fieldName];
207
        $value    = $class->reflFields[$fieldName]->getValue($entity);
208
209 6
        if ($value === null || is_array($value)) {
210 6
            $value = new ArrayCollection((array) $value);
211 6
        }
212
213 6
        if ( ! $value instanceof PersistentCollection) {
214
            $value = new PersistentCollection(
215
                $this->_em,
216 35
                $this->_metadataCache->{$relation['targetEntity']},
217
                $value
218
            );
219 103
            $value->setOwner($entity, $relation);
220
221
            $class->reflFields[$fieldName]->setValue($entity, $value);
222
            $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
223
224
            $this->initializedCollections[$oid . $fieldName] = $value;
225
        } else if (
226
            isset($this->_hints[Query::HINT_REFRESH]) ||
227
            isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
228
             ! $value->isInitialized()
229
        ) {
230
            // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
231
            $value->setDirty(false);
232 608
            $value->setInitialized(true);
233
            $value->unwrap()->clear();
234 608
235
            $this->initializedCollections[$oid . $fieldName] = $value;
236 608
        } else {
237 69
            // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
238
            $this->existingCollections[$oid . $fieldName] = $value;
239 69
        }
240 1
241
        return $value;
242
    }
243 68
244
    /**
245 68
     * Gets an entity instance.
246 1
     *
247
     * @param array  $data     The instance data.
248
     * @param string $dqlAlias The DQL alias of the entity's class.
249 68
     *
250
     * @return object The entity.
251
     *
252
     * @throws HydrationException
253 68
     */
254 68
    private function getEntity(array $data, $dqlAlias)
255
    {
256 68
        $className = $this->_rsm->aliasMap[$dqlAlias];
257 1
258
        if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
259
            $fieldName = $this->_rsm->discriminatorColumns[$dqlAlias];
260 67
261
            if ( ! isset($this->_rsm->metaMappings[$fieldName])) {
262 67
                throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $fieldName, $dqlAlias);
263
            }
264
265 606
            $discrColumn = $this->_rsm->metaMappings[$fieldName];
266 24
267
            if ( ! isset($data[$discrColumn])) {
268
                throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias);
269 606
            }
270
271 606
            if ($data[$discrColumn] === "") {
272
                throw HydrationException::emptyDiscriminatorValue($dqlAlias);
273
            }
274
275
            /* @var $class ClassMetadata */
276
            $class    = $this->_metadataCache->{$className};
277
            $discrMap = $class->discriminatorMap;
278
            $discriminatorValue = (string) $data[$discrColumn];
279
280 35
            if ( ! isset($discrMap[$discriminatorValue])) {
281
                throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap));
282
            }
283 35
284
            $className = $discrMap[$discriminatorValue];
285
286 35
            unset($data[$discrColumn]);
287 1
        }
288
289 1
        if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->rootAliases[$dqlAlias])) {
290 1
            $this->registerManaged($this->_metadataCache->{$className}, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
291 1
        }
292 1
293
        $this->_hints['fetchAlias'] = $dqlAlias;
294
295 1
        $managedEntity = ($this->entityTryGetCallbacksByEntityName->{$className})($data);
296 34
297
        // If the entity is not existing in the UnitOfWork, then we
298
        // are the one creating it: let's track the object hash to
299
        // allow writing to it. Note that we discard `$managedEntity`
300 34
        // anyway, as we need to call `createEntity` with the data
301
        // in any case to allow refreshing proxy information, for
302
        // example
303
        if (! $managedEntity) {
304
            $entity = $this->_uow->createEntity($className, $data, $this->_hints);
305
306
            $this->trackedWritableEntities[\spl_object_hash($entity)] = true;
307
308
            return $entity;
309
        }
310
311
        $entity = $this->_uow->createEntity($className, $data, $this->_hints);
312
313
        // If the entity is not created by us, we can only consider
314
        // it to be created in here if it's a non-initialized proxy
315
        if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
316
            $this->trackedWritableEntities[\spl_object_hash($entity)] = true;
317
        }
318
319
        return $entity;
320
    }
321
322
    /**
323
     * @internal do not use this or you shall be punished with the wrath of the debugger
324
     * @private
325 643
     *
326
     * @param string $className
327
     *
328 643
     * @return callable
329 643
     */
330
    public function buildEntityTryGet(string $className) : callable
331 643
    {
332
        /* @var $metadata ClassMetadata */
333
        $metadata       = $this->_metadataCache->{$className};
334 643
        $rootEntityName = $metadata->rootEntityName;
335
        /* @var $idKeys string[] */
336
        $idKeys      = [];
337 643
338 608
        // Optimisation if the identifier is not composite.
339
        // This entire block can be removed and everything works like before, just with more function calls
340 608
        if (! $metadata->isIdentifierComposite) {
341
            $idKey = $metadata->associationMappings[$metadata->identifier[0]]['joinColumns'][0]['name']
342
                ?? $metadata->identifier[0];
343 320
344
            /**
345
             * @return object|Proxy|null
346 320
             */
347
            return function (array $data) use ($idKey, $rootEntityName) {
348
                if (! isset($data[$idKey])) {
349 320
                    return null;
350
                }
351 2
352
                return $this->_uow->tryGetByIdHash((string) $data[$idKey], $rootEntityName) ?: null;
353
            };
354 320
        }
355 320
356 320
        foreach ($metadata->identifier as $idFieldName) {
357 320
            $idKeys[] = isset($metadata->associationMappings[$idFieldName])
358
                ? $metadata->associationMappings[$idFieldName]['joinColumns'][0]['name']
359
                : $idFieldName;
360 320
        }
361 18
362 18
        /**
363 304
         * @return object|Proxy|null
364 304
         */
365
        return function (array $data) use ($idKeys, $rootEntityName) {
366
            $idHashData = [];
367 2
368
            foreach ($idKeys as $idColumn) {
369
                if (! isset($data[$idColumn])) {
370 2
                    return null;
371 2
                }
372
373
                $idHashData[] = $data[$idColumn];
374 2
            }
375 2
376
            return $this->_uow->tryGetByIdHash(\implode(' ', $idHashData), $rootEntityName) ?: null;
377
        };
378 320
    }
379
380
    /**
381 320
     * Hydrates a single row in an SQL result set.
382
     *
383 104
     * @internal
384
     * First, the data of the row is split into chunks where each chunk contains data
385 104
     * that belongs to a particular component/class. Afterwards, all these chunks
386 100
     * are processed, one after the other. For each chunk of class data only one of the
387 100
     * following code paths is executed:
388 47
     *
389 100
     * Path A: The data chunk belongs to a joined/associated object and the association
390 100
     *         is collection-valued.
391
     * Path B: The data chunk belongs to a joined/associated object and the association
392
     *         is single-valued.
393 100
     * Path C: The data chunk belongs to a root result element/object that appears in the topmost
394 100
     *         level of the hydrated result. A typical example are the objects of the type
395 100
     *         specified by the FROM clause in a DQL query.
396
     *
397 100
     * @param array $row    The data of the row to process.
398 100
     * @param array $result The result array to fill.
399
     *
400 35
     * @return void
401 35
     */
402
    protected function hydrateRowData(array $row, array &$result)
403 35
    {
404
        // Initialize
405
        $id = $this->idTemplate; // initialize the id-memory
406 68
        $nonemptyComponents = [];
407
        // Split the row data into chunks of class data.
408 68
        $rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
409 12
410 12
        // reset result pointers for each data row
411 12
        $this->resultPointers = [];
412
413 56
        // Hydrate the data chunks
414 56
        foreach ($rowData['data'] as $dqlAlias => $data) {
415 56
            $entityName = $this->_rsm->aliasMap[$dqlAlias];
416
417
            if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
418 100
                // It's a joined result
419
420
                $parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
421
                // we need the $path to save into the identifier map which entities were already
422 100
                // seen for this parent-child relationship
423
                $path = $parentAlias . '.' . $dqlAlias;
424 9
425 5
                // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
426 6
                if ( ! isset($nonemptyComponents[$parentAlias])) {
427 104
                    // TODO: Add special case code where we hydrate the right join objects into identity map at least
428
                    continue;
429
                }
430
431
                $parentClass    = $this->_metadataCache->{$this->_rsm->aliasMap[$parentAlias]};
432 238
                $relationField  = $this->_rsm->relationMap[$dqlAlias];
433
                $relation       = $parentClass->associationMappings[$relationField];
434 238
                $reflField      = $parentClass->reflFields[$relationField];
435
436
                // Get a reference to the parent object to which the joined element belongs.
437 223
                if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) {
438 118
                    $objectClass = $this->resultPointers[$parentAlias];
439 117
                    $parentObject = $objectClass[key($objectClass)];
440 117
                } else if (isset($this->resultPointers[$parentAlias])) {
441 117
                    $parentObject = $this->resultPointers[$parentAlias];
442
                } else {
443 117
                    // Parent object of relation not found, mark as not-fetched again
444
                    $element = $this->getEntity($data, $dqlAlias);
445
446 53
                    // Update result pointer and provide initial fetch data for parent
447 36
                    $this->resultPointers[$dqlAlias] = $element;
448 36
                    $rowData['data'][$parentAlias][$relationField] = $element;
449 13
450 36
                    // Mark as not-fetched again
451
                    unset($this->_hints['fetched'][$parentAlias][$relationField]);
452 18
                    continue;
453
                }
454 53
455
                $oid = spl_object_hash($parentObject);
456
457
                // Check the type of the relation (many or single-valued)
458 67
                if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
459 67
                    // PATH A: Collection-valued association
460
                    $reflFieldValue = $reflField->getValue($parentObject);
461
462 117
                    if (isset($nonemptyComponents[$dqlAlias])) {
463
                        $collKey = $oid . $relationField;
464 125
                        if (isset($this->initializedCollections[$collKey])) {
465 222
                            $reflFieldValue = $this->initializedCollections[$collKey];
466
                        } else if ( ! isset($this->existingCollections[$collKey])) {
467
                            $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
468
                        }
469
470 319
                        $indexExists    = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
471
                        $index          = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
472
                        $indexIsValid   = $index !== false ? isset($reflFieldValue[$index]) : false;
473
474
                        if ( ! $indexExists || ! $indexIsValid) {
475 608
                            if (isset($this->existingCollections[$collKey])) {
476 608
                                // Collection exists, only look for the element in the identity map.
477
                                if ($element = ($this->entityTryGetCallbacksByEntityName->{$entityName})($data)) {
478
                                    $this->resultPointers[$dqlAlias] = $element;
479 608
                                } else {
480 6
                                    unset($this->resultPointers[$dqlAlias]);
481 2
                                }
482
                            } else {
483 4
                                $element = $this->getEntity($data, $dqlAlias);
484
485 6
                                if (isset($this->_rsm->indexByMap[$dqlAlias])) {
486 6
                                    $indexValue = $row[$this->_rsm->indexByMap[$dqlAlias]];
487 6
                                    $reflFieldValue->hydrateSet($indexValue, $element);
488
                                    $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
489
                                } else {
490
                                    $reflFieldValue->hydrateAdd($element);
491 608
                                    $reflFieldValue->last();
492 608
                                    $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
493
                                }
494 606
                                // Update result pointer
495 41
                                $this->resultPointers[$dqlAlias] = $element;
496
                            }
497
                        } else {
498 606
                            // Update result pointer
499 27
                            $this->resultPointers[$dqlAlias] = $reflFieldValue[$index];
500
                        }
501 27
                    } else if ( ! $reflFieldValue) {
502 10
                        $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...
503
                    } else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
504
                        $reflFieldValue->setInitialized(true);
505 27
                    }
506
507 589
                } else {
508 589
                    // PATH B: Single-valued association
509
                    $reflFieldValue = $reflField->getValue($parentObject);
510 589
511 110
                    if (isset($this->trackedWritableEntities[$oid]) || isset($this->_hints[Query::HINT_REFRESH])) {
512
                        // we only need to take action if `$parentObject` was not built or is not to be refreshed by this hydrator,
513
                        if (isset($nonemptyComponents[$dqlAlias])) {
514 589
                            $element = $this->getEntity($data, $dqlAlias);
515
                            $reflField->setValue($parentObject, $element);
516
                            $this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
517 606
                            $targetClass = $this->_metadataCache->{$relation['targetEntity']};
518
519
                            if ($relation['isOwningSide']) {
520 606
                                // TODO: Just check hints['fetched'] here?
521
                                // If there is an inverse mapping on the target class its bidirectional
522
                                if ($relation['inversedBy']) {
523
                                    $inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']];
524 78 View Code Duplication
                                    if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
525 78
                                        $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
526 78
                                        $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject);
527
                                    }
528
                                } else if ($parentClass === $targetClass && $relation['mappedBy']) {
529
                                    // Special case: bi-directional self-referencing one-one on the same class
530 606
                                    $targetClass->reflFields[$relationField]->setValue($element, $parentObject);
531 606
                                }
532 View Code Duplication
                            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
533
                                // For sure bidirectional, as there is no inverse side in unidirectional mappings
534
                                $targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject);
535 640
                                $this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject);
536 35
                            }
537
                            // Update result pointer
538
                            $this->resultPointers[$dqlAlias] = $element;
539
                        } else {
540 640
                            $this->_uow->setOriginalEntityProperty($oid, $relationField, null);
541 54
                            $reflField->setValue($parentObject, null);
542 22
                        }
543 2
                        // else leave $reflFieldValue null for single-valued associations
544 22
                    } else {
545
                        // Update result pointer
546
                        $this->resultPointers[$dqlAlias] = $reflFieldValue;
547 54
                    }
548 54
                }
549
            } else {
550
                // PATH C: Its a root result element
551
                $this->rootAliases[$dqlAlias] = true; // Mark as root alias
552
                $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
553 640
554 19
                // if this row has a NULL value for the root result id then make it a null result.
555 13 View Code Duplication
                if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
556
                    if ($this->_rsm->isMixed) {
557
                        $result[] = [$entityKey => null];
558
                    } else {
559 19
                        $result[] = null;
560
                    }
561 19
                    $resultKey = $this->resultCounter;
562 19
                    ++$this->resultCounter;
563 19
                    continue;
564 19
                }
565
566 19
                // check for existing result from the iterations before
567 10
                if ( ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
568
                    $element = $this->getEntity($data, $dqlAlias);
569 10
570
                    if ($this->_rsm->isMixed) {
571
                        $element = [$entityKey => $element];
572 9
                    }
573
574
                    if (isset($this->_rsm->indexByMap[$dqlAlias])) {
575 640
                        $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
576
577
                        if (isset($this->_hints['collection'])) {
578
                            $this->_hints['collection']->hydrateSet($resultKey, $element);
579
                        }
580
581
                        $result[$resultKey] = $element;
582
                    } else {
583
                        $resultKey = $this->resultCounter;
584
                        ++$this->resultCounter;
585 4
586
                        if (isset($this->_hints['collection'])) {
587 4
                            $this->_hints['collection']->hydrateAdd($element);
588
                        }
589 4
590
                        $result[] = $element;
591 4
                    }
592 4
593
                    $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
594
595
                    // Update result pointer
596
                    $this->resultPointers[$dqlAlias] = $element;
597
598
                } else {
599
                    // Update result pointer
600
                    $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
601
                    $this->resultPointers[$dqlAlias] = $result[$index];
602
                    $resultKey = $index;
603
                }
604
            }
605
606 View Code Duplication
            if (isset($this->_hints[Query::HINT_INTERNAL_ITERATION]) && $this->_hints[Query::HINT_INTERNAL_ITERATION]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
607
                $this->_uow->hydrationComplete();
608
            }
609
        }
610
611
        if ( ! isset($resultKey) ) {
612
            $this->resultCounter++;
613
        }
614
615
        // Append scalar values to mixed result sets
616 View Code Duplication
        if (isset($rowData['scalars'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
617
            if ( ! isset($resultKey) ) {
618
                $resultKey = (isset($this->_rsm->indexByMap['scalars']))
619
                    ? $row[$this->_rsm->indexByMap['scalars']]
620
                    : $this->resultCounter - 1;
621
            }
622
623
            foreach ($rowData['scalars'] as $name => $value) {
624
                $result[$resultKey][$name] = $value;
625
            }
626
        }
627
628
        // Append new object to mixed result sets
629 View Code Duplication
        if (isset($rowData['newObjects'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
630
            if ( ! isset($resultKey) ) {
631
                $resultKey = $this->resultCounter - 1;
632
            }
633
634
635
            $scalarCount = (isset($rowData['scalars'])? count($rowData['scalars']): 0);
636
637
            foreach ($rowData['newObjects'] as $objIndex => $newObject) {
638
                $class  = $newObject['class'];
639
                $args   = $newObject['args'];
640
                $obj    = $class->newInstanceArgs($args);
641
642
                if ($scalarCount == 0 && count($rowData['newObjects']) == 1 ) {
643
                    $result[$resultKey] = $obj;
644
645
                    continue;
646
                }
647
648
                $result[$resultKey][$objIndex] = $obj;
649
            }
650
        }
651
    }
652
653
    /**
654
     * When executed in a hydrate() loop we may have to clear internal state to
655
     * decrease memory consumption.
656
     *
657
     * @param mixed $eventArgs
658
     *
659
     * @return void
660
     */
661
    public function onClear($eventArgs)
662
    {
663
        parent::onClear($eventArgs);
664
665
        $aliases             = array_keys($this->identifierMap);
666
667
        $this->identifierMap = array_fill_keys($aliases, []);
668
    }
669
}
670