Passed
Pull Request — 2.8.x (#8022)
by Benjamin
08:51
created

ClassMetadataFactory::addInheritedRelations()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 10
nc 10
nop 2
dl 0
loc 18
rs 8.4444
c 0
b 0
f 0
ccs 11
cts 11
cp 1
crap 8
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\Mapping;
21
22
use Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory;
23
use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
24
use Doctrine\Common\Persistence\Mapping\ReflectionService;
25
use Doctrine\DBAL\Platforms;
26
use Doctrine\ORM\EntityManagerInterface;
27
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
28
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
29
use Doctrine\ORM\Events;
30
use Doctrine\ORM\Id\BigIntegerIdentityGenerator;
31
use Doctrine\ORM\Id\IdentityGenerator;
32
use Doctrine\ORM\ORMException;
33
use ReflectionException;
34
35
/**
36
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
37
 * metadata mapping information of a class which describes how a class should be mapped
38
 * to a relational database.
39
 *
40
 * @since   2.0
41
 * @author  Benjamin Eberlei <[email protected]>
42
 * @author  Guilherme Blanco <[email protected]>
43
 * @author  Jonathan Wage <[email protected]>
44
 * @author  Roman Borschel <[email protected]>
45
 */
46
class ClassMetadataFactory extends AbstractClassMetadataFactory
47
{
48
    /**
49
     * @var EntityManagerInterface|null
50
     */
51
    private $em;
52
53
    /**
54
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
55
     */
56
    private $targetPlatform;
57
58
    /**
59
     * @var \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver
60
     */
61
    private $driver;
62
63
    /**
64
     * @var \Doctrine\Common\EventManager
65
     */
66
    private $evm;
67
68
    /**
69
     * @var array
70
     */
71
    private $embeddablesActiveNesting = [];
72
73
    /**
74
     * {@inheritDoc}
75
     */
76 508
    protected function loadMetadata($name)
77
    {
78 508
        $loaded = parent::loadMetadata($name);
79
80 486
        array_map([$this, 'resolveDiscriminatorValue'], array_map([$this, 'getMetadataFor'], $loaded));
81
82 486
        return $loaded;
83
    }
84
85
    /**
86
     * @param EntityManagerInterface $em
87
     */
88 2512
    public function setEntityManager(EntityManagerInterface $em)
89
    {
90 2512
        $this->em = $em;
91 2512
    }
92
93
    /**
94
     * {@inheritDoc}
95
     */
96 578
    protected function initialize()
97
    {
98 578
        $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
0 ignored issues
show
Bug introduced by
The method getConfiguration() does not exist on null. ( Ignorable by Annotation )

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

98
        $this->driver = $this->em->/** @scrutinizer ignore-call */ getConfiguration()->getMetadataDriverImpl();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
99 578
        $this->evm = $this->em->getEventManager();
100 578
        $this->initialized = true;
101 578
    }
102
103
    /**
104
     * {@inheritDoc}
105
     */
106 3
    protected function onNotFoundMetadata($className)
107
    {
108 3
        if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
109 1
            return;
110
        }
111
112 2
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em);
0 ignored issues
show
Bug introduced by
It seems like $this->em can also be of type null; however, parameter $objectManager of Doctrine\ORM\Event\OnCla...ventArgs::__construct() does only seem to accept Doctrine\Common\Persistence\ObjectManager, maybe add an additional type check? ( Ignorable by Annotation )

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

112
        $eventArgs = new OnClassMetadataNotFoundEventArgs($className, /** @scrutinizer ignore-type */ $this->em);
Loading history...
113
114 2
        $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
115
116 2
        return $eventArgs->getFoundMetadata();
117
    }
118
119
    /**
120
     * {@inheritDoc}
121
     */
