Passed
Pull Request — 2.6 (#7821)
by Marco
07:55
created

ResultSetMapping::isMixedResult()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 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 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 function array_keys;
24
use function array_values;
25
use function assert;
26
27
/**
28
 * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result.
29
 *
30
 * IMPORTANT NOTE:
31
 * The properties of this class are only public for fast internal READ access and to (drastically)
32
 * reduce the size of serialized instances for more effective caching due to better (un-)serialization
33
 * performance.
34
 *
35
 * <b>Users should use the public methods.</b>
36
 *
37
 * @author Roman Borschel <[email protected]>
38
 * @since 2.0
39
 * @todo Think about whether the number of lookup maps can be reduced.
40
 */
41
class ResultSetMapping
42
{
43
    /**
44
     * Whether the result is mixed (contains scalar values together with field values).
45
     *
46
     * @ignore
47
     * @var boolean
48
     */
49
    public $isMixed = false;
50
51
    /**
52
     * Whether the result is a select statement.
53
     *
54
     * @ignore
55
     * @var boolean
56
     */
57
    public $isSelect = true;
58
59
    /**
60
     * Maps alias names to class names.
61
     *
62
     * @ignore
63
     * @var array
64
     */
65
    public $aliasMap = [];
66
67
    /**
68
     * Maps alias names to related association field names.
69
     *
70
     * @ignore
71
     * @var array
72
     */
73
    public $relationMap = [];
74
75
    /**
76
     * Maps alias names to parent alias names.
77
     *
78
     * @ignore
79
     * @var array
80
     */
81
    public $parentAliasMap = [];
82
83
    /**
84
     * Maps column names in the result set to field names for each class.
85
     *
86
     * @ignore
87
     * @var array
88
     */
89
    public $fieldMappings = [];
90
91
    /**
92
     * Maps column names in the result set to the alias/field name to use in the mapped result.
93
     *
94
     * @ignore
95
     * @var array
96
     */
97
    public $scalarMappings = [];
98
99
    /**
100
     * Maps column names in the result set to the alias/field type to use in the mapped result.
101
     *
102
     * @ignore
103
     * @var array
104
     */
105
    public $typeMappings = [];
106
107
    /**
108
     * Maps entities in the result set to the alias name to use in the mapped result.
109
     *
110
     * @ignore
111
     * @var array
112
     */
113
    public $entityMappings = [];
114
115
    /**
116
     * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
117
     *
118
     * @ignore
119
     * @var array
120
     */
121
    public $metaMappings = [];
122
123
    /**
124
     * Maps column names in the result set to the alias they belong to.
125
     *
126
     * @ignore
127
     * @var array
128
     */
129
    public $columnOwnerMap = [];
130
131
    /**
132
     * List of columns in the result set that are used as discriminator columns.
133
     *
134
     * @ignore
135
     * @var array
136
     */
137
    public $discriminatorColumns = [];
138
139
    /**
140
     * Maps alias names to field names that should be used for indexing.
141
     *
142
     * @ignore
143
     * @var array
144
     */
145
    public $indexByMap = [];
146
147
    /**
148
     * Map from column names to class names that declare the field the column is mapped to.
149
     *
150
     * @ignore
151
     * @var array
152
     */
153
    public $declaringClasses = [];
154
155
    /**
156
     * This is necessary to hydrate derivate foreign keys correctly.
157
     *
158
     * @var array
159
     */
160
    public $isIdentifierColumn = [];
161
162
    /**
163
     * Maps column names in the result set to field names for each new object expression.
164
     *
165
     * @var array
166
     */
167
    public $newObjectMappings = [];
168
169
    /**
170
     * Maps metadata parameter names to the metadata attribute.
171
     *
172
     * @var array
173
     */
174
    public $metadataParameterMapping = [];
175
176
    /**
177
     * Contains query parameter names to be resolved as discriminator values
178
     *
179
     * @var array
180
     */
181
    public $discriminatorParameters = [];
182
183
    /**
184
     * Adds an entity result to this ResultSetMapping.
185
     *
186
     * @param string      $class       The class name of the entity.
187
     * @param string      $alias       The alias for the class. The alias must be unique among all entity
188
     *                                 results or joined entity results within this ResultSetMapping.
189
     * @param string|null $resultAlias The result alias with which the entity result should be
190
     *                                 placed in the result structure.
191
     *
192
     * @return ResultSetMapping This ResultSetMapping instance.
193
     *
194
     * @todo Rename: addRootEntity
195
     */
196 1165
    public function addEntityResult($class, $alias, $resultAlias = null)
197
    {
198 1165
        $this->aliasMap[$alias] = $class;
199 1165
        $this->entityMappings[$alias] = $resultAlias;
200
201 1165
        if ($resultAlias !== null) {
202 45
            $this->isMixed = true;
203
        }
204
205 1165
        return $this;
206
    }
207
208
    /**
209
     * Sets a discriminator column for an entity result or joined entity result.
210
     * The discriminator column will be used to determine the concrete class name to
211
     * instantiate.
212
     *
213
     * @param string $alias       The alias of the entity result or joined entity result the discriminator
214
     *                            column should be used for.
215
     * @param string $discrColumn The name of the discriminator column in the SQL result set.
216
     *
217
     * @return ResultSetMapping This ResultSetMapping instance.
218
     *
219
     * @todo Rename: addDiscriminatorColumn
220
     */
221 180
    public function setDiscriminatorColumn($alias, $discrColumn)
222
    {
223 180
        $this->discriminatorColumns[$alias] = $discrColumn;
224 180
        $this->columnOwnerMap[$discrColumn] = $alias;
225
226 180
        return $this;
227
    }
228
229
    /**
230
     * Sets a field to use for indexing an entity result or joined entity result.
231
     *
232
     * @param string $alias     The alias of an entity result or joined entity result.
233
     * @param string $fieldName The name of the field to use for indexing.
234
     *
235
     * @return ResultSetMapping This ResultSetMapping instance.
236
     */
237 43
    public function addIndexBy($alias, $fieldName)
238
    {
239 43
        $found = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $found is dead and can be removed.
Loading history...
240
241 43
        foreach (array_merge($this->metaMappings, $this->fieldMappings) as $columnName => $columnFieldName) {
242 43
            if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue;
243
244 42
            $this->addIndexByColumn($alias, $columnName);
245 42
            $found = true;
246
247 42
            break;
248
        }
249
250
        /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals
251
        if ( ! $found) {
252
            $message = sprintf(
253
                'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.',
254
                $alias,
255
                $fieldName
256
            );
257
258
            throw new \LogicException($message);
259
        }
260
        */
261
262 43
        return $this;
263
    }
264
265
    /**
266
     * Sets to index by a scalar result column name.
267
     *
268
     * @param string $resultColumnName
269
     *
270
     * @return ResultSetMapping This ResultSetMapping instance.
271
     */
272 3
    public function addIndexByScalar($resultColumnName)
273
    {
274 3
        $this->indexByMap['scalars'] = $resultColumnName;
275
276 3
        return $this;
277
    }
278
279
    /**
280
     * Sets a column to use for indexing an entity or joined entity result by the given alias name.
281
     *
282
     * @param string $alias
283
     * @param string $resultColumnName
284
     *
285
     * @return ResultSetMapping This ResultSetMapping instance.
286
     */
287 43
    public function addIndexByColumn($alias, $resultColumnName)
288
    {
289 43
        $this->indexByMap[$alias] = $resultColumnName;
290
291 43
        return $this;
292
    }
293
294
    /**
295
     * Checks whether an entity result or joined entity result with a given alias has
296
     * a field set for indexing.
297
     *
298
     * @param string $alias
299
     *
300
     * @return boolean
301
     *
302
     * @todo Rename: isIndexed($alias)
303
     */
304 2
    public function hasIndexBy($alias)
305
    {
306 2
        return isset($this->indexByMap[$alias]);
307
    }
308
309
    /**
310
     * Checks whether the column with the given name is mapped as a field result
311
     * as part of an entity result or joined entity result.
312
     *
313
     * @param string $columnName The name of the column in the SQL result set.
314
     *
315
     * @return boolean
316
     *
317
     * @todo Rename: isField
318
     */
319 1
    public function isFieldResult($columnName)
320
    {
321 1
        return isset($this->fieldMappings[$columnName]);
322
    }
323
324
    /**
325
     * Adds a field to the result that belongs to an entity or joined entity.
326
     *
327
     * @param string      $alias          The alias of the root entity or joined entity to which the field belongs.
328
     * @param string      $columnName     The name of the column in the SQL result set.
329
     * @param string      $fieldName      The name of the field on the declaring class.
330
     * @param string|null $declaringClass The name of the class that declares/owns the specified field.
331
     *                                    When $alias refers to a superclass in a mapped hierarchy but
332
     *                                    the field $fieldName is defined on a subclass, specify that here.
333
     *                                    If not specified, the field is assumed to belong to the class
334
     *                                    designated by $alias.
335
     *
336
     * @return ResultSetMapping This ResultSetMapping instance.
337
     *
338
     * @todo Rename: addField
339
     */
340 1149
    public function addFieldResult($alias, $columnName, $fieldName, $declaringClass = null)
341
    {
342
        // column name (in result set) => field name
343 1149
        $this->fieldMappings[$columnName] = $fieldName;
344
        // column name => alias of owner
345 1149
        $this->columnOwnerMap[$columnName] = $alias;
346
        // field name => class name of declaring class
347 1149
        $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
348
349 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...
350 10
            $this->isMixed = true;
351
        }
352
353 1149
        return $this;
354
    }
355
356
    /**
357
     * Adds a joined entity result.
358
     *
359
     * @param string $class       The class name of the joined entity.
360
     * @param string $alias       The unique alias to use for the joined entity.
361
     * @param string $parentAlias The alias of the entity result that is the parent of this joined result.
362
     * @param string $relation    The association field that connects the parent entity result
363
     *                            with the joined entity result.
364
     *
365
     * @return ResultSetMapping This ResultSetMapping instance.
366
     *
367
     * @todo Rename: addJoinedEntity
368
     */
369 389
    public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
370
    {
371 389
        $this->aliasMap[$alias]       = $class;
372 389
        $this->parentAliasMap[$alias] = $parentAlias;
373 389
        $this->relationMap[$alias]    = $relation;
374
375 389
        return $this;
376
    }
377
378
    /**
379
     * Adds a scalar result mapping.
380
     *
381
     * @param string $columnName The name of the column in the SQL result set.
382
     * @param string $alias      The result alias with which the scalar result should be placed in the result structure.
383
     * @param string $type       The column type
384
     *
385
     * @return ResultSetMapping This ResultSetMapping instance.
386
     *
387
     * @todo Rename: addScalar
388
     */
389 352
    public function addScalarResult($columnName, $alias, $type = 'string')
390
    {
391 352
        $this->scalarMappings[$columnName] = $alias;
392 352
        $this->typeMappings[$columnName]   = $type;
393
394 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...
395 122
            $this->isMixed = true;
396
        }
397
398 352
        return $this;
399
    }
