Failed Conditions
Push — master ( 8be1e3...e3936d )
by Marco
14s
created

AbstractHydrator::gatherRowData()   C

Complexity

Conditions 11
Paths 10

Size

Total Lines 64
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 11.0225

Importance

Changes 0
Metric Value
cc 11
eloc 36
nc 10
nop 3
dl 0
loc 64
ccs 33
cts 35
cp 0.9429
crap 11.0225
rs 6.0563
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Internal\Hydration;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use Doctrine\ORM\Events;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
use PDO;
11
12
/**
13
 * Base class for all hydrators. A hydrator is a class that provides some form
14
 * of transformation of an SQL result set into another structure.
15
 */
16
abstract class AbstractHydrator
17
{
18
    /**
19
     * The ResultSetMapping.
20
     *
21
     * @var \Doctrine\ORM\Query\ResultSetMapping
22
     */
23
    protected $rsm;
24
25
    /**
26
     * The EntityManager instance.
27
     *
28
     * @var EntityManagerInterface
29
     */
30
    protected $em;
31
32
    /**
33
     * The dbms Platform instance.
34
     *
35
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
36
     */
37
    protected $platform;
38
39
    /**
40
     * The UnitOfWork of the associated EntityManager.
41
     *
42
     * @var \Doctrine\ORM\UnitOfWork
43
     */
44
    protected $uow;
45
46
    /**
47
     * Local ClassMetadata cache to avoid going to the EntityManager all the time.
48
     *
49
     * @var ClassMetadata[]
50
     */
51
    protected $metadataCache = [];
52
53
    /**
54
     * The cache used during row-by-row hydration.
55
     *
56
     * @var mixed[][]
57
     */
58
    protected $cache = [];
59
60
    /**
61
     * The statement that provides the data to hydrate.
62
     *
63
     * @var \Doctrine\DBAL\Driver\Statement
64
     */
65
    protected $stmt;
66
67
    /**
68
     * The query hints.
69
     *
70
     * @var mixed[]
71
     */
72
    protected $hints;
73
74
    /**
75
     * Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
76
     *
77
     * @param EntityManagerInterface $em The EntityManager to use.
78
     */
79 991
    public function __construct(EntityManagerInterface $em)
80
    {
81 991
        $this->em       = $em;
82 991
        $this->platform = $em->getConnection()->getDatabasePlatform();
83 991
        $this->uow      = $em->getUnitOfWork();
84 991
    }
85
86
    /**
87
     * Initiates a row-by-row hydration.
88
     *
89
     * @param object  $stmt
90
     * @param object  $resultSetMapping
91
     * @param mixed[] $hints
92
     *
93
     * @return IterableResult
94
     */
95 12
    public function iterate($stmt, $resultSetMapping, array $hints = [])
96
    {
97 12
        $this->stmt  = $stmt;
98 12
        $this->rsm   = $resultSetMapping;
99 12
        $this->hints = $hints;
100
101 12
        $evm = $this->em->getEventManager();
102
103 12
        $evm->addEventListener([Events::onClear], $this);
104
105 12
        $this->prepare();
106
107 12
        return new IterableResult($this);
108
    }
109
110
    /**
111
     * Hydrates all rows returned by the passed statement instance at once.
112
     *
113
     * @param object  $stmt
114
     * @param object  $resultSetMapping
115
     * @param mixed[] $hints
116
     *
117
     * @return mixed[]
118
     */
119 979
    public function hydrateAll($stmt, $resultSetMapping, array $hints = [])
120
    {
121 979
        $this->stmt  = $stmt;
122 979
        $this->rsm   = $resultSetMapping;
123 979
        $this->hints = $hints;
124
125 979
        $this->em->getEventManager()->addEventListener([Events::onClear], $this);
126
127 979
        $this->prepare();
128
129 978
        $result = $this->hydrateAllData();
130
131 968
        $this->cleanup();
132
133 968
        return $result;
134
    }
135
136
    /**
137
     * Hydrates a single row returned by the current statement instance during
138
     * row-by-row hydration with {@link iterate()}.
139
     *
140
     * @return mixed
141
     */
142 11
    public function hydrateRow()
143
    {
144 11
        $row = $this->stmt->fetch(PDO::FETCH_ASSOC);
145
146 11
        if (! $row) {
147 8
            $this->cleanup();
148
149 8
            return false;
150
        }
151
152 10
        $result = [];
153
154 10
        $this->hydrateRowData($row, $result);
155
156 10
        return $result;
157
    }
158
159
    /**
160
     * When executed in a hydrate() loop we have to clear internal state to
161
     * decrease memory consumption.
162
     *
163
     * @param mixed $eventArgs
164
     */
165 8
    public function onClear($eventArgs)
166
    {
167 8
    }
168
169
    /**
170
     * Executes one-time preparation tasks, once each time hydration is started
171
     * through {@link hydrateAll} or {@link iterate()}.
172
     */
173 106
    protected function prepare()
174
    {
175 106
    }
176
177
    /**
178
     * Executes one-time cleanup tasks at the end of a hydration that was initiated
179
     * through {@link hydrateAll} or {@link iterate()}.
180
     */
181 976
    protected function cleanup()
182
    {
183 976
        $this->stmt->closeCursor();
184
185 976
        $this->stmt          = null;
186 976
        $this->rsm           = null;
187 976
        $this->cache         = [];
188 976
        $this->metadataCache = [];
189
190 976
        $this->em
191 976
             ->getEventManager()
192 976
             ->removeEventListener([Events::onClear], $this);
193 976
    }
194
195
    /**
196
     * Hydrates a single row from the current statement instance.
197
     *
198
     * Template method.
199
     *
200
     * @param mixed[] $data   The row data.
201
     * @param mixed[] $result The result to fill.
202
     *
203
     * @throws HydrationException
204
     */
205
    protected function hydrateRowData(array $data, array &$result)
206
    {
207
        throw new HydrationException('hydrateRowData() not implemented by this hydrator.');
208
    }
209
210
    /**
211
     * Hydrates all rows from the current statement instance at once.
212
     *
213
     * @return mixed[]
214
     */
215
    abstract protected function hydrateAllData();
216
217
    /**
218
     * Processes a row of the result set.
219
     *
220
     * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY).
221
     * Puts the elements of a result row into a new array, grouped by the dql alias
222
     * they belong to. The column names in the result set are mapped to their
223
     * field names during this procedure as well as any necessary conversions on
224
     * the values applied. Scalar values are kept in a specific key 'scalars'.
225
     *
226
     * @param mixed[] $data                SQL Result Row.
227
     * @param mixed[] &$id                 Dql-Alias => ID-Hash.
228
     * @param mixed[] &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
229
     *
230
     * @return mixed[] An array with all the fields (name => value) of the data row,
231
     *                grouped by their component alias.
232
     */
233 702
    protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents)