122 505
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents)
123
    {
124
        /* @var $class ClassMetadata */
125
        /* @var $parent ClassMetadata */
126 505
        if ($parent) {
0 ignored issues
show
introduced by
$parent is of type Doctrine\ORM\Mapping\ClassMetadata, thus it always evaluated to true.
Loading history...
127 148
            $class->setInheritanceType($parent->inheritanceType);
128 148
            $class->setDiscriminatorColumn($parent->discriminatorColumn);
129 148
            $class->setIdGeneratorType($parent->generatorType);
130 148
            $this->addInheritedFields($class, $parent);
131 148
            $this->addInheritedRelations($class, $parent);
132 147
            $this->addInheritedEmbeddedClasses($class, $parent);
133 147
            $class->setIdentifier($parent->identifier);
134 147
            $class->setVersioned($parent->isVersioned);
135 147
            $class->setVersionField($parent->versionField);
136 147
            $class->setDiscriminatorMap($parent->discriminatorMap);
137 147
            $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
138 147
            $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
139
140 147
            if ( ! empty($parent->customGeneratorDefinition)) {
141 1
                $class->setCustomGeneratorDefinition($parent->customGeneratorDefinition);
142
            }
143
144 147
            if ($parent->isMappedSuperclass) {
145 52
                $class->setCustomRepositoryClass($parent->customRepositoryClassName);
146
            }
147
        }
148
149
        // Invoke driver
150
        try {
151 505
            $this->driver->loadMetadataForClass($class->getName(), $class);
152 5
        } catch (ReflectionException $e) {
153
            throw MappingException::reflectionFailure($class->getName(), $e);
154
        }
155
156
        // If this class has a parent the id generator strategy is inherited.
157
        // However this is only true if the hierarchy of parents contains the root entity,
158
        // if it consists of mapped superclasses these don't necessarily include the id field.
159 500
        if ($parent && $rootEntityFound) {
160 100
            $this->inheritIdGeneratorMapping($class, $parent);
161
        } else {
162 498
            $this->completeIdGeneratorMapping($class);
163
        }
164
165 498
        if (!$class->isMappedSuperclass) {
166 496
            foreach ($class->embeddedClasses as $property => $embeddableClass) {
167
168 14
                if (isset($embeddableClass['inherited'])) {
169 1
                    continue;
170
                }
171
172 14
                if ( ! (isset($embeddableClass['class']) && $embeddableClass['class'])) {
173 1
                    throw MappingException::missingEmbeddedClass($property);
174
                }
175
176 13
                if (isset($this->embeddablesActiveNesting[$embeddableClass['class']])) {
177 2
                    throw MappingException::infiniteEmbeddableNesting($class->name, $property);
178
                }
179
180 13
                $this->embeddablesActiveNesting[$class->name] = true;
181
182 13
                $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
183
184 12
                if ($embeddableMetadata->isEmbeddedClass) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedClass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
185 12
                    $this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property);
186
                }
187
188 12
                $identifier = $embeddableMetadata->getIdentifier();
189
190 12
                if (! empty($identifier)) {
191 5
                    $this->inheritIdGeneratorMapping($class, $embeddableMetadata);
192
                }
193
194 12
                $class->inlineEmbeddable($property, $embeddableMetadata);
195
196 12
                unset($this->embeddablesActiveNesting[$class->name]);
197
            }
198
        }
199
200 495
        if ($parent) {
0 ignored issues
show
introduced by
$parent is of type Doctrine\ORM\Mapping\ClassMetadata, thus it always evaluated to true.
Loading history...
201 147
            if ($parent->isInheritanceTypeSingleTable()) {
202 38
                $class->setPrimaryTable($parent->table);
203
            }
204
205 147
            if ($parent) {
0 ignored issues
show
introduced by
$parent is of type Doctrine\ORM\Mapping\ClassMetadata, thus it always evaluated to true.
Loading history...
206 147
                $this->addInheritedIndexes($class, $parent);
207
            }
208
209 147
            if ($parent->cache) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parent->cache 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...
210 3
                $class->cache = $parent->cache;
211
            }