400
401
    /**
402
     * Adds a metadata parameter mappings.
403
     *
404
     * @param mixed  $parameter The parameter name in the SQL result set.
405
     * @param string $attribute The metadata attribute.
406
     */
407
    public function addMetadataParameterMapping($parameter, $attribute)
408
    {
409
        $this->metadataParameterMapping[$parameter] = $attribute;
410
    }
411
412
    /**
413
     * Checks whether a column with a given name is mapped as a scalar result.
414
     *
415
     * @param string $columnName The name of the column in the SQL result set.
416
     *
417
     * @return boolean
418
     *
419
     * @todo Rename: isScalar
420
     */
421 2
    public function isScalarResult($columnName)
422
    {
423 2
        return isset($this->scalarMappings[$columnName]);
424
    }
425
426
    /**
427
     * Gets the name of the class of an entity result or joined entity result,
428
     * identified by the given unique alias.
429
     *
430
     * @param string $alias
431
     *
432
     * @return string
433
     */
434 5
    public function getClassName($alias)
435
    {
436 5
        return $this->aliasMap[$alias];
437
    }
438
439
    /**
440
     * Gets the field alias for a column that is mapped as a scalar value.
441
     *
442
     * @param string $columnName The name of the column in the SQL result set.
443
     *
444
     * @return string
445
     */
