Failed Conditions
Push — master ( 8be1e3...e3936d )
by Marco
14s
created

Doctrine/ORM/Internal/Hydration/ArrayHydrator.php (1 issue)

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