212
213 147
            if ($parent->containsForeignIdentifier) {
214 1
                $class->containsForeignIdentifier = true;
215
            }
216
217 147
            if ( ! empty($parent->namedQueries)) {
218 1
                $this->addInheritedNamedQueries($class, $parent);
219
            }
220
221 147
            if ( ! empty($parent->namedNativeQueries)) {
222 9
                $this->addInheritedNamedNativeQueries($class, $parent);
223
            }
224
225 147
            if ( ! empty($parent->sqlResultSetMappings)) {
226 9
                $this->addInheritedSqlResultSetMappings($class, $parent);
227
            }
228
229 147
            if ( ! empty($parent->entityListeners) && empty($class->entityListeners)) {
230 15
                $class->entityListeners = $parent->entityListeners;
231
            }
232
        }
233
234 495
        $class->setParentClasses($nonSuperclassParents);
235
236 495
        if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) {
237
            $this->addDefaultDiscriminatorMap($class);
238
        }
239
240 495
        if ($this->evm->hasListeners(Events::loadClassMetadata)) {
241 6
            $eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
242 6
            $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
243
        }
244
245 494
        $this->validateRuntimeMetadata($class, $parent);
246 493
    }
247
248
    /**
249
     * Validate runtime metadata is correctly defined.
250
     *
251
     * @param ClassMetadata               $class
252
     * @param ClassMetadataInterface|null $parent
253
     *
254
     * @return void
255
     *
256
     * @throws MappingException
257
     */
258 494
    protected function validateRuntimeMetadata($class, $parent)
259
    {
260 494
        if ( ! $class->reflClass ) {
261
            // only validate if there is a reflection class instance
262 8
            return;
263
        }
264
265 486
        $class->validateIdentifier();
266 486
        $class->validateAssociations();
267 486
        $class->validateLifecycleCallbacks($this->getReflectionService());
268
269
        // verify inheritance
270 486
        if ( ! $class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
271 90
            if ( ! $parent) {
272 87
                if (count($class->discriminatorMap) == 0) {
273
                    throw MappingException::missingDiscriminatorMap($class->name);
274
                }
275 87
                if ( ! $class->discriminatorColumn) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->discriminatorColumn 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...
276 90
                    throw MappingException::missingDiscriminatorColumn($class->name);
277
                }
278
            }
279 438
        } else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->discriminatorColumn 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...
280
            // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
281 1
            throw MappingException::noInheritanceOnMappedSuperClass($class->name);
282
        }
283 485
    }
284
285
    /**
286
     * {@inheritDoc}
287
     */
288 500
    protected function newClassMetadataInstance($className)
289
    {
290 500
        return new ClassMetadata($className, $this->em->getConfiguration()->getNamingStrategy());
291
    }
292
293
    /**
294
     * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
295
     * map classes and looking for a fitting one.
296
     *
297
     * @param ClassMetadata $metadata
298
     *
299
     * @return void
300
     *
301
     * @throws MappingException
302
     */
303 486
    private function resolveDiscriminatorValue(ClassMetadata $metadata)
304
    {
305 486
        if ($metadata->discriminatorValue
306 467
            || ! $metadata->discriminatorMap
307 52
            || $metadata->isMappedSuperclass
308 51
            || ! $metadata->reflClass
309 486
            || $metadata->reflClass->isAbstract()
310
        ) {
311 486
            return;
312
        }
313
314
        // minor optimization: avoid loading related metadata when not needed
315 5
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
316 5
            if ($discriminatorClass === $metadata->name) {
317 3
                $metadata->discriminatorValue = $discriminatorValue;
318
319 5
                return;
320
            }
321
        }
322
323
        // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata
324 2
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
325 2
            if ($metadata->name === $this->getMetadataFor($discriminatorClass)->getName()) {
326 1
                $metadata->discriminatorValue = $discriminatorValue;
327
328 2
                return;
329
            }
