Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

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

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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]) ) {
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'])) {
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'])) {
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