Passed
Push — master ( c32ba8...e3ecec )
by Marco
01:03
created

ArrayHydrator::updateResultPointer()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5.0592

Importance

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