330
        }
331
332 1
        throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->name, $metadata->rootEntityName);
333
    }
334
335
    /**
336
     * Adds a default discriminator map if no one is given
337
     *
338
     * If an entity is of any inheritance type and does not contain a
339
     * discriminator map, then the map is generated automatically. This process
340
     * is expensive computation wise.
341
     *
342
     * The automatically generated discriminator map contains the lowercase short name of
343
     * each class as key.
344
     *
345
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
346
     *
347
     * @throws MappingException
348
     */
349
    private function addDefaultDiscriminatorMap(ClassMetadata $class)
350
    {
351
        $allClasses = $this->driver->getAllClassNames();
352
        $fqcn = $class->getName();
353
        $map = [$this->getShortName($class->name) => $fqcn];
354
355
        $duplicates = [];
356
        foreach ($allClasses as $subClassCandidate) {
357
            if (is_subclass_of($subClassCandidate, $fqcn)) {
358
                $shortName = $this->getShortName($subClassCandidate);
359
360
                if (isset($map[$shortName])) {
361
                    $duplicates[] = $shortName;
362
                }
363
364
                $map[$shortName] = $subClassCandidate;
365
            }
366
        }
367
368
        if ($duplicates) {
369
            throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map);
370
        }
371
372
        $class->setDiscriminatorMap($map);
373
    }
374
375
    /**
376
     * Gets the lower-case short name of a class.
377
     *
378
     * @param string $className
379
     *
380
     * @return string
381
     */
382
    private function getShortName($className)
383
    {
384
        if (strpos($className, "\\") === false) {
385
            return strtolower($className);
386
        }
387
388
        $parts = explode("\\", $className);
389
390
        return strtolower(end($parts));
391
    }
392
393
    /**
394
     * Adds inherited fields to the subclass mapping.
395
     *
396
     * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
397
     * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
398
     *
399
     * @return void
400
     */
401 148
    private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
402
    {
403 148
        foreach ($parentClass->fieldMappings as $mapping) {
404 132
            if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
405 100
                $mapping['inherited'] = $parentClass->name;
406
            }
407 132
            if ( ! isset($mapping['declared'])) {
408 132
                $mapping['declared'] = $parentClass->name;
409
            }
410 132
            $subClass->addInheritedFieldMapping($mapping);
411
        }
412 148
        foreach ($parentClass->reflFields as $name => $field) {
413 133
            $subClass->reflFields[$name] = $field;
414
        }
415 148
    }
416
417
    /**
418
     * Adds inherited association mappings to the subclass mapping.
419
     *
420
     * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
421
     * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
422
     *
423
     * @return void
424
     *
425
     * @throws MappingException
426
     */
427 148
    private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
428
    {
429 148
        foreach ($parentClass->associationMappings as $field => $mapping) {
430 59
            if ($parentClass->isMappedSuperclass) {
431 16
                if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) {
432 1
                    throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
433
                }
434 15
                $mapping['sourceEntity'] = $subClass->name;
435
            }
436
437
            //$subclassMapping = $mapping;
438 58
            if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
439 43
                $mapping['inherited'] = $parentClass->name;
440
            }
441 58
            if ( ! isset($mapping['declared'])) {
442 58
                $mapping['declared'] = $parentClass->name;
443
            }
444 58
            $subClass->addInheritedAssociationMapping($mapping);
445
        }
446 147
    }
447
448 147
    private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass)
449
    {
450 147
        foreach ($parentClass->embeddedClasses as $field => $embeddedClass) {
451 2
            if ( ! isset($embeddedClass['inherited']) && ! $parentClass->isMappedSuperclass) {
452 1
                $embeddedClass['inherited'] = $parentClass->name;
453
            }
454 2
            if ( ! isset($embeddedClass['declared'])) {
455 2
                $embeddedClass['declared'] = $parentClass->name;
456
            }
457
458 2
            $subClass->embeddedClasses[$field] = $embeddedClass;
459
        }
460 147
    }