446 2
    public function getScalarAlias($columnName)
447
    {
448 2
        return $this->scalarMappings[$columnName];
449
    }
450
451
    /**
452
     * Gets the name of the class that owns a field mapping for the specified column.
453
     *
454
     * @param string $columnName
455
     *
456
     * @return string
457
     */
458 4
    public function getDeclaringClass($columnName)
459
    {
460 4
        return $this->declaringClasses[$columnName];
461
    }
462
463
    /**
464
     * @param string $alias
465
     *
466
     * @return string
467
     */
468
    public function getRelation($alias)
469
    {
470
        return $this->relationMap[$alias];
471
    }
472
473
    /**
474
     * @param string $alias
475
     *
476
     * @return boolean
477
     */
478 1
    public function isRelation($alias)
479
    {
480 1
        return isset($this->relationMap[$alias]);
481
    }
482
483
    /**
484
     * Gets the alias of the class that owns a field mapping for the specified column.
485
     *
486
     * @param string $columnName
487
     *
488
     * @return string
489
     */
490 4
    public function getEntityAlias($columnName)
491
    {
492 4
        return $this->columnOwnerMap[$columnName];
493
    }
494
495
    /**
496
     * Gets the parent alias of the given alias.
497
     *
498
     * @param string $alias
499
     *
500
     * @return string
501
     */