234
    {
235 702
        $rowData = ['data' => []];
236
237 702
        foreach ($data as $key => $value) {
238 702
            $cacheKeyInfo = $this->hydrateColumnInfo($key);
239 702
            if ($cacheKeyInfo === null) {
240 8
                continue;
241
            }
242
243 702
            $fieldName = $cacheKeyInfo['fieldName'];
244
245
            switch (true) {
246 702
                case (isset($cacheKeyInfo['isNewObjectParameter'])):
247 21
                    $argIndex = $cacheKeyInfo['argIndex'];
248 21
                    $objIndex = $cacheKeyInfo['objIndex'];
249 21
                    $type     = $cacheKeyInfo['type'];
250 21
                    $value    = $type->convertToPHPValue($value, $this->platform);
251
252 21
                    $rowData['newObjects'][$objIndex]['class']           = $cacheKeyInfo['class'];
253 21
                    $rowData['newObjects'][$objIndex]['args'][$argIndex] = $value;
254 21
                    break;
255
256 687
                case (isset($cacheKeyInfo['isScalar'])):
257 110
                    $type  = $cacheKeyInfo['type'];
258 110
                    $value = $type->convertToPHPValue($value, $this->platform);
259
260 110
                    $rowData['scalars'][$fieldName] = $value;
261 110
                    break;
262
263
                //case (isset($cacheKeyInfo['isMetaColumn'])):
0 ignored issues
show
Unused Code Comprehensibility introduced by
92% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
264
                default:
265 651
                    $dqlAlias = $cacheKeyInfo['dqlAlias'];
266 651
                    $type     = $cacheKeyInfo['type'];
267
268
                    // If there are field name collisions in the child class, then we need
269
                    // to only hydrate if we are looking at the correct discriminator value
270 651
                    if (isset($cacheKeyInfo['discriminatorColumn'], $data[$cacheKeyInfo['discriminatorColumn']]) &&
271
                        // Note: loose comparison required. See https://github.com/doctrine/doctrine2/pull/6304#issuecomment-323294442
272 651
                        $data[$cacheKeyInfo['discriminatorColumn']] != $cacheKeyInfo['discriminatorValue'] // TODO get rid of loose comparison
273
                    ) {
274 26
                        break;
275
                    }
276
277
                    // in an inheritance hierarchy the same field could be defined several times.
278
                    // We overwrite this value so long we don't have a non-null value, that value we keep.
279
                    // Per definition it cannot be that a field is defined several times and has several values.
280 651
                    if (isset($rowData['data'][$dqlAlias][$fieldName])) {
281
                        break;
282
                    }
283
284 651
                    $rowData['data'][$dqlAlias][$fieldName] = $type
285 651
                        ? $type->convertToPHPValue($value, $this->platform)
286
                        : $value;
287
288 651
                    if ($cacheKeyInfo['isIdentifier'] && $value !== null) {
289 651
                        $id[$dqlAlias]                .= '|' . $value;
290 651
                        $nonemptyComponents[$dqlAlias] = true;
291
                    }
292 702
                    break;
293
            }
294
        }
295
296 702
        return $rowData;
297
    }