461
462
    /**
463
     * Adds nested embedded classes metadata to a parent class.
464
     *
465
     * @param ClassMetadata $subClass    Sub embedded class metadata to add nested embedded classes metadata from.
466
     * @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to.
467
     * @param string        $prefix      Embedded classes' prefix to use for nested embedded classes field names.
468
     */
469 12
    private function addNestedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass, $prefix)
470
    {
471 12
        foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
472 4
            if (isset($embeddableClass['inherited'])) {
473
                continue;
474
            }
475
476 4
            $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
477
478 4
            $parentClass->mapEmbedded(
479
                [
480 4
                    'fieldName' => $prefix . '.' . $property,
481 4
                    'class' => $embeddableMetadata->name,
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
482 4
                    'columnPrefix' => $embeddableClass['columnPrefix'],
483 4
                    'nullable' => $embeddableClass['nullable'],
484 4
                    'declaredField' => $embeddableClass['declaredField']
485 1
                            ? $prefix . '.' . $embeddableClass['declaredField']
486 4
                            : $prefix,
487 4
                    'originalField' => $embeddableClass['originalField'] ?: $property,
488
                ]
489
            );
490
        }
491 12
    }
492
493
    /**
494
     * Copy the table indices from the parent class superclass to the child class
495
     *
496
     * @param ClassMetadata $subClass
497
     * @param ClassMetadata $parentClass
498
     *
499
     * @return void
500
     */
501 147
    private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass)
502
    {
503 147
        if (! $parentClass->isMappedSuperclass) {
504 100
            return;
505
        }
506
507 52
        foreach (['uniqueConstraints', 'indexes'] as $indexType) {
508 52
            if (isset($parentClass->table[$indexType])) {
509 1
                foreach ($parentClass->table[$indexType] as $indexName => $index) {
510 1
                    if (isset($subClass->table[$indexType][$indexName])) {
511
                        continue; // Let the inheriting table override indices
512
                    }
513
514 52
                    $subClass->table[$indexType][$indexName] = $index;
515
                }
516
            }
517
        }
518 52
    }
519
520
    /**
521
     * Adds inherited named queries to the subclass mapping.
522
     *
523
     * @since 2.2
524
     *
525
     * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
526
     * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
527
     *
528
     * @return void
529
     */
530 1
    private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
531
    {
532 1
        foreach ($parentClass->namedQueries as $name => $query) {
533 1
            if ( ! isset ($subClass->namedQueries[$name])) {
534 1
                $subClass->addNamedQuery(
535
                    [
536 1
                        'name'  => $query['name'],
537 1
                        'query' => $query['query']
538
                    ]
539
                );
540
            }
541
        }
542 1
    }
543
544
    /**
545
     * Adds inherited named native queries to the subclass mapping.
546
     *
547
     * @since 2.3
548
     *
549
     * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
550
     * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
551
     *
552
     * @return void
553
     */
554 9
    private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
555
    {
556 9
        foreach ($parentClass->namedNativeQueries as $name => $query) {
557 9
            if ( ! isset ($subClass->namedNativeQueries[$name])) {
558 9
                $subClass->addNamedNativeQuery(
559
                    [
560 9
                        'name'              => $query['name'],
561 9
                        'query'             => $query['query'],
562 9
                        'isSelfClass'       => $query['isSelfClass'],
563 9
                        'resultSetMapping'  => $query['resultSetMapping'],
564 9
                        'resultClass'       => $query['isSelfClass'] ? $subClass->name : $query['resultClass'],
565
                    ]
566
                );
567
            }
568
        }
569 9
    }
570
571
    /**
572
     * Adds inherited sql result set mappings to the subclass mapping.
573
     *
574
     * @since 2.3
575
     *
576
     * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
577
     * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
578
     *
579
     * @return void
580
     */
