1 | <?php |
||
37 | abstract class AbstractHydrator |
||
38 | { |
||
39 | /** |
||
40 | * The ResultSetMapping. |
||
41 | * |
||
42 | * @var \Doctrine\ORM\Query\ResultSetMapping |
||
43 | */ |
||
44 | protected $_rsm; |
||
45 | |||
46 | /** |
||
47 | * The EntityManager instance. |
||
48 | * |
||
49 | * @var EntityManagerInterface |
||
50 | */ |
||
51 | protected $_em; |
||
52 | |||
53 | /** |
||
54 | * The dbms Platform instance. |
||
55 | * |
||
56 | * @var \Doctrine\DBAL\Platforms\AbstractPlatform |
||
57 | */ |
||
58 | protected $_platform; |
||
59 | |||
60 | /** |
||
61 | * The UnitOfWork of the associated EntityManager. |
||
62 | * |
||
63 | * @var \Doctrine\ORM\UnitOfWork |
||
64 | */ |
||
65 | protected $_uow; |
||
66 | |||
67 | /** |
||
68 | * Local ClassMetadata cache to avoid going to the EntityManager all the time. |
||
69 | * |
||
70 | * @var array |
||
71 | */ |
||
72 | protected $_metadataCache = []; |
||
73 | |||
74 | /** |
||
75 | * The cache used during row-by-row hydration. |
||
76 | * |
||
77 | * @var array |
||
78 | */ |
||
79 | protected $_cache = []; |
||
80 | |||
81 | /** |
||
82 | * The statement that provides the data to hydrate. |
||
83 | * |
||
84 | * @var \Doctrine\DBAL\Driver\Statement |
||
85 | */ |
||
86 | protected $_stmt; |
||
87 | |||
88 | /** |
||
89 | * The query hints. |
||
90 | * |
||
91 | * @var array |
||
92 | */ |
||
93 | protected $_hints; |
||
94 | |||
95 | /** |
||
96 | * Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>. |
||
97 | * |
||
98 | * @param EntityManagerInterface $em The EntityManager to use. |
||
99 | */ |
||
100 | 976 | public function __construct(EntityManagerInterface $em) |
|
106 | |||
107 | /** |
||
108 | * Initiates a row-by-row hydration. |
||
109 | * |
||
110 | * @param object $stmt |
||
111 | * @param object $resultSetMapping |
||
112 | * @param array $hints |
||
113 | * |
||
114 | * @return IterableResult |
||
115 | */ |
||
116 | 11 | public function iterate($stmt, $resultSetMapping, array $hints = []) |
|
117 | { |
||
118 | 11 | $this->_stmt = $stmt; |
|
119 | 11 | $this->_rsm = $resultSetMapping; |
|
120 | 11 | $this->_hints = $hints; |
|
121 | |||
122 | 11 | $evm = $this->_em->getEventManager(); |
|
123 | |||
124 | 11 | $evm->addEventListener([Events::onClear], $this); |
|
125 | |||
126 | 11 | $this->prepare(); |
|
127 | |||
128 | 11 | return new IterableResult($this); |
|
129 | } |
||
130 | |||
131 | /** |
||
132 | * Hydrates all rows returned by the passed statement instance at once. |
||
133 | * |
||
134 | * @param object $stmt |
||
135 | * @param object $resultSetMapping |
||
136 | * @param array $hints |
||
137 | * |
||
138 | * @return array |
||
139 | */ |
||
140 | 965 | public function hydrateAll($stmt, $resultSetMapping, array $hints = []) |
|
154 | |||
155 | /** |
||
156 | * Hydrates a single row returned by the current statement instance during |
||
157 | * row-by-row hydration with {@link iterate()}. |
||
158 | * |
||
159 | * @return mixed |
||
160 | */ |
||
161 | 10 | public function hydrateRow() |
|
177 | |||
178 | /** |
||
179 | * When executed in a hydrate() loop we have to clear internal state to |
||
180 | * decrease memory consumption. |
||
181 | * |
||
182 | * @param mixed $eventArgs |
||
183 | * |
||
184 | * @return void |
||
185 | */ |
||
186 | 6 | public function onClear($eventArgs) |
|
189 | |||
190 | /** |
||
191 | * Executes one-time preparation tasks, once each time hydration is started |
||
192 | * through {@link hydrateAll} or {@link iterate()}. |
||
193 | * |
||
194 | * @return void |
||
195 | */ |
||
196 | 88 | protected function prepare() |
|
199 | |||
200 | /** |
||
201 | * Executes one-time cleanup tasks at the end of a hydration that was initiated |
||
202 | * through {@link hydrateAll} or {@link iterate()}. |
||
203 | * |
||
204 | * @return void |
||
205 | */ |
||
206 | 961 | protected function cleanup() |
|
215 | |||
216 | /** |
||
217 | * Hydrates a single row from the current statement instance. |
||
218 | * |
||
219 | * Template method. |
||
220 | * |
||
221 | * @param array $data The row data. |
||
222 | * @param array $result The result to fill. |
||
223 | * |
||
224 | * @return void |
||
225 | * |
||
226 | * @throws HydrationException |
||
227 | */ |
||
228 | protected function hydrateRowData(array $data, array &$result) |
||
232 | |||
233 | /** |
||
234 | * Hydrates all rows from the current statement instance at once. |
||
235 | * |
||
236 | * @return array |
||
237 | */ |
||
238 | abstract protected function hydrateAllData(); |
||
239 | |||
240 | /** |
||
241 | * Processes a row of the result set. |
||
242 | * |
||
243 | * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). |
||
244 | * Puts the elements of a result row into a new array, grouped by the dql alias |
||
245 | * they belong to. The column names in the result set are mapped to their |
||
246 | * field names during this procedure as well as any necessary conversions on |
||
247 | * the values applied. Scalar values are kept in a specific key 'scalars'. |
||
248 | * |
||
249 | * @param array $data SQL Result Row. |
||
250 | * @param array &$id Dql-Alias => ID-Hash. |
||
251 | * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value? |
||
252 | * |
||
253 | * @return array An array with all the fields (name => value) of the data row, |
||
254 | * grouped by their component alias. |
||
255 | */ |
||
256 | 678 | protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents) |
|
257 | { |
||
258 | 678 | $rowData = ['data' => []]; |
|
259 | |||
260 | 678 | foreach ($data as $key => $value) { |
|
261 | 678 | if (($cacheKeyInfo = $this->hydrateColumnInfo($key)) === null) { |
|
262 | 3 | continue; |
|
263 | } |
||
264 | |||
265 | 678 | $fieldName = $cacheKeyInfo['fieldName']; |
|
266 | |||
267 | switch (true) { |
||
268 | 678 | case (isset($cacheKeyInfo['isNewObjectParameter'])): |
|
269 | 19 | $argIndex = $cacheKeyInfo['argIndex']; |
|
270 | 19 | $objIndex = $cacheKeyInfo['objIndex']; |
|
271 | 19 | $type = $cacheKeyInfo['type']; |
|
272 | 19 | $value = $type->convertToPHPValue($value, $this->_platform); |
|
273 | |||
274 | 19 | $rowData['newObjects'][$objIndex]['class'] = $cacheKeyInfo['class']; |
|
275 | 19 | $rowData['newObjects'][$objIndex]['args'][$argIndex] = $value; |
|
276 | 19 | break; |
|
277 | |||
278 | 665 | case (isset($cacheKeyInfo['isScalar'])): |
|
279 | 89 | $type = $cacheKeyInfo['type']; |
|
280 | 89 | $value = $type->convertToPHPValue($value, $this->_platform); |
|
281 | |||
282 | 89 | $rowData['scalars'][$fieldName] = $value; |
|
283 | 89 | break; |
|
284 | |||
285 | //case (isset($cacheKeyInfo['isMetaColumn'])): |
||
286 | default: |
||
287 | 647 | $dqlAlias = $cacheKeyInfo['dqlAlias']; |
|
288 | 647 | $type = $cacheKeyInfo['type']; |
|
289 | |||
290 | // in an inheritance hierarchy the same field could be defined several times. |
||
291 | // We overwrite this value so long we don't have a non-null value, that value we keep. |
||
292 | // Per definition it cannot be that a field is defined several times and has several values. |
||
293 | 647 | if (isset($rowData['data'][$dqlAlias][$fieldName])) { |
|
294 | 3 | break; |
|
295 | } |
||
296 | |||
297 | 647 | $rowData['data'][$dqlAlias][$fieldName] = $type |
|
298 | 647 | ? $type->convertToPHPValue($value, $this->_platform) |
|
299 | 1 | : $value; |
|
300 | |||
301 | 647 | if ($cacheKeyInfo['isIdentifier'] && $value !== null) { |
|
302 | 647 | $id[$dqlAlias] .= '|' . $value; |
|
303 | 647 | $nonemptyComponents[$dqlAlias] = true; |
|
304 | } |
||
305 | 678 | break; |
|
306 | } |
||
307 | } |
||
308 | |||
309 | 678 | return $rowData; |
|
310 | } |
||
311 | |||
312 | /** |
||
313 | * Processes a row of the result set. |
||
314 | * |
||
315 | * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that |
||
316 | * simply converts column names to field names and properly converts the |
||
317 | * values according to their types. The resulting row has the same number |
||
318 | * of elements as before. |
||
319 | * |
||
320 | * @param array $data |
||
321 | * |
||
322 | * @return array The processed row. |
||
323 | */ |
||
324 | 82 | protected function gatherScalarRowData(&$data) |
|
351 | |||
352 | /** |
||
353 | * Retrieve column information from ResultSetMapping. |
||
354 | * |
||
355 | * @param string $key Column name |
||
356 | * |
||
357 | * @return array|null |
||
358 | */ |
||
359 | 919 | protected function hydrateColumnInfo($key) |
|
360 | { |
||
361 | 919 | if (isset($this->_cache[$key])) { |
|
362 | 411 | return $this->_cache[$key]; |
|
363 | } |
||
364 | |||
365 | switch (true) { |
||
366 | // NOTE: Most of the times it's a field mapping, so keep it first!!! |
||
367 | 919 | case (isset($this->_rsm->fieldMappings[$key])): |
|
368 | 875 | $classMetadata = $this->getClassMetadata($this->_rsm->declaringClasses[$key]); |
|
369 | 875 | $fieldName = $this->_rsm->fieldMappings[$key]; |
|
370 | 875 | $fieldMapping = $classMetadata->fieldMappings[$fieldName]; |
|
371 | |||
372 | 875 | return $this->_cache[$key] = [ |
|
373 | 875 | 'isIdentifier' => in_array($fieldName, $classMetadata->identifier), |
|
374 | 875 | 'fieldName' => $fieldName, |
|
375 | 875 | 'type' => Type::getType($fieldMapping['type']), |
|
376 | 875 | 'dqlAlias' => $this->_rsm->columnOwnerMap[$key], |
|
377 | ]; |
||
378 | |||
379 | 716 | case (isset($this->_rsm->newObjectMappings[$key])): |
|
380 | // WARNING: A NEW object is also a scalar, so it must be declared before! |
||
381 | 19 | $mapping = $this->_rsm->newObjectMappings[$key]; |
|
382 | |||
383 | 19 | return $this->_cache[$key] = [ |
|
384 | 19 | 'isScalar' => true, |
|
385 | 'isNewObjectParameter' => true, |
||
386 | 19 | 'fieldName' => $this->_rsm->scalarMappings[$key], |
|
387 | 19 | 'type' => Type::getType($this->_rsm->typeMappings[$key]), |
|
388 | 19 | 'argIndex' => $mapping['argIndex'], |
|
389 | 19 | 'objIndex' => $mapping['objIndex'], |
|
390 | 19 | 'class' => new \ReflectionClass($mapping['className']), |
|
391 | ]; |
||
392 | |||
393 | 703 | case (isset($this->_rsm->scalarMappings[$key])): |
|
394 | 127 | return $this->_cache[$key] = [ |
|
395 | 127 | 'isScalar' => true, |
|
396 | 127 | 'fieldName' => $this->_rsm->scalarMappings[$key], |
|
397 | 127 | 'type' => Type::getType($this->_rsm->typeMappings[$key]), |
|
398 | ]; |
||
399 | |||
400 | 621 | case (isset($this->_rsm->metaMappings[$key])): |
|
401 | // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). |
||
402 | 616 | $fieldName = $this->_rsm->metaMappings[$key]; |
|
403 | 616 | $dqlAlias = $this->_rsm->columnOwnerMap[$key]; |
|
404 | 616 | $type = isset($this->_rsm->typeMappings[$key]) |
|
405 | 616 | ? Type::getType($this->_rsm->typeMappings[$key]) |
|
406 | 616 | : null; |
|
407 | |||
408 | // Cache metadata fetch |
||
409 | 616 | $this->getClassMetadata($this->_rsm->aliasMap[$dqlAlias]); |
|
410 | |||
411 | 616 | return $this->_cache[$key] = [ |
|
412 | 616 | 'isIdentifier' => isset($this->_rsm->isIdentifierColumn[$dqlAlias][$key]), |
|
413 | 'isMetaColumn' => true, |
||
414 | 616 | 'fieldName' => $fieldName, |
|
415 | 616 | 'type' => $type, |
|
416 | 616 | 'dqlAlias' => $dqlAlias, |
|
417 | ]; |
||
418 | } |
||
419 | |||
420 | // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 |
||
421 | // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. |
||
422 | 5 | return null; |
|
423 | } |
||
424 | |||
425 | /** |
||
426 | * Retrieve ClassMetadata associated to entity class name. |
||
427 | * |
||
428 | * @param string $className |
||
429 | * |
||
430 | * @return \Doctrine\ORM\Mapping\ClassMetadata |
||
431 | */ |
||
432 | 900 | protected function getClassMetadata($className) |
|
440 | |||
441 | /** |
||
442 | * Register entity as managed in UnitOfWork. |
||
443 | * |
||
444 | * @param ClassMetadata $class |
||
445 | * @param object $entity |
||
446 | * @param array $data |
||
447 | * |
||
448 | * @return void |
||
449 | * |
||
450 | * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow |
||
451 | */ |
||
452 | 72 | protected function registerManaged(ClassMetadata $class, $entity, array $data) |
|
473 | } |
||
474 |