298
299
    /**
300
     * Processes a row of the result set.
301
     *
302
     * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
303
     * simply converts column names to field names and properly converts the
304
     * values according to their types. The resulting row has the same number
305
     * of elements as before.
306
     *
307
     * @param mixed[] $data
308
     *
309
     * @return mixed[] The processed row.
310
     */
311 98
    protected function gatherScalarRowData(&$data)
312
    {
313 98
        $rowData = [];
314
315 98
        foreach ($data as $key => $value) {
316 98
            $cacheKeyInfo = $this->hydrateColumnInfo($key);
317 98
            if ($cacheKeyInfo === null) {
318 1
                continue;
319
            }
320
321 98
            $fieldName = $cacheKeyInfo['fieldName'];
322
323
            // WARNING: BC break! We know this is the desired behavior to type convert values, but this
324
            // erroneous behavior exists since 2.0 and we're forced to keep compatibility.
325 98
            if (! isset($cacheKeyInfo['isScalar'])) {
326 49
                $dqlAlias  = $cacheKeyInfo['dqlAlias'];
327 49
                $type      = $cacheKeyInfo['type'];
328 49
                $fieldName = $dqlAlias . '_' . $fieldName;
329 49
                $value     = $type
330 49
                    ? $type->convertToPHPValue($value, $this->platform)
331 49
                    : $value;
332
            }
333
334 98
            $rowData[$fieldName] = $value;
335
        }
336
337 98
        return $rowData;
338
    }
339
340
    /**
341
     * Retrieve column information from ResultSetMapping.
342
     *
343
     * @param string $key Column name
344
     *
345
     * @return mixed[]|null
346
     */
