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
|
|||
299 | 1 | return; |
|
300 | } |
||
301 | |||
302 | 67 | end($coll); |
|
303 | 67 | $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; |
|
304 | |||
305 | 67 | return; |
|
306 | } |
||
307 | } |
||
308 |
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.