Failed Conditions
Push — master ( 671fd5...a30d8d )
by Marco
21:27
created

AbstractHydrator::gatherRowData()   C

Complexity

Conditions 12
Paths 10

Size

Total Lines 66
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 12.0033

Importance

Changes 0
Metric Value
dl 0
loc 66
ccs 34
cts 35
cp 0.9714
rs 5.9123
c 0
b 0
f 0
cc 12
eloc 38
nc 10
nop 3
crap 12.0033

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
 * 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 Doctrine\DBAL\Types\Type;
23
use Doctrine\ORM\EntityManagerInterface;
24
use Doctrine\ORM\Events;
25
use Doctrine\ORM\Mapping\ClassMetadata;
26
use PDO;
27
28
/**
29
 * Base class for all hydrators. A hydrator is a class that provides some form
30
 * of transformation of an SQL result set into another structure.
31
 *
32
 * @since  2.0
33
 * @author Konsta Vesterinen <[email protected]>
34
 * @author Roman Borschel <[email protected]>
35
 * @author Guilherme Blanco <[email protected]>
36
 */
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 1019
    public function __construct(EntityManagerInterface $em)
101
    {
102 1019
        $this->_em       = $em;
103 1019
        $this->_platform = $em->getConnection()->getDatabasePlatform();
104 1019
        $this->_uow      = $em->getUnitOfWork();
105 1019
    }
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 12 View Code Duplication
    public function iterate($stmt, $resultSetMapping, array $hints = [])
117
    {
118 12
        $this->_stmt  = $stmt;
119 12
        $this->_rsm   = $resultSetMapping;
120 12
        $this->_hints = $hints;
121
122 12
        $evm = $this->_em->getEventManager();
123
124 12
        $evm->addEventListener([Events::onClear], $this);
125
126 12
        $this->prepare();
127
128 12
        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 1007 View Code Duplication
    public function hydrateAll($stmt, $resultSetMapping, array $hints = [])
141
    {
142 1007
        $this->_stmt  = $stmt;
143 1007
        $this->_rsm   = $resultSetMapping;
144 1007
        $this->_hints = $hints;
145
146 1007
        $this->_em->getEventManager()->addEventListener([Events::onClear], $this);
147
148 1007
        $this->prepare();
149
150 1006
        $result = $this->hydrateAllData();
151
152 996
        $this->cleanup();
153
154 996
        return $result;
155
    }
156
157
    /**
158
     * Hydrates a single row returned by the current statement instance during
159
     * row-by-row hydration with {@link iterate()}.
160
     *
161
     * @return mixed
162
     */
163 11 View Code Duplication
    public function hydrateRow()
164
    {
165 11
        $row = $this->_stmt->fetch(PDO::FETCH_ASSOC);
166
167 11
        if ( ! $row) {
168 8
            $this->cleanup();
169
170 8
            return false;
171
        }
172
173 10
        $result = [];
174
175 10
        $this->hydrateRowData($row, $result);
176
177 10
        return $result;
178
    }
179
180
    /**
181
     * When executed in a hydrate() loop we have to clear internal state to
182
     * decrease memory consumption.
183
     *
184
     * @param mixed $eventArgs
185
     *
186
     * @return void
187
     */
188 8
    public function onClear($eventArgs)
189
    {
190 8
    }
191
192
    /**
193
     * Executes one-time preparation tasks, once each time hydration is started
194
     * through {@link hydrateAll} or {@link iterate()}.
195
     *
196
     * @return void
197
     */
198 106
    protected function prepare()
199
    {
200 106
    }
201
202
    /**
203
     * Executes one-time cleanup tasks at the end of a hydration that was initiated
204
     * through {@link hydrateAll} or {@link iterate()}.
205
     *
206
     * @return void
207
     */
208 1004
    protected function cleanup()
209
    {
210 1004
        $this->_stmt->closeCursor();
211
212 1004
        $this->_stmt          = null;
213 1004
        $this->_rsm           = null;
214 1004
        $this->_cache         = [];
215 1004
        $this->_metadataCache = [];
216
217
        $this
218 1004
            ->_em
219 1004
            ->getEventManager()
220 1004
            ->removeEventListener([Events::onClear], $this);
221 1004
    }
222
223
    /**
224
     * Hydrates a single row from the current statement instance.
225
     *
226
     * Template method.
227
     *
228
     * @param array $data   The row data.
229
     * @param array $result The result to fill.
230
     *
231
     * @return void
232
     *
233
     * @throws HydrationException
234
     */
235
    protected function hydrateRowData(array $data, array &$result)
236
    {
237
        throw new HydrationException("hydrateRowData() not implemented by this hydrator.");
238
    }
239
240
    /**
241
     * Hydrates all rows from the current statement instance at once.
242
     *
243
     * @return array
244
     */
245
    abstract protected function hydrateAllData();
246
247
    /**
248
     * Processes a row of the result set.
249
     *
250
     * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY).
251
     * Puts the elements of a result row into a new array, grouped by the dql alias
252
     * they belong to. The column names in the result set are mapped to their
253
     * field names during this procedure as well as any necessary conversions on
254
     * the values applied. Scalar values are kept in a specific key 'scalars'.
255
     *
256
     * @param array  $data               SQL Result Row.
257
     * @param array &$id                 Dql-Alias => ID-Hash.
258
     * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
259
     *
260
     * @return array  An array with all the fields (name => value) of the data row,
261
     *                grouped by their component alias.
262
     */
263 708
    protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents)
