|
0
|
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 LGPL. For more information, see |
|
|
17 |
* <http://www.doctrine-project.org>. |
|
|
18 |
*/ |
|
|
19 |
|
|
|
20 |
namespace Doctrine\ORM\Internal\Hydration; |
|
|
21 |
|
|
|
22 |
use PDO, |
|
|
23 |
Doctrine\ORM\Mapping\ClassMetadata, |
|
|
24 |
Doctrine\ORM\PersistentCollection, |
|
|
25 |
Doctrine\ORM\Query, |
|
|
26 |
Doctrine\Common\Collections\ArrayCollection, |
|
|
27 |
Doctrine\Common\Collections\Collection; |
|
|
28 |
|
|
|
29 |
/** |
|
|
30 |
* The ObjectHydrator constructs an object graph out of an SQL result set. |
|
|
31 |
* |
|
|
32 |
* @author Roman Borschel <roman@code-factory.org> |
|
|
33 |
* @since 2.0 |
|
|
34 |
* @internal Highly performance-sensitive code. |
|
|
35 |
*/ |
|
|
36 |
class ObjectHydrator extends AbstractHydrator |
|
|
37 |
{ |
|
|
38 |
/* Local ClassMetadata cache to avoid going to the EntityManager all the time. |
|
|
39 |
* This local cache is maintained between hydration runs and not cleared. |
|
|
40 |
*/ |
|
|
41 |
private $_ce = array(); |
|
|
42 |
|
|
|
43 |
/* The following parts are reinitialized on every hydration run. */ |
|
|
44 |
|
|
|
45 |
private $_identifierMap; |
|
|
46 |
private $_resultPointers; |
|
|
47 |
private $_idTemplate; |
|
|
48 |
private $_resultCounter; |
|
|
49 |
private $_rootAliases = array(); |
|
|
50 |
private $_initializedCollections = array(); |
|
|
51 |
private $_existingCollections = array(); |
|
|
52 |
//private $_createdEntities; |
|
|
53 |
|
|
|
54 |
|
|
|
55 |
/** @override */ |
|
|
56 |
protected function _prepare() |
|
|
57 |
{ |
|
|
58 |
$this->_identifierMap = |
|
|
59 |
$this->_resultPointers = |
|
|
60 |
$this->_idTemplate = array(); |
|
|
61 |
$this->_resultCounter = 0; |
|
|
62 |
if (!isset($this->_hints['deferEagerLoad'])) { |
|
|
63 |
$this->_hints['deferEagerLoad'] = true; |
|
|
64 |
} |
|
|
65 |
|
|
|
66 |
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { |
|
|
67 |
$this->_identifierMap[$dqlAlias] = array(); |
|
|
68 |
$this->_idTemplate[$dqlAlias] = ''; |
|
|
69 |
$class = $this->_em->getClassMetadata($className); |
|
|
70 |
|
|
|
71 |
if ( ! isset($this->_ce[$className])) { |
|
|
72 |
$this->_ce[$className] = $class; |
|
|
73 |
} |
|
|
74 |
|
|
|
75 |
// Remember which associations are "fetch joined", so that we know where to inject |
|
|
76 |
// collection stubs or proxies and where not. |
|
|
77 |
if (isset($this->_rsm->relationMap[$dqlAlias])) { |
|
|
78 |
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) { |
|
|
79 |
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]); |
|
|
80 |
} |
|
|
81 |
|
|
|
82 |
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; |
|
|
83 |
$sourceClass = $this->_getClassMetadata($sourceClassName); |
|
|
84 |
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; |
|
|
85 |
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true; |
|
|
86 |
if ($sourceClass->subClasses) { |
|
|
87 |
foreach ($sourceClass->subClasses as $sourceSubclassName) { |
|
|
88 |
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true; |
|
|
89 |
} |
|
|
90 |
} |
|
|
91 |
if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) { |
|
|
92 |
// Mark any non-collection opposite sides as fetched, too. |
|
|
93 |
if ($assoc['mappedBy']) { |
|
|
94 |
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true; |
|
|
95 |
} else { |
|
|
96 |
if ($assoc['inversedBy']) { |
|
|
97 |
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; |
|
|
98 |
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) { |
|
|
99 |
$this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true; |
|
|
100 |
if ($class->subClasses) { |
|
|
101 |
foreach ($class->subClasses as $targetSubclassName) { |
|
|
102 |
$this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true; |
|
|
103 |
} |
|
|
104 |
} |
|
|
105 |
} |
|
|
106 |
} |
|
|
107 |
} |
|
|
108 |
} |
|
|
109 |
} |
|
|
110 |
} |
|
|
111 |
} |
|
|
112 |
|
|
|
113 |
/** |
|
|
114 |
* {@inheritdoc} |
|
|
115 |
*/ |
|
|
116 |
protected function _cleanup() |
|
|
117 |
{ |
|
|
118 |
$eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; |
|
|
119 |
|
|
|
120 |
parent::_cleanup(); |
|
|
121 |
$this->_identifierMap = |
|
|
122 |
$this->_initializedCollections = |
|
|
123 |
$this->_existingCollections = |
|
|
124 |
$this->_resultPointers = array(); |
|
|
125 |
|
|
|
126 |
if ($eagerLoad) { |
|
|
127 |
$this->_em->getUnitOfWork()->triggerEagerLoads(); |
|
|
128 |
} |
|
|
129 |
} |
|
|
130 |
|
|
|
131 |
/** |
|
|
132 |
* {@inheritdoc} |
|
|
133 |
*/ |
|
|
134 |
protected function _hydrateAll() |
|
|
135 |
{ |
|
|
136 |
$result = array(); |
|
|
137 |
$cache = array(); |
|
|
138 |
|
|
|
139 |
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { |
|
|
140 |
$this->_hydrateRow($row, $cache, $result); |
|
|
141 |
} |
|
|
142 |
|
|
|
143 |
// Take snapshots from all newly initialized collections |
|
|
144 |
foreach ($this->_initializedCollections as $coll) { |
|
|
145 |
$coll->takeSnapshot(); |
|
|
146 |
} |
|
|
147 |
|
|
|
148 |
return $result; |
|
|
149 |
} |
|
|
150 |
|
|
|
151 |
/** |
|
|
152 |
* Initializes a related collection. |
|
|
153 |
* |
|
|
154 |
* @param object $entity The entity to which the collection belongs. |
|
|
155 |
* @param string $name The name of the field on the entity that holds the collection. |
|
|
156 |
*/ |
|
|
157 |
private function _initRelatedCollection($entity, $class, $fieldName) |
|
|
158 |
{ |
|
|
159 |
$oid = spl_object_hash($entity); |
|
|
160 |
$relation = $class->associationMappings[$fieldName]; |
|
|
161 |
|
|
|
162 |
$value = $class->reflFields[$fieldName]->getValue($entity); |
|
|
163 |
if ($value === null) { |
|
|
164 |
$value = new ArrayCollection; |
|
|
165 |
} |
|
|
166 |
|
|
|
167 |
if ( ! $value instanceof PersistentCollection) { |
|
|
168 |
$value = new PersistentCollection( |
|
|
169 |
$this->_em, |
|
|
170 |
$this->_ce[$relation['targetEntity']], |
|
|
171 |
$value |
|
|
172 |
); |
|
|
173 |
$value->setOwner($entity, $relation); |
|
|
174 |
$class->reflFields[$fieldName]->setValue($entity, $value); |
|
|
175 |
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); |
|
|
176 |
$this->_initializedCollections[$oid . $fieldName] = $value; |
|
|
177 |
} else if (isset($this->_hints[Query::HINT_REFRESH]) || |
|
|
178 |
isset($this->_hints['fetched'][$class->name][$fieldName]) && |
|
|
179 |
! $value->isInitialized()) { |
|
|
180 |
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! |
|
|
181 |
$value->setDirty(false); |
|
|
182 |
$value->setInitialized(true); |
|
|
183 |
$value->unwrap()->clear(); |
|
|
184 |
$this->_initializedCollections[$oid . $fieldName] = $value; |
|
|
185 |
} else { |
|
|
186 |
// Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! |
|
|
187 |
$this->_existingCollections[$oid . $fieldName] = $value; |
|
|
188 |
} |
|
|
189 |
|
|
|
190 |
return $value; |
|
|
191 |
} |
|
|
192 |
|
|
|
193 |
/** |
|
|
194 |
* Gets an entity instance. |
|
|
195 |
* |
|
|
196 |
* @param $data The instance data. |
|
|
197 |
* @param $dqlAlias The DQL alias of the entity's class. |
|
|
198 |
* @return object The entity. |
|
|
199 |
*/ |
|
|
200 |
private function _getEntity(array $data, $dqlAlias) |
|
|
201 |
{ |
|
|
202 |
$className = $this->_rsm->aliasMap[$dqlAlias]; |
|
|
203 |
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { |
|
|
204 |
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]]; |
|
|
205 |
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]]; |
|
|
206 |
unset($data[$discrColumn]); |
|
|
207 |
} |
|
|
208 |
|
|
|
209 |
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) { |
|
|
210 |
$class = $this->_ce[$className]; |
|
|
211 |
$this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); |
|
|
212 |
} |
|
|
213 |
|
|
|
214 |
return $this->_uow->createEntity($className, $data, $this->_hints); |
|
|
215 |
} |
|
|
216 |
|
|
|
217 |
private function _getEntityFromIdentityMap($className, array $data) |
|
|
218 |
{ |
|
|
219 |
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent? |
|
|
220 |
$class = $this->_ce[$className]; |
|
|
221 |
/* @var $class ClassMetadata */ |
|
|
222 |
if ($class->isIdentifierComposite) { |
|
|
223 |
$idHash = ''; |
|
|
224 |
foreach ($class->identifier as $fieldName) { |
|
|
225 |
if (isset($class->associationMappings[$fieldName])) { |
|
|
226 |
$idHash .= $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] . ' '; |
|
|
227 |
} else { |
|
|
228 |
$idHash .= $data[$fieldName] . ' '; |
|
|
229 |
} |
|
|
230 |
} |
|
|
231 |
return $this->_uow->tryGetByIdHash(rtrim($idHash), $class->rootEntityName); |
|
|
232 |
} else if (isset($class->associationMappings[$class->identifier[0]])) { |
|
|
233 |
return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName); |
|
|
234 |
} else { |
|
|
235 |
return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName); |
|
|
236 |
} |
|
|
237 |
} |
|
|
238 |
|
|
|
239 |
/** |
|
|
240 |
* Gets a ClassMetadata instance from the local cache. |
|
|
241 |
* If the instance is not yet in the local cache, it is loaded into the |
|
|
242 |
* local cache. |
|
|
243 |
* |
|
|
244 |
* @param string $className The name of the class. |
|
|
245 |
* @return ClassMetadata |
|
|
246 |
*/ |
|
|
247 |
private function _getClassMetadata($className) |
|
|
248 |
{ |
|
|
249 |
if ( ! isset($this->_ce[$className])) { |
|
|
250 |
$this->_ce[$className] = $this->_em->getClassMetadata($className); |
|
|
251 |
} |
|
|
252 |
return $this->_ce[$className]; |
|
|
253 |
} |
|
|
254 |
|
|
|
255 |
/** |
|
|
256 |
* Hydrates a single row in an SQL result set. |
|
|
257 |
* |
|
|
258 |
* @internal |
|
|
259 |
* First, the data of the row is split into chunks where each chunk contains data |
|
|
260 |
* that belongs to a particular component/class. Afterwards, all these chunks |
|
|
261 |
* are processed, one after the other. For each chunk of class data only one of the |
|
|
262 |
* following code paths is executed: |
|
|
263 |
* |
|
|
264 |
* Path A: The data chunk belongs to a joined/associated object and the association |
|
|
265 |
* is collection-valued. |
|
|
266 |
* Path B: The data chunk belongs to a joined/associated object and the association |
|
|
267 |
* is single-valued. |
|
|
268 |
* Path C: The data chunk belongs to a root result element/object that appears in the topmost |
|
|
269 |
* level of the hydrated result. A typical example are the objects of the type |
|
|
270 |
* specified by the FROM clause in a DQL query. |
|
|
271 |
* |
|
|
272 |
* @param array $data The data of the row to process. |
|
|
273 |
* @param array $cache The cache to use. |
|
|
274 |
* @param array $result The result array to fill. |
|
|
275 |
*/ |
|
|
276 |
protected function _hydrateRow(array $data, array &$cache, array &$result) |
|
|
277 |
{ |
|
|
278 |
// Initialize |
|
|
279 |
$id = $this->_idTemplate; // initialize the id-memory |
|
|
280 |
$nonemptyComponents = array(); |
|
|
281 |
// Split the row data into chunks of class data. |
|
|
282 |
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); |
|
|
283 |
|
|
|
284 |
// Extract scalar values. They're appended at the end. |
|
|
285 |
if (isset($rowData['scalars'])) { |
|
|
286 |
$scalars = $rowData['scalars']; |
|
|
287 |
unset($rowData['scalars']); |
|
|
288 |
if (empty($rowData)) { |
|
|
289 |
++$this->_resultCounter; |
|
|
290 |
} |
|
|
291 |
} |
|
|
292 |
|
|
|
293 |
// Hydrate the data chunks |
|
|
294 |
foreach ($rowData as $dqlAlias => $data) { |
|
|
295 |
$entityName = $this->_rsm->aliasMap[$dqlAlias]; |
|
|
296 |
|
|
|
297 |
if (isset($this->_rsm->parentAliasMap[$dqlAlias])) { |
|
|
298 |
// It's a joined result |
|
|
299 |
|
|
|
300 |
$parentAlias = $this->_rsm->parentAliasMap[$dqlAlias]; |
|
|
301 |
// we need the $path to save into the identifier map which entities were already |
|
|
302 |
// seen for this parent-child relationship |
|
|
303 |
$path = $parentAlias . '.' . $dqlAlias; |
|
|
304 |
|
|
|
305 |
// Get a reference to the parent object to which the joined element belongs. |
|
|
306 |
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) { |
|
|
307 |
$first = reset($this->_resultPointers); |
|
|
308 |
$parentObject = $this->_resultPointers[$parentAlias][key($first)]; |
|
|
309 |
} else if (isset($this->_resultPointers[$parentAlias])) { |
|
|
310 |
$parentObject = $this->_resultPointers[$parentAlias]; |
|
|
311 |
} else { |
|
|
312 |
// Parent object of relation not found, so skip it. |
|
|
313 |
continue; |
|
|
314 |
} |
|
|
315 |
|
|
|
316 |
$parentClass = $this->_ce[$this->_rsm->aliasMap[$parentAlias]]; |
|
|
317 |
$oid = spl_object_hash($parentObject); |
|
|
318 |
$relationField = $this->_rsm->relationMap[$dqlAlias]; |
|
|
319 |
$relation = $parentClass->associationMappings[$relationField]; |
|
|
320 |
$reflField = $parentClass->reflFields[$relationField]; |
|
|
321 |
|
|
|
322 |
// Check the type of the relation (many or single-valued) |
|
|
323 |
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { |
|
|
324 |
// PATH A: Collection-valued association |
|
|
325 |
if (isset($nonemptyComponents[$dqlAlias])) { |
|
|
326 |
$collKey = $oid . $relationField; |
|
|
327 |
if (isset($this->_initializedCollections[$collKey])) { |
|
|
328 |
$reflFieldValue = $this->_initializedCollections[$collKey]; |
|
|
329 |
} else if ( ! isset($this->_existingCollections[$collKey])) { |
|
|
330 |
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); |
|
|
331 |
} |
|
|
332 |
|
|
|
333 |
$indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); |
|
|
334 |
$index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; |
|
|
335 |
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; |
|
|
336 |
|
|
|
337 |
if ( ! $indexExists || ! $indexIsValid) { |
|
|
338 |
if (isset($this->_existingCollections[$collKey])) { |
|
|
339 |
// Collection exists, only look for the element in the identity map. |
|
|
340 |
if ($element = $this->_getEntityFromIdentityMap($entityName, $data)) { |
|
|
341 |
$this->_resultPointers[$dqlAlias] = $element; |
|
|
342 |
} else { |
|
|
343 |
unset($this->_resultPointers[$dqlAlias]); |
|
|
344 |
} |
|
|
345 |
} else { |
|
|
346 |
$element = $this->_getEntity($data, $dqlAlias); |
|
|
347 |
|
|
|
348 |
if (isset($this->_rsm->indexByMap[$dqlAlias])) { |
|
|
349 |
$field = $this->_rsm->indexByMap[$dqlAlias]; |
|
|
350 |
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element); |
|
|
351 |
$reflFieldValue->hydrateSet($indexValue, $element); |
|
|
352 |
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; |
|
|
353 |
} else { |
|
|
354 |
$reflFieldValue->hydrateAdd($element); |
|
|
355 |
$reflFieldValue->last(); |
|
|
356 |
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key(); |
|
|
357 |
} |
|
|
358 |
// Update result pointer |
|
|
359 |
$this->_resultPointers[$dqlAlias] = $element; |
|
|
360 |
} |
|
|
361 |
} else { |
|
|
362 |
// Update result pointer |
|
|
363 |
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; |
|
|
364 |
} |
|
|
365 |
} else if ( ! $reflField->getValue($parentObject)) { |
|
|
366 |
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); |
|
|
367 |
$coll->setOwner($parentObject, $relation); |
|
|
368 |
$reflField->setValue($parentObject, $coll); |
|
|
369 |
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll); |
|
|
370 |
} |
|
|
371 |
} else { |
|
|
372 |
// PATH B: Single-valued association |
|
|
373 |
$reflFieldValue = $reflField->getValue($parentObject); |
|
|
374 |
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH])) { |
|
|
375 |
if (isset($nonemptyComponents[$dqlAlias])) { |
|
|
376 |
$element = $this->_getEntity($data, $dqlAlias); |
|
|
377 |
$reflField->setValue($parentObject, $element); |
|
|
378 |
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element); |
|
|
379 |
$targetClass = $this->_ce[$relation['targetEntity']]; |
|
|
380 |
if ($relation['isOwningSide']) { |
|
|
381 |
//TODO: Just check hints['fetched'] here? |
|
|
382 |
// If there is an inverse mapping on the target class its bidirectional |
|
|
383 |
if ($relation['inversedBy']) { |
|
|
384 |
$inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']]; |
|
|
385 |
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) { |
|
|
386 |
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject); |
|
|
387 |
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject); |
|
|
388 |
} |
|
|
389 |
} else if ($parentClass === $targetClass && $relation['mappedBy']) { |
|
|
390 |
// Special case: bi-directional self-referencing one-one on the same class |
|
|
391 |
$targetClass->reflFields[$relationField]->setValue($element, $parentObject); |
|
|
392 |
} |
|
|
393 |
} else { |
|
|
394 |
// For sure bidirectional, as there is no inverse side in unidirectional mappings |
|
|
395 |
$targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject); |
|
|
396 |
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject); |
|
|
397 |
} |
|
|
398 |
// Update result pointer |
|
|
399 |
$this->_resultPointers[$dqlAlias] = $element; |
|
|
400 |
} |
|
|
401 |
// else leave $reflFieldValue null for single-valued associations |
|
|
402 |
} else { |
|
|
403 |
// Update result pointer |
|
|
404 |
$this->_resultPointers[$dqlAlias] = $reflFieldValue; |
|
|
405 |
} |
|
|
406 |
} |
|
|
407 |
} else { |
|
|
408 |
// PATH C: Its a root result element |
|
|
409 |
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias |
|
|
410 |
|
|
|
411 |
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { |
|
|
412 |
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); |
|
|
413 |
if (isset($this->_rsm->indexByMap[$dqlAlias])) { |
|
|
414 |
$field = $this->_rsm->indexByMap[$dqlAlias]; |
|
|
415 |
$key = $this->_ce[$entityName]->reflFields[$field]->getValue($element); |
|
|
416 |
if ($this->_rsm->isMixed) { |
|
|
417 |
$element = array($key => $element); |
|
|
418 |
$result[] = $element; |
|
|
419 |
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter; |
|
|
420 |
++$this->_resultCounter; |
|
|
421 |
} else { |
|
|
422 |
$result[$key] = $element; |
|
|
423 |
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key; |
|
|
424 |
} |
|
|
425 |
|
|
|
426 |
if (isset($this->_hints['collection'])) { |
|
|
427 |
$this->_hints['collection']->hydrateSet($key, $element); |
|
|
428 |
} |
|
|
429 |
} else { |
|
|
430 |
if ($this->_rsm->isMixed) { |
|
|
431 |
$element = array(0 => $element); |
|
|
432 |
} |
|
|
433 |
$result[] = $element; |
|
|
434 |
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter; |
|
|
435 |
++$this->_resultCounter; |
|
|
436 |
|
|
|
437 |
if (isset($this->_hints['collection'])) { |
|
|
438 |
$this->_hints['collection']->hydrateAdd($element); |
|
|
439 |
} |
|
|
440 |
} |
|
|
441 |
|
|
|
442 |
// Update result pointer |
|
|
443 |
$this->_resultPointers[$dqlAlias] = $element; |
|
|
444 |
|
|
|
445 |
} else { |
|
|
446 |
// Update result pointer |
|
|
447 |
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; |
|
|
448 |
$this->_resultPointers[$dqlAlias] = $result[$index]; |
|
|
449 |
/*if ($this->_rsm->isMixed) { |
|
|
450 |
$result[] = $result[$index]; |
|
|
451 |
++$this->_resultCounter; |
|
|
452 |
}*/ |
|
|
453 |
} |
|
|
454 |
} |
|
|
455 |
} |
|
|
456 |
|
|
|
457 |
// Append scalar values to mixed result sets |
|
|
458 |
if (isset($scalars)) { |
|
|
459 |
foreach ($scalars as $name => $value) { |
|
|
460 |
$result[$this->_resultCounter - 1][$name] = $value; |
|
|
461 |
} |
|
|
462 |
} |
|
|
463 |
} |
|
|
464 |
} |