581 9
    private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass)
582
    {
583 9
        foreach ($parentClass->sqlResultSetMappings as $name => $mapping) {
584 9
            if ( ! isset ($subClass->sqlResultSetMappings[$name])) {
585 9
                $entities = [];
586 9
                foreach ($mapping['entities'] as $entity) {
587 9
                    $entities[] = [
588 9
                        'fields'                => $entity['fields'],
589 9
                        'isSelfClass'           => $entity['isSelfClass'],
590 9
                        'discriminatorColumn'   => $entity['discriminatorColumn'],
591 9
                        'entityClass'           => $entity['isSelfClass'] ? $subClass->name : $entity['entityClass'],
592
                    ];
593
                }
594
595 9
                $subClass->addSqlResultSetMapping(
596
                    [
597 9
                        'name'          => $mapping['name'],
598 9
                        'columns'       => $mapping['columns'],
599 9
                        'entities'      => $entities,
600
                    ]
601
                );
602
            }
603
        }
604 9
    }
605
606
    /**
607
     * Completes the ID generator mapping. If "auto" is specified we choose the generator
608
     * most appropriate for the targeted database platform.
609
     *
610
     * @param ClassMetadataInfo $class
611
     *
612
     * @return void
613
     *
614
     * @throws ORMException
615
     */
616 498
    private function completeIdGeneratorMapping(ClassMetadataInfo $class)
617
    {
618 498
        $idGenType = $class->generatorType;
619 498
        if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) {
620 354
            if ($this->getTargetPlatform()->prefersSequences()) {
621
                $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE);
622 354
            } else if ($this->getTargetPlatform()->prefersIdentityColumns()) {
623 354
                $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
624
            } else {
625
                $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE);
626
            }
627
        }
628
629
        // Create & assign an appropriate ID generator instance
630 498
        switch ($class->generatorType) {
631 498
            case ClassMetadata::GENERATOR_TYPE_IDENTITY:
632 367
                $sequenceName = null;
633 367
                $fieldName    = $class->identifier ? $class->getSingleIdentifierFieldName() : null;
634
635
                // Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
636 367
                if ($this->getTargetPlatform()->usesSequenceEmulatedIdentityColumns()) {
637
                    $columnName     = $class->getSingleIdentifierColumnName();
638
                    $quoted         = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
639
                    $sequencePrefix = $class->getSequencePrefix($this->getTargetPlatform());
640
                    $sequenceName   = $this->getTargetPlatform()->getIdentitySequenceName($sequencePrefix, $columnName);
641
                    $definition     = [
642
                        'sequenceName' => $this->getTargetPlatform()->fixSchemaElementName($sequenceName)
643
                    ];
644
645
                    if ($quoted) {
646
                        $definition['quoted'] = true;
647
                    }
648
649
                    $sequenceName = $this
650
                        ->em
651
                        ->getConfiguration()
652
                        ->getQuoteStrategy()
653
                        ->getSequenceName($definition, $class, $this->getTargetPlatform());
654
                }
655
656 367
                $generator = ($fieldName && $class->fieldMappings[$fieldName]['type'] === 'bigint')
657 1
                    ? new BigIntegerIdentityGenerator($sequenceName)
658 367
                    : new IdentityGenerator($sequenceName);
659
660 367
                $class->setIdGenerator($generator);
661
662 367
                break;
663
664 178
            case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
665
                // If there is no sequence definition yet, create a default definition
666 6
                $definition = $class->sequenceGeneratorDefinition;
667
668 6
                if ( ! $definition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $definition 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...
669
                    $fieldName      = $class->getSingleIdentifierFieldName();
670
                    $sequenceName   = $class->getSequenceName($this->getTargetPlatform());
671
                    $quoted         = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']);
672
673
                    $definition = [
674
                        'sequenceName'      => $this->getTargetPlatform()->fixSchemaElementName($sequenceName),
675
                        'allocationSize'    => 1,
676
                        'initialValue'      => 1,
677
                    ];
678
679
                    if ($quoted) {
680
                        $definition['quoted'] = true;
681
                    }
682
683
                    $class->setSequenceGeneratorDefinition($definition);
684
                }
685
686 6
                $sequenceGenerator = new \Doctrine\ORM\Id\SequenceGenerator(
687 6
                    $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()),
688 6
                    $definition['allocationSize']
689
                );