264
    {
265 708
        $rowData = ['data' => []];
266
267 708
        foreach ($data as $key => $value) {
268 708
            if (($cacheKeyInfo = $this->hydrateColumnInfo($key)) === null) {
269 8
                continue;
270
            }
271
272 708
            $fieldName = $cacheKeyInfo['fieldName'];
273
274
            switch (true) {
275 708
                case (isset($cacheKeyInfo['isNewObjectParameter'])):
276 21
                    $argIndex = $cacheKeyInfo['argIndex'];
277 21
                    $objIndex = $cacheKeyInfo['objIndex'];
278 21
                    $type     = $cacheKeyInfo['type'];
279 21
                    $value    = $type->convertToPHPValue($value, $this->_platform);
280
281 21
                    $rowData['newObjects'][$objIndex]['class']           = $cacheKeyInfo['class'];
282 21
                    $rowData['newObjects'][$objIndex]['args'][$argIndex] = $value;
283 21
                    break;
284
285 693
                case (isset($cacheKeyInfo['isScalar'])):
286 101
                    $type  = $cacheKeyInfo['type'];
287 101
                    $value = $type->convertToPHPValue($value, $this->_platform);
288
289 101
                    $rowData['scalars'][$fieldName] = $value;
290 101
                    break;
291
292
                //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...
293
                default:
294 666
                    $dqlAlias = $cacheKeyInfo['dqlAlias'];
295 666
                    $type     = $cacheKeyInfo['type'];
296
297
                    // If there are field name collisions in the child class, then we need
298
                    // to only hydrate if we are looking at the correct discriminator value
299
                    if(
300 666
                        isset($cacheKeyInfo['discriminatorColumn']) && 
301 666
                        isset($data[$cacheKeyInfo['discriminatorColumn']]) &&
302
                        // Note: loose comparison required. See https://github.com/doctrine/doctrine2/pull/6304#issuecomment-323294442
303 666
                        $data[$cacheKeyInfo['discriminatorColumn']] != $cacheKeyInfo['discriminatorValue']
304
                    ) {
305 26
                        break;
306
                    }
307
308
                    // in an inheritance hierarchy the same field could be defined several times.
309
                    // We overwrite this value so long we don't have a non-null value, that value we keep.
310
                    // Per definition it cannot be that a field is defined several times and has several values.
311 666
                    if (isset($rowData['data'][$dqlAlias][$fieldName])) {
312
                        break;
313
                    }
314
315 666
                    $rowData['data'][$dqlAlias][$fieldName] = $type
316 666
                        ? $type->convertToPHPValue($value, $this->_platform)
317 1
                        : $value;
318
319 666
                    if ($cacheKeyInfo['isIdentifier'] && $value !== null) {
320 666
                        $id[$dqlAlias] .= '|' . $value;
321 666
                        $nonemptyComponents[$dqlAlias] = true;
322
                    }
323 708
                    break;
324
            }
325
        }
326
327 708
        return $rowData;
328
    }
329
330
    /**
331
     * Processes a row of the result set.
332
     *
333
     * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
334
     * simply converts column names to field names and properly converts the
335
     * values according to their types. The resulting row has the same number
336
     * of elements as before.
337
     *
338
     * @param array $data
339
     *
340
     * @return array The processed row.
341
     */
342 98
    protected function gatherScalarRowData(&$data)
343
    {
344 98
        $rowData = [];
345
346 98
        foreach ($data as $key => $value) {
347 98
            if (($cacheKeyInfo = $this->hydrateColumnInfo($key)) === null) {
348 1
                continue;
349
            }
350
351 98
            $fieldName = $cacheKeyInfo['fieldName'];
352
353
            // WARNING: BC break! We know this is the desired behavior to type convert values, but this
354
            // erroneous behavior exists since 2.0 and we're forced to keep compatibility.
355 98
            if ( ! isset($cacheKeyInfo['isScalar'])) {
356 49
                $dqlAlias  = $cacheKeyInfo['dqlAlias'];
357 49
                $type      = $cacheKeyInfo['type'];
358 49
                $fieldName = $dqlAlias . '_' . $fieldName;
359 49
                $value     = $type
360 49
                    ? $type->convertToPHPValue($value, $this->_platform)
361 49
                    : $value;
362
            }
363
364 98
            $rowData[$fieldName] = $value;
365
        }
366
367 98
        return $rowData;
368
    }
369
370
    /**
371
     * Retrieve column information from ResultSetMapping.
372
     *
373
     * @param string $key Column name
374
     *
375
     * @return array|null
376
     */
377 959
    protected function hydrateColumnInfo($key)
