Completed
Pull Request — 2.6 (#7903)
by Gabriel
532:29 queued 524:33
created

ResultSetMapping::addIndexByScalar()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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