Completed
Pull Request — master (#6958)
by Matthew
126:53 queued 28:49
created

ArrayHydrator   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Test Coverage

Coverage 94.02%

Importance

Changes 0
Metric Value
dl 0
loc 271
ccs 110
cts 117
cp 0.9402
rs 8.3999
c 0
b 0
f 0
wmc 46

4 Methods

Rating   Name   Duplication   Size   Complexity  
A hydrateAllData() 0 9 2
A prepare() 0 12 2
F hydrateRowData() 0 168 38
B updateResultPointer() 0 22 4

How to fix   Complexity   

Complex Class

Complex classes like ArrayHydrator 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 ArrayHydrator, and based on these observations, apply Extract Interface, too.

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