Passed
Pull Request — 2.6 (#7821)
by Marco
11:52
created

ResultSetMapping   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 584
Duplicated Lines 0 %

Test Coverage

Coverage 88.3%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 39
eloc 85
c 1
b 0
f 0
dl 0
loc 584
ccs 83
cts 94
cp 0.883
rs 9.28

26 Methods

Rating   Name   Duplication   Size   Complexity  
A addMetadataParameterMapping() 0 3 1
A isFieldResult() 0 3 1
A getAliasMap() 0 3 1
A isScalarResult() 0 3 1
A hasParentAlias() 0 3 1
A addEntityResult() 0 10 2
A getDeclaringClass() 0 3 1
A getParentAlias() 0 3 1
A addMetaResult() 0 14 3
A getClassName() 0 3 1
A addJoinedEntityResult() 0 7 1
A hasIndexBy() 0 3 1
A addScalarResult() 0 10 3
A getEntityResultCount() 0 3 1
A addIndexBy() 0 26 4
A addIndexByScalar() 0 5 1
A isRelation() 0 3 1
A getEntityAlias() 0 3 1
A getScalarAlias() 0 3 1
A isMixedResult() 0 3 1
A setDiscriminatorColumn() 0 6 1
A addFieldResult() 0 14 4
A getRelation() 0 3 1
A getFieldName() 0 3 1
A addIndexByColumn() 0 5 1
A getTypeOfSelectionRootSingleIdentifierColumn() 0 23 3
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\Query;
21
22
use Doctrine\ORM\EntityManagerInterface;
23
use Doctrine\ORM\Mapping\MappingException;
24
use function array_keys;
25
use function array_values;
26
use function assert;
27
28
/**
29
 * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result.
30
 *
31
 * IMPORTANT NOTE:
32
 * The properties of this class are only public for fast internal READ access and to (drastically)
33
 * reduce the size of serialized instances for more effective caching due to better (un-)serialization
34
 * performance.
35
 *
36
 * <b>Users should use the public methods.</b>
37
 *
38
 * @author Roman Borschel <[email protected]>
39
 * @since 2.0
40
 * @todo Think about whether the number of lookup maps can be reduced.
41
 */
42
class ResultSetMapping
43
{
44
    /**
45
     * Whether the result is mixed (contains scalar values together with field values).
46
     *
47
     * @ignore
48
     * @var boolean
49
     */
50
    public $isMixed = false;
51
52
    /**
53
     * Whether the result is a select statement.
54
     *
55
     * @ignore
56
     * @var boolean
57
     */
58
    public $isSelect = true;
59
60
    /**
61
     * Maps alias names to class names.
62
     *
63
     * @ignore
64
     * @var array
65
     */
66
    public $aliasMap = [];
67
68
    /**
69
     * Maps alias names to related association field names.
70
     *
71
     * @ignore
72
     * @var array
73
     */
74
    public $relationMap = [];
75
76
    /**
77
     * Maps alias names to parent alias names.
78
     *
79
     * @ignore
80
     * @var array
81
     */
82
    public $parentAliasMap = [];
83
84
    /**
85
     * Maps column names in the result set to field names for each class.
86
     *
87
     * @ignore
88
     * @var array
89
     */
90
    public $fieldMappings = [];
91
92
    /**
93
     * Maps column names in the result set to the alias/field name to use in the mapped result.
94
     *
95
     * @ignore
96
     * @var array
97
     */
98
    public $scalarMappings = [];
99
100
    /**
101
     * Maps column names in the result set to the alias/field type to use in the mapped result.
102
     *
103
     * @ignore
104
     * @var array
105
     */
106
    public $typeMappings = [];
107
108
    /**
109
     * Maps entities in the result set to the alias name to use in the mapped result.
110
     *
111
     * @ignore
112
     * @var array
113
     */
114
    public $entityMappings = [];
115
116
    /**
117
     * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
118
     *
119
     * @ignore
120
     * @var array
121
     */
122
    public $metaMappings = [];
123
124
    /**
125
     * Maps column names in the result set to the alias they belong to.
126
     *
127
     * @ignore
128
     * @var array
129
     */
130
    public $columnOwnerMap = [];
131
132
    /**
133
     * List of columns in the result set that are used as discriminator columns.
134
     *
135
     * @ignore
136
     * @var array
137
     */
138
    public $discriminatorColumns = [];
139
140
    /**
141
     * Maps alias names to field names that should be used for indexing.
142
     *
143
     * @ignore
144
     * @var array
145
     */
146
    public $indexByMap = [];
147
148
    /**
149
     * Map from column names to class names that declare the field the column is mapped to.
150
     *
151
     * @ignore
152
     * @var array
153
     */
154
    public $declaringClasses = [];
155
156
    /**
157
     * This is necessary to hydrate derivate foreign keys correctly.
158
     *
159
     * @var array
160
     */
161
    public $isIdentifierColumn = [];
162
163
    /**
164
     * Maps column names in the result set to field names for each new object expression.
165
     *
166
     * @var array
167
     */
168
    public $newObjectMappings = [];
169
170
    /**
171
     * Maps metadata parameter names to the metadata attribute.
172
     *
173
     * @var array
174
     */
175
    public $metadataParameterMapping = [];
176
177
    /**
178
     * Contains query parameter names to be resolved as discriminator values
179
     *
180
     * @var array
181
     */
182
    public $discriminatorParameters = [];
183
184
    /**
185
     * Adds an entity result to this ResultSetMapping.
186
     *
187
     * @param string      $class       The class name of the entity.
188
     * @param string      $alias       The alias for the class. The alias must be unique among all entity
189
     *                                 results or joined entity results within this ResultSetMapping.
190
     * @param string|null $resultAlias The result alias with which the entity result should be
191
     *                                 placed in the result structure.
192
     *
193
     * @return ResultSetMapping This ResultSetMapping instance.
194
     *
195
     * @todo Rename: addRootEntity
196
     */
197 1165
    public function addEntityResult($class, $alias, $resultAlias = null)
198
    {
199 1165
        $this->aliasMap[$alias] = $class;
200 1165
        $this->entityMappings[$alias] = $resultAlias;
201
202 1165
        if ($resultAlias !== null) {
203 45
            $this->isMixed = true;
204
        }
205
206 1165
        return $this;
207
    }
208
209
    /**
210
     * Sets a discriminator column for an entity result or joined entity result.
211
     * The discriminator column will be used to determine the concrete class name to
212
     * instantiate.
213
     *
214
     * @param string $alias       The alias of the entity result or joined entity result the discriminator
215
     *                            column should be used for.
216
     * @param string $discrColumn The name of the discriminator column in the SQL result set.
217
     *
218
     * @return ResultSetMapping This ResultSetMapping instance.
219
     *
220
     * @todo Rename: addDiscriminatorColumn
221
     */
222 180
    public function setDiscriminatorColumn($alias, $discrColumn)
223
    {
224 180
        $this->discriminatorColumns[$alias] = $discrColumn;
225 180
        $this->columnOwnerMap[$discrColumn] = $alias;
226
227 180
        return $this;
228
    }
229
230
    /**
231
     * Sets a field to use for indexing an entity result or joined entity result.
232
     *
233
     * @param string $alias     The alias of an entity result or joined entity result.
234
     * @param string $fieldName The name of the field to use for indexing.
235
     *
236
     * @return ResultSetMapping This ResultSetMapping instance.
237
     */
238 43
    public function addIndexBy($alias, $fieldName)
239
    {
240 43
        $found = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $found is dead and can be removed.
Loading history...
241
242 43
        foreach (array_merge($this->metaMappings, $this->fieldMappings) as $columnName => $columnFieldName) {
243 43
            if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue;
244
245 42
            $this->addIndexByColumn($alias, $columnName);
246 42
            $found = true;
247
248 42
            break;
249
        }
250
251
        /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals
252
        if ( ! $found) {
253
            $message = sprintf(
254
                'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.',
255
                $alias,
256
                $fieldName
257
            );
258
259
            throw new \LogicException($message);
260
        }
261
        */
262
263 43
        return $this;
264
    }
265
266
    /**
267
     * Sets to index by a scalar result column name.
268
     *
269
     * @param string $resultColumnName
270
     *
271
     * @return ResultSetMapping This ResultSetMapping instance.
272
     */
273 3
    public function addIndexByScalar($resultColumnName)
274
    {
275 3
        $this->indexByMap['scalars'] = $resultColumnName;
276
277 3
        return $this;
278
    }
279
280
    /**
281
     * Sets a column to use for indexing an entity or joined entity result by the given alias name.
282
     *
283
     * @param string $alias
284
     * @param string $resultColumnName
285
     *
286
     * @return ResultSetMapping This ResultSetMapping instance.
287
     */
288 43
    public function addIndexByColumn($alias, $resultColumnName)
289
    {
290 43
        $this->indexByMap[$alias] = $resultColumnName;
291
292 43
        return $this;
293
    }
294
295
    /**
296
     * Checks whether an entity result or joined entity result with a given alias has
297
     * a field set for indexing.
298
     *
299
     * @param string $alias
300
     *
301
     * @return boolean
302
     *
303
     * @todo Rename: isIndexed($alias)
304
     */
305 2
    public function hasIndexBy($alias)
306
    {
307 2
        return isset($this->indexByMap[$alias]);
308
    }
309
310
    /**
311
     * Checks whether the column with the given name is mapped as a field result
312
     * as part of an entity result or joined entity result.
313
     *
314
     * @param string $columnName The name of the column in the SQL result set.
315
     *
316
     * @return boolean
317
     *
318
     * @todo Rename: isField
319
     */
320 1
    public function isFieldResult($columnName)
321
    {
322 1
        return isset($this->fieldMappings[$columnName]);
323
    }
324
325
    /**
326
     * Adds a field to the result that belongs to an entity or joined entity.
327
     *
328
     * @param string      $alias          The alias of the root entity or joined entity to which the field belongs.
329
     * @param string      $columnName     The name of the column in the SQL result set.
330
     * @param string      $fieldName      The name of the field on the declaring class.
331
     * @param string|null $declaringClass The name of the class that declares/owns the specified field.
332
     *                                    When $alias refers to a superclass in a mapped hierarchy but
333
     *                                    the field $fieldName is defined on a subclass, specify that here.
334
     *                                    If not specified, the field is assumed to belong to the class
335
     *                                    designated by $alias.
336
     *
337
     * @return ResultSetMapping This ResultSetMapping instance.
338
     *
339
     * @todo Rename: addField
340
     */
341 1149
    public function addFieldResult($alias, $columnName, $fieldName, $declaringClass = null)
342
    {
343
        // column name (in result set) => field name
344 1149
        $this->fieldMappings[$columnName] = $fieldName;
345
        // column name => alias of owner
346 1149
        $this->columnOwnerMap[$columnName] = $alias;
347
        // field name => class name of declaring class
348 1149
        $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
349
350 1149
        if ( ! $this->isMixed && $this->scalarMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->scalarMappings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
351 10
            $this->isMixed = true;
352
        }
353
354 1149
        return $this;
355
    }
356
357
    /**
358
     * Adds a joined entity result.
359
     *
360
     * @param string $class       The class name of the joined entity.
361
     * @param string $alias       The unique alias to use for the joined entity.
362
     * @param string $parentAlias The alias of the entity result that is the parent of this joined result.
363
     * @param string $relation    The association field that connects the parent entity result
364
     *                            with the joined entity result.
365
     *
366
     * @return ResultSetMapping This ResultSetMapping instance.
367
     *
368
     * @todo Rename: addJoinedEntity
369
     */
370 389
    public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
371
    {
372 389
        $this->aliasMap[$alias]       = $class;
373 389
        $this->parentAliasMap[$alias] = $parentAlias;
374 389
        $this->relationMap[$alias]    = $relation;
375
376 389
        return $this;
377
    }
378
379
    /**
380
     * Adds a scalar result mapping.
381
     *
382
     * @param string $columnName The name of the column in the SQL result set.
383
     * @param string $alias      The result alias with which the scalar result should be placed in the result structure.
384
     * @param string $type       The column type
385
     *
386
     * @return ResultSetMapping This ResultSetMapping instance.
387
     *
388
     * @todo Rename: addScalar
389
     */
390 352
    public function addScalarResult($columnName, $alias, $type = 'string')
391
    {
392 352
        $this->scalarMappings[$columnName] = $alias;
393 352
        $this->typeMappings[$columnName]   = $type;
394
395 352
        if ( ! $this->isMixed && $this->fieldMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fieldMappings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
396 122
            $this->isMixed = true;
397
        }
398
399 352
        return $this;
400
    }
401
402
    /**
403
     * Adds a metadata parameter mappings.
404
     *
405
     * @param mixed  $parameter The parameter name in the SQL result set.
406
     * @param string $attribute The metadata attribute.
407
     */
408
    public function addMetadataParameterMapping($parameter, $attribute)
409
    {
410
        $this->metadataParameterMapping[$parameter] = $attribute;
411
    }
412
413
    /**
414
     * Checks whether a column with a given name is mapped as a scalar result.
415
     *
416
     * @param string $columnName The name of the column in the SQL result set.
417
     *
418
     * @return boolean
419
     *
420
     * @todo Rename: isScalar
421
     */
422 2
    public function isScalarResult($columnName)
423
    {
424 2
        return isset($this->scalarMappings[$columnName]);
425
    }
426
427
    /**
428
     * Gets the name of the class of an entity result or joined entity result,
429
     * identified by the given unique alias.
430
     *
431
     * @param string $alias
432
     *
433
     * @return string
434
     */
435 5
    public function getClassName($alias)
436
    {
437 5
        return $this->aliasMap[$alias];
438
    }
439
440
    /**
441
     * Gets the field alias for a column that is mapped as a scalar value.
442
     *
443
     * @param string $columnName The name of the column in the SQL result set.
444
     *
445
     * @return string
446
     */
447 2
    public function getScalarAlias($columnName)
448
    {
449 2
        return $this->scalarMappings[$columnName];
450
    }
451
452
    /**
453
     * Gets the name of the class that owns a field mapping for the specified column.
454
     *
455
     * @param string $columnName
456
     *
457
     * @return string
458
     */
459 4
    public function getDeclaringClass($columnName)
460
    {
461 4
        return $this->declaringClasses[$columnName];
462
    }
463
464
    /**
465
     * @param string $alias
466
     *
467
     * @return string
468
     */
469
    public function getRelation($alias)
470
    {
471
        return $this->relationMap[$alias];
472
    }
473
474
    /**
475
     * @param string $alias
476
     *
477
     * @return boolean
478
     */
479 1
    public function isRelation($alias)
480
    {
481 1
        return isset($this->relationMap[$alias]);
482
    }
483
484
    /**
485
     * Gets the alias of the class that owns a field mapping for the specified column.
486
     *
487
     * @param string $columnName
488
     *
489
     * @return string
490
     */
491 4
    public function getEntityAlias($columnName)
492
    {
493 4
        return $this->columnOwnerMap[$columnName];
494
    }
495
496
    /**
497
     * Gets the parent alias of the given alias.
498
     *
499
     * @param string $alias
500
     *
501
     * @return string
502
     */
503
    public function getParentAlias($alias)
504
    {
505
        return $this->parentAliasMap[$alias];
506
    }
507
508
    /**
509
     * Checks whether the given alias has a parent alias.
510
     *
511
     * @param string $alias
512
     *
513
     * @return boolean
514
     */
515 1
    public function hasParentAlias($alias)
516
    {
517 1
        return isset($this->parentAliasMap[$alias]);
518
    }
519
520
    /**
521
     * Gets the field name for a column name.
522
     *
523
     * @param string $columnName
524
     *
525
     * @return string
526
     */
527 1
    public function getFieldName($columnName)
528
    {
529 1
        return $this->fieldMappings[$columnName];
530
    }
531
532
    /**
533
     * @return array
534
     */
535
    public function getAliasMap()
536
    {
537
        return $this->aliasMap;
538
    }
539
540
    /**
541
     * Gets the number of different entities that appear in the mapped result.
542
     *
543
     * @return integer
544
     */
545
    public function getEntityResultCount()
546
    {
547
        return count($this->aliasMap);
548
    }
549
550
    /**
551
     * Checks whether this ResultSetMapping defines a mixed result.
552
     *
553
     * Mixed results can only occur in object and array (graph) hydration. In such a
554
     * case a mixed result means that scalar values are mixed with objects/array in
555
     * the result.
556
     *
557
     * @return boolean
558
     */
559 1
    public function isMixedResult()
560
    {
561 1
        return $this->isMixed;
562
    }
563
564
    /**
565
     * Adds a meta column (foreign key or discriminator column) to the result set.
566
     *
567
     * @param string $alias              The result alias with which the meta result should be placed in the result structure.
568
     * @param string $columnName         The name of the column in the SQL result set.
569
     * @param string $fieldName          The name of the field on the declaring class.
570
     * @param bool   $isIdentifierColumn
571
     * @param string $type               The column type
572
     *
573
     * @return ResultSetMapping This ResultSetMapping instance.
574
     *
575
     * @todo Make all methods of this class require all parameters and not infer anything
576
     */
577 774
    public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false, $type = null)
578
    {
579 774
        $this->metaMappings[$columnName] = $fieldName;
580 774
        $this->columnOwnerMap[$columnName] = $alias;
581
582 774
        if ($isIdentifierColumn) {
583 70
            $this->isIdentifierColumn[$alias][$columnName] = true;
584
        }
585
586 774
        if ($type) {
587 773
            $this->typeMappings[$columnName] = $type;
588
        }
589
590 774
        return $this;
591
    }
592
593
    /**
594
     * Retrieves the DBAL type name for the single identifier column of the root of a selection.
595
     * Composite identifiers not supported!
596
     *
597
     * @internal only to be used by ORM internals: do not use in downstream projects! This API is a minimal abstraction
598
     *           that only ORM internals need, and it tries to make sense of the very complex and squishy array-alike
599
     *           structure inside this class. Some assumptions are coded in here, so here be dragons.
600
     * @throws MappingException If the identifier is not a single field, or if metadata for its
601
     *                          owner is incorrect/missing.
602
     */
603 69
    final public function getTypeOfSelectionRootSingleIdentifierColumn(EntityManagerInterface $em) : string
604
    {
605 69
        assert($this->isSelect);
606
607 69
        if ($this->isIdentifierColumn !== []) {
608
            // Identifier columns are already discovered here: we can use the first one directly.
609 2
            assert($this->typeMappings !== []);
610
611 2
            return $this->typeMappings[array_keys(array_values($this->isIdentifierColumn)[0])[0]];
612
        }
613
614
        // We are selecting entities, and the first selected entity is our root of the selection.
615 67
        if ($this->aliasMap !== []) {
616 43
            $metadata = $em->getClassMetadata($this->aliasMap[array_keys($this->aliasMap)[0]]);
617
618 43
            return $metadata->getTypeOfField($metadata->getSingleIdentifierFieldName());
619
        }
620
621
        // We are selecting scalar fields - the first selected field will be assumed (!!! assumption !!!) as identifier
622 24
        assert($this->scalarMappings !== []);
623 24
        assert($this->typeMappings !== []);
624
625 24
        return $this->typeMappings[array_keys($this->scalarMappings)[0]];
626
    }
627
}
628