378
    {
379 959
        if (isset($this->_cache[$key])) {
380 436
            return $this->_cache[$key];
381
        }
382
383
        switch (true) {
384
            // NOTE: Most of the times it's a field mapping, so keep it first!!!
385 959
            case (isset($this->_rsm->fieldMappings[$key])):
386 894
                $classMetadata = $this->getClassMetadata($this->_rsm->declaringClasses[$key]);
387 894
                $fieldName     = $this->_rsm->fieldMappings[$key];
388 894
                $fieldMapping  = $classMetadata->fieldMappings[$fieldName];
389 894
                $ownerMap      = $this->_rsm->columnOwnerMap[$key];
390
                $columnInfo    = [
391 894
                    'isIdentifier' => \in_array($fieldName, $classMetadata->identifier, true),
392 894
                    'fieldName'    => $fieldName,
393 894
                    'type'         => Type::getType($fieldMapping['type']),
394 894
                    'dqlAlias'     => $ownerMap,
395
                ];
396
397
                // the current discriminator value must be saved in order to disambiguate fields hydration,
398
                // should there be field name collisions
399 894
                if ($classMetadata->parentClasses && isset($this->_rsm->discriminatorColumns[$ownerMap])) {
400 105
                    return $this->_cache[$key] = \array_merge(
401 105
                        $columnInfo,
402
                        [
403 105
                            'discriminatorColumn' => $this->_rsm->discriminatorColumns[$ownerMap],
404 105
                            'discriminatorValue'  => $classMetadata->discriminatorValue
405
                        ]
406
                    );
407
                }
408
409 865
                return $this->_cache[$key] = $columnInfo;
410
411 756
            case (isset($this->_rsm->newObjectMappings[$key])):
412
                // WARNING: A NEW object is also a scalar, so it must be declared before!
413 21
                $mapping = $this->_rsm->newObjectMappings[$key];
414
415 21
                return $this->_cache[$key] = [
416 21
                    'isScalar'             => true,
417
                    'isNewObjectParameter' => true,
418 21
                    'fieldName'            => $this->_rsm->scalarMappings[$key],
419 21
                    'type'                 => Type::getType($this->_rsm->typeMappings[$key]),
420 21
                    'argIndex'             => $mapping['argIndex'],
421 21
                    'objIndex'             => $mapping['objIndex'],
422 21
                    'class'                => new \ReflectionClass($mapping['className']),
423
                ];
424
425 741
            case (isset($this->_rsm->scalarMappings[$key])):
426 152
                return $this->_cache[$key] = [
427 152
                    'isScalar'  => true,
428 152
                    'fieldName' => $this->_rsm->scalarMappings[$key],
429 152
                    'type'      => Type::getType($this->_rsm->typeMappings[$key]),
430
                ];
431
432 637
            case (isset($this->_rsm->metaMappings[$key])):
433
                // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
434 632
                $fieldName = $this->_rsm->metaMappings[$key];
435 632
                $dqlAlias  = $this->_rsm->columnOwnerMap[$key];
436 632
                $type      = isset($this->_rsm->typeMappings[$key])
437 632
                    ? Type::getType($this->_rsm->typeMappings[$key])
438 632
                    : null;
439
440
                // Cache metadata fetch
441 632
                $this->getClassMetadata($this->_rsm->aliasMap[$dqlAlias]);
442
443 632
                return $this->_cache[$key] = [
444 632
                    'isIdentifier' => isset($this->_rsm->isIdentifierColumn[$dqlAlias][$key]),
445
                    'isMetaColumn' => true,
446 632
                    'fieldName'    => $fieldName,
447 632
                    'type'         => $type,
448 632
                    'dqlAlias'     => $dqlAlias,
449
                ];
450
        }
451
452
        // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
453
        // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
454 10
        return null;
455
    }
456
457
    /**
458
     * Retrieve ClassMetadata associated to entity class name.
459
     *
460
     * @param string $className
461
     *
462
     * @return \Doctrine\ORM\Mapping\ClassMetadata
463
     */
464 919
    protected function getClassMetadata($className)
465
    {
466 919
        if ( ! isset($this->_metadataCache[$className])) {
467 919
            $this->_metadataCache[$className] = $this->_em->getClassMetadata($className);
468
        }
469
470 919
        return $this->_metadataCache[$className];
471
    }
472
473
    /**
474
     * Register entity as managed in UnitOfWork.
475
     *
476
     * @param ClassMetadata $class
477
     * @param object        $entity
478
     * @param array         $data
479
     *
480
     * @return void
481
     *
482
     * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow
483
     */
484 72
    protected function registerManaged(ClassMetadata $class, $entity, array $data)
485
    {
486 72
        if ($class->isIdentifierComposite) {
487 5
            $id = [];
488
489 5 View Code Duplication
            foreach ($class->identifier as $fieldName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
490 5
                $id[$fieldName] = isset($class->associationMappings[$fieldName])
491 3
                    ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
492 5
                    : $data[$fieldName];
493
            }
494
        } else {
495 67
            $fieldName = $class->identifier[0];
496
            $id        = [
497 67
                $fieldName => isset($class->associationMappings[$fieldName])
498
                    ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
499 67
                    : $data[$fieldName]
500
            ];
501
        }
502
503 72
        $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
504 72
    }
505
}
506