347 934
    protected function hydrateColumnInfo($key)
348
    {
349 934
        if (isset($this->cache[$key])) {
350 429
            return $this->cache[$key];
351
        }
352
353
        switch (true) {
354
            // NOTE: Most of the times it's a field mapping, so keep it first!!!
355 934
            case (isset($this->rsm->fieldMappings[$key])):
356 858
                $classMetadata = $this->getClassMetadata($this->rsm->declaringClasses[$key]);
357 858
                $fieldName     = $this->rsm->fieldMappings[$key];
358 858
                $ownerMap      = $this->rsm->columnOwnerMap[$key];
359 858
                $property      = $classMetadata->getProperty($fieldName);
360
361
                $columnInfo = [
362 858
                    'isIdentifier' => $property->isPrimaryKey(),
363 858
                    'fieldName'    => $fieldName,
364 858
                    'type'         => $property->getType(),
0 ignored issues
show
Bug introduced by
The method getType() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

364
                    'type'         => $property->/** @scrutinizer ignore-call */ getType(),
Loading history...
365 858
                    'dqlAlias'     => $this->rsm->columnOwnerMap[$key],
366
                ];
367
368
                // the current discriminator value must be saved in order to disambiguate fields hydration,
369
                // should there be field name collisions
370 858
                if ($classMetadata->getParent() && isset($this->rsm->discriminatorColumns[$ownerMap])) {
371 106
                    return $this->cache[$key] = \array_merge(
372 106
                        $columnInfo,
373
                        [
374 106
                            'discriminatorColumn' => $this->rsm->discriminatorColumns[$ownerMap],
375 106
                            'discriminatorValue'  => $classMetadata->discriminatorValue,
376
                        ]
377
                    );
378
                }
379
380 828
                return $this->cache[$key] = $columnInfo;
381
382 744
            case (isset($this->rsm->newObjectMappings[$key])):
383
                // WARNING: A NEW object is also a scalar, so it must be declared before!
384 21
                $mapping = $this->rsm->newObjectMappings[$key];
385
386 21
                return $this->cache[$key] = [
387 21
                    'isScalar'             => true,
388
                    'isNewObjectParameter' => true,
389 21
                    'fieldName'            => $this->rsm->scalarMappings[$key],
390 21
                    'type'                 => $this->rsm->typeMappings[$key],
391 21
                    'argIndex'             => $mapping['argIndex'],
392 21
                    'objIndex'             => $mapping['objIndex'],
393 21
                    'class'                => new \ReflectionClass($mapping['className']),
394
                ];
395
396 729
            case (isset($this->rsm->scalarMappings[$key])):
397 161
                return $this->cache[$key] = [
398 161
                    'isScalar'  => true,
399 161
                    'fieldName' => $this->rsm->scalarMappings[$key],
400 161
                    'type'      => $this->rsm->typeMappings[$key],
401
                ];
402
403 615
            case (isset($this->rsm->metaMappings[$key])):
404
                // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
405 610
                $fieldName = $this->rsm->metaMappings[$key];
406 610
                $dqlAlias  = $this->rsm->columnOwnerMap[$key];
407
408
                // Cache metadata fetch
409 610
                $this->getClassMetadata($this->rsm->aliasMap[$dqlAlias]);
410
411 610
                return $this->cache[$key] = [
412 610
                    'isIdentifier' => isset($this->rsm->isIdentifierColumn[$dqlAlias][$key]),
413
                    'isMetaColumn' => true,
414 610
                    'fieldName'    => $fieldName,
415 610
                    'type'         => $this->rsm->typeMappings[$key],
416 610
                    '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 10
        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 879
    protected function getClassMetadata($className)
433
    {
434 879
        if (! isset($this->metadataCache[$className])) {
435 879
            $this->metadataCache[$className] = $this->em->getClassMetadata($className);
436
        }
437
438 879
        return $this->metadataCache[$className];
439
    }
440
}
441