502
    public function getParentAlias($alias)
503
    {
504
        return $this->parentAliasMap[$alias];
505
    }
506
507
    /**
508
     * Checks whether the given alias has a parent alias.
509
     *
510
     * @param string $alias
511
     *
512
     * @return boolean
513
     */
514 1
    public function hasParentAlias($alias)
515
    {
516 1
        return isset($this->parentAliasMap[$alias]);
517
    }
518
519
    /**
520
     * Gets the field name for a column name.
521
     *
522
     * @param string $columnName
523
     *
524
     * @return string
525
     */
526 1
    public function getFieldName($columnName)
527
    {
528 1
        return $this->fieldMappings[$columnName];
529
    }
530
531
    /**
532
     * @return array
533
     */
534
    public function getAliasMap()
535
    {
536
        return $this->aliasMap;
537
    }
538
539
    /**
540
     * Gets the number of different entities that appear in the mapped result.
541
     *
542
     * @return integer
543
     */
544
    public function getEntityResultCount()
545
    {
546
        return count($this->aliasMap);
547
    }
548
549
    /**
550
     * Checks whether this ResultSetMapping defines a mixed result.
551
     *
552
     * Mixed results can only occur in object and array (graph) hydration. In such a
553
     * case a mixed result means that scalar values are mixed with objects/array in
554
     * the result.
555
     *
556
     * @return boolean
557
     */
558 1
    public function isMixedResult()
559
    {
560 1
        return $this->isMixed;
561
    }
562
563
    /**
564
     * Adds a meta column (foreign key or discriminator column) to the result set.
565
     *
566
     * @param string $alias              The result alias with which the meta result should be placed in the result structure.
567
     * @param string $columnName         The name of the column in the SQL result set.
568
     * @param string $fieldName          The name of the field on the declaring class.
569
     * @param bool   $isIdentifierColumn
570
     * @param string $type               The column type
571
     *
572
     * @return ResultSetMapping This ResultSetMapping instance.
573
     *
574
     * @todo Make all methods of this class require all parameters and not infer anything
575
     */
576 774
    public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false, $type = null)
577
    {
578 774
        $this->metaMappings[$columnName] = $fieldName;
579 774
        $this->columnOwnerMap[$columnName] = $alias;
580
581 774
        if ($isIdentifierColumn) {
582 70
            $this->isIdentifierColumn[$alias][$columnName] = true;
583
        }
584
585 774
        if ($type) {
586 773
            $this->typeMappings[$columnName] = $type;
587
        }
588
589 774
        return $this;
590
    }
591
592
    /**
593
     * Retrieves the DBAL type name for the single identifier column of the root of a selection.
594
     * Composite identifiers not supported!
595
     *
596
     * @throws \Doctrine\ORM\Mapping\MappingException if the identifier is not a single field, or if metadata for its
597
     *                                                owner is incorrect/missing.
598
     * @internal only to be used by ORM internals: do not use in downstream projects! This API is a minimal abstraction
599
     *           that only ORM internals need, and it tries to make sense of the very complex and squishy array-alike
600
     *           structure inside this class. Some assumptions are coded in here, so here be dragons.
601
     *
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
617 43
            $metadata = $em->getClassMetadata($this->aliasMap[array_keys($this->aliasMap)[0]]);
618
619 43
            return $metadata->getTypeOfField($metadata->getSingleIdentifierFieldName());
620
        }
621
622
        // We are selecting scalar fields - the first selected field will be assumed (!!! assumption !!!) as identifier
623 24
        assert($this->scalarMappings !== []);
624 24
        assert($this->typeMappings !== []);
625
626 24
        return $this->typeMappings[array_keys($this->scalarMappings)[0]];
627
    }
628
}
629
630