690 6
                $class->setIdGenerator($sequenceGenerator);
691 6
                break;
692
693 175
            case ClassMetadata::GENERATOR_TYPE_NONE:
694 170
                $class->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
695 170
                break;
696
697 7
            case ClassMetadata::GENERATOR_TYPE_UUID:
698 2
                $class->setIdGenerator(new \Doctrine\ORM\Id\UuidGenerator());
699 2
                break;
700
701 5
            case ClassMetadata::GENERATOR_TYPE_TABLE:
702
                throw new ORMException("TableGenerator not yet implemented.");
703
                break;
704
705 5
            case ClassMetadata::GENERATOR_TYPE_CUSTOM:
706 5
                $definition = $class->customGeneratorDefinition;
707 5
                if ($definition === null) {
708 1
                    throw new ORMException("Can't instantiate custom generator : no custom generator definition");
709
                }
710 4
                if ( ! class_exists($definition['class'])) {
711 1
                    throw new ORMException("Can't instantiate custom generator : " .
712 1
                        $definition['class']);
713
                }
714 3
                $class->setIdGenerator(new $definition['class']);
715 3
                break;
716
717
            default:
718
                throw new ORMException("Unknown generator type: " . $class->generatorType);
719
        }
720 496
    }
721
722
    /**
723
     * Inherits the ID generator mapping from a parent class.
724
     *
725
     * @param ClassMetadataInfo $class
726
     * @param ClassMetadataInfo $parent
727
     */
728 105
    private function inheritIdGeneratorMapping(ClassMetadataInfo $class, ClassMetadataInfo $parent)
729
    {
730 105
        if ($parent->isIdGeneratorSequence()) {
731 3
            $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
732 102
        } elseif ($parent->isIdGeneratorTable()) {
733
            $class->tableGeneratorDefinition = $parent->tableGeneratorDefinition;
734
        }
735
736 105
        if ($parent->generatorType) {
737 105
            $class->setIdGeneratorType($parent->generatorType);
738
        }
739
740 105
        if ($parent->idGenerator) {
741 105
            $class->setIdGenerator($parent->idGenerator);
742
        }
743 105
    }
744
745
    /**
746
     * {@inheritDoc}
747
     */
748 2150
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService)
749
    {
750
        /* @var $class ClassMetadata */
751 2150
        $class->wakeupReflection($reflService);
752 2150
    }
753
754
    /**
755
     * {@inheritDoc}
756
     */
757 505
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService)
758
    {
759
        /* @var $class ClassMetadata */
760 505
        $class->initializeReflection($reflService);
761 505
    }
762
763
    /**
764
     * {@inheritDoc}
765
     */
766 4
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
767
    {
768 4
        return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
769
    }
770
771
    /**
772
     * {@inheritDoc}
773
     */
774 259
    protected function getDriver()
775
    {
776 259
        return $this->driver;
777
    }
778
779
    /**
780
     * {@inheritDoc}
781
     */
782 493
    protected function isEntity(ClassMetadataInterface $class)
783
    {
784 493
        return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
785
    }
786
787
    /**
788
     * @return Platforms\AbstractPlatform
789
     */
790 373
    private function getTargetPlatform()
791
    {
792 373
        if (!$this->targetPlatform) {
793 373
            $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
794
        }
795
796 373
        return $this->targetPlatform;
797
    }
798
}
799