ArrayHydrator::hydrateRowData()   F
last analyzed

Complexity

Conditions 38
Paths > 20000

Size

Total Lines 167
Code Lines 95

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 83
CRAP Score 38.6799

Importance

Changes 0
Metric Value
cc 38
eloc 95
nc 25200
nop 2
dl 0
loc 167
ccs 83
cts 90
cp 0.9222
crap 38.6799
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Internal\Hydration;
6
7
use Doctrine\DBAL\FetchMode;
8
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
9
use function count;
10
use function end;
11
use function is_array;
12
use function key;
13
use function reset;
14
15
/**
16
 * The ArrayHydrator produces a nested array "graph" that is often (not always)
17
 * interchangeable with the corresponding object graph for read-only access.
18
 */
19
class ArrayHydrator extends AbstractHydrator
20
{
21
    /** @var bool[] */
22
    private $rootAliases = [];
23
24
    /** @var bool */
25
    private $isSimpleQuery = false;
26
27
    /** @var mixed[][] */
28
    private $identifierMap = [];
29
30
    /** @var mixed[] */
31
    private $resultPointers = [];
32
33
    /** @var string[] */
34
    private $idTemplate = [];
35
36
    /** @var int */
37
    private $resultCounter = 0;
38
39
    /**
40
     * {@inheritdoc}
41
     */
42 87
    protected function prepare()
43
    {
44 87
        $simpleQuery = 0;
45
46 87
        foreach ($this->rsm->aliasMap as $dqlAlias => $className) {
47 67
            $this->identifierMap[$dqlAlias]  = [];
48 67
            $this->resultPointers[$dqlAlias] = [];
49 67
            $this->idTemplate[$dqlAlias]     = '';
50 67
            $simpleQuery                    += 1; // avoiding counting the alias map
51
        }
52
53 87
        $this->isSimpleQuery = $simpleQuery < 2;
54 87
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59 84
    protected function hydrateAllData()
60
    {
61 84
        $result = [];
62
63 84
        while ($data = $this->stmt->fetch(FetchMode::ASSOCIATIVE)) {
64 82
            $this->hydrateRowData($data, $result);
65
        }
66
67 84
        return $result;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73 85
    protected function hydrateRowData(array $row, array &$result)
74
    {
75
        // 1) Initialize
76 85
        $id                 = $this->idTemplate; // initialize the id-memory
77 85
        $nonemptyComponents = [];
78 85
        $rowData            = $this->gatherRowData($row, $id, $nonemptyComponents);
79
80
        // 2) Now hydrate the data found in the current row.
81 85
        foreach ($rowData['data'] as $dqlAlias => $data) {
82 66
            $index = false;
83
84 66
            if (isset($this->rsm->parentAliasMap[$dqlAlias])) {
85
                // It's a joined result
86
87 18
                $parent = $this->rsm->parentAliasMap[$dqlAlias];
88 18
                $path   = $parent . '.' . $dqlAlias;
89
90
                // missing parent data, skipping as RIGHT JOIN hydration is not supported.
91 18
                if (! isset($nonemptyComponents[$parent])) {
92
                    continue;
93
                }
94
95
                // Get a reference to the right element in the result tree.
96
                // This element will get the associated element attached.
97 18
                if ($this->rsm->isMixed && isset($this->rootAliases[$parent])) {
98 10
                    $first = reset($this->resultPointers);
99
                    // TODO: Exception if $key === null ?
100 10
                    $baseElement =& $this->resultPointers[$parent][key($first)];
101 9
                } elseif (isset($this->resultPointers[$parent])) {
102 9
                    $baseElement =& $this->resultPointers[$parent];
103
                } else {
104
                    unset($this->resultPointers[$dqlAlias]); // Ticket #1228
105
106
                    continue;
107
                }
108
109 18
                $relationAlias = $this->rsm->relationMap[$dqlAlias];
110 18
                $parentClass   = $this->metadataCache[$this->rsm->aliasMap[$parent]];
111 18
                $relation      = $parentClass->getProperty($relationAlias);
112
113
                // Check the type of the relation (many or single-valued)
114 18
                if (! $relation instanceof ToOneAssociationMetadata) {
115 15
                    $oneToOne = false;
116
117 15
                    if (! isset($baseElement[$relationAlias])) {
118 15
                        $baseElement[$relationAlias] = [];
119
                    }
120
121 15
                    if (isset($nonemptyComponents[$dqlAlias])) {
122 15
                        $indexExists  = isset($this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
123 15
                        $index        = $indexExists ? $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
124 15
                        $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
125
126 15
                        if (! $indexExists || ! $indexIsValid) {
127 15
                            $element = $data;
128
129 15
                            if (isset($this->rsm->indexByMap[$dqlAlias])) {
130 5
                                $baseElement[$relationAlias][$row[$this->rsm->indexByMap[$dqlAlias]]] = $element;
131
                            } else {
132 10
                                $baseElement[$relationAlias][] = $element;
133
                            }
134
135 15
                            end($baseElement[$relationAlias]);
136
137 15
                            $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
138
                        }
139
                    }
140
                } else {
141 6
                    $oneToOne = true;
142
143 6
                    if (! isset($nonemptyComponents[$dqlAlias]) &&
144 6
                        ( ! isset($baseElement[$relationAlias]))
145
                    ) {
146
                        $baseElement[$relationAlias] = null;
147 6
                    } elseif (! isset($baseElement[$relationAlias])) {
148 5
                        $baseElement[$relationAlias] = $data;
149
                    }
150
                }
151
152 18
                $coll =& $baseElement[$relationAlias];
153
154 18
                if (is_array($coll)) {
155 18
                    $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
156
                }
157
            } else {
158
                // It's a root result element
159
160 66
                $this->rootAliases[$dqlAlias] = true; // Mark as root
161 66
                $entityKey                    = $this->rsm->entityMappings[$dqlAlias] ?: 0;
162
163
                // if this row has a NULL value for the root result id then make it a null result.
164 66
                if (! isset($nonemptyComponents[$dqlAlias])) {
165 4
                    $result[] = $this->rsm->isMixed
166 4
                        ? [$entityKey => null]
167
                        : null;
168
169 4
                    $resultKey = $this->resultCounter;
170 4
                    ++$this->resultCounter;
171
172 4
                    continue;
173
                }
174
175
                // Check for an existing element
176 66
                if ($this->isSimpleQuery || ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
177 66
                    $element = $this->rsm->isMixed
178 48
                        ? [$entityKey => $data]
179 66
                        : $data;
180
181 66
                    if (isset($this->rsm->indexByMap[$dqlAlias])) {
182 9
                        $resultKey          = $row[$this->rsm->indexByMap[$dqlAlias]];
183 9
                        $result[$resultKey] = $element;
184
                    } else {
185 57
                        $resultKey = $this->resultCounter;
186 57
                        $result[]  = $element;
187
188 57
                        ++$this->resultCounter;
189
                    }
190
191 66
                    $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
192
                } else {
193 15
                    $index     = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
194 15
                    $resultKey = $index;
195
                }
196
197 66
                $this->updateResultPointer($result, $index, $dqlAlias, false);
198
            }
199
        }
200
201 85
        if (! isset($resultKey)) {
202 19
            $this->resultCounter++;
203
        }
204
205
        // Append scalar values to mixed result sets
206 85
        if (isset($rowData['scalars'])) {
207 60
            if (! isset($resultKey)) {
208
                // this only ever happens when no object is fetched (scalar result only)
209 17
                $resultKey = isset($this->rsm->indexByMap['scalars'])
210
                    ? $row[$this->rsm->indexByMap['scalars']]
211 17
                    : $this->resultCounter - 1;
212
            }
213
214 60
            foreach ($rowData['scalars'] as $name => $value) {
215 60
                $result[$resultKey][$name] = $value;
216
            }
217
        }
218
219
        // Append new object to mixed result sets
220 85
        if (isset($rowData['newObjects'])) {
221 2
            if (! isset($resultKey)) {
222 2
                $resultKey = $this->resultCounter - 1;
223
            }
224
225 2
            $scalarCount      = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0);
226 2
            $onlyOneRootAlias = $scalarCount === 0 && count($rowData['newObjects']) === 1;
227
228 2
            foreach ($rowData['newObjects'] as $objIndex => $newObject) {
229 2
                $class = $newObject['class'];
230 2
                $args  = $newObject['args'];
231 2
                $obj   = $class->newInstanceArgs($args);
232
233 2
                if ($onlyOneRootAlias || count($args) === $scalarCount) {
234 2
                    $result[$resultKey] = $obj;
235
236 2
                    continue;
237
                }
238
239
                $result[$resultKey][$objIndex] = $obj;
240
            }
241
        }
242 85
    }
243
244
    /**
245
     * Updates the result pointer for an Entity. The result pointers point to the
246
     * last seen instance of each Entity type. This is used for graph construction.
247
     *
248
     * @param mixed[]  $coll     The element.
249
     * @param bool|int $index    Index of the element in the collection.
250
     * @param string   $dqlAlias
251
     * @param bool     $oneToOne Whether it is a single-valued association or not.
252
     */
253 66
    private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
254
    {
255 66
        if ($coll === null) {
0 ignored issues
show
introduced by
The condition $coll === null is always false.
Loading history...
256
            unset($this->resultPointers[$dqlAlias]); // Ticket #1228
257
258
            return;
259
        }
260
261 66
        if ($oneToOne) {
262 5
            $this->resultPointers[$dqlAlias] =& $coll;
263
264 5
            return;
265
        }
266
267 66
        if ($index !== false) {
268 15
            $this->resultPointers[$dqlAlias] =& $coll[$index];
269
270 15
            return;
271
        }
272
273 66
        if (! $coll) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $coll of type array<mixed,mixed> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
274 1
            return;
275
        }
276
277 66
        end($coll);
278 66
        $this->resultPointers[$dqlAlias] =& $coll[key($coll)];
279 66
    }
280
}
281