Passed
Push — develop ( 0afeac...fa0b42 )
by Daniel
04:48
created

addDefaultDiscriminatorMap()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 24
rs 9.5222
c 0
b 0
f 0
cc 5
nc 8
nop 1
1
<?php
2
3
namespace Silverback\ApiComponentBundle\DoctrineExtension;
4
5
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
6
use Doctrine\ORM\EntityManagerInterface;
7
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
8
use Doctrine\ORM\Mapping\ClassMetadata;
9
use Doctrine\ORM\Mapping\MappingException;
10
use Silverback\ApiComponentBundle\Entity\Component\AbstractComponent;
11
use Silverback\ApiComponentBundle\Entity\Component\Collection\Collection;
12
use Silverback\ApiComponentBundle\Entity\Component\Content\Content;
13
use Silverback\ApiComponentBundle\Entity\Component\Feature\Columns\FeatureColumns;
14
use Silverback\ApiComponentBundle\Entity\Component\Feature\Columns\FeatureColumnsItem;
15
use Silverback\ApiComponentBundle\Entity\Component\Feature\Stacked\FeatureStacked;
16
use Silverback\ApiComponentBundle\Entity\Component\Feature\Stacked\FeatureStackedItem;
17
use Silverback\ApiComponentBundle\Entity\Component\Feature\TextList\FeatureTextList;
18
use Silverback\ApiComponentBundle\Entity\Component\Feature\TextList\FeatureTextListItem;
19
use Silverback\ApiComponentBundle\Entity\Component\Form\Form;
20
use Silverback\ApiComponentBundle\Entity\Component\Gallery\Gallery;
21
use Silverback\ApiComponentBundle\Entity\Component\Gallery\GalleryItem;
22
use Silverback\ApiComponentBundle\Entity\Component\Hero\Hero;
23
use Silverback\ApiComponentBundle\Entity\Component\Image\SimpleImage;
24
use Silverback\ApiComponentBundle\Entity\Component\Layout\SideColumn;
25
use Silverback\ApiComponentBundle\Entity\Component\Navigation\Menu\Menu;
26
use Silverback\ApiComponentBundle\Entity\Component\Navigation\Menu\MenuItem;
27
use Silverback\ApiComponentBundle\Entity\Component\Navigation\NavBar\NavBar;
28
use Silverback\ApiComponentBundle\Entity\Component\Navigation\NavBar\NavBarItem;
29
use Silverback\ApiComponentBundle\Entity\Component\Navigation\Tabs\Tabs;
30
use Silverback\ApiComponentBundle\Entity\Component\Navigation\Tabs\TabsItem;
31
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
32
33
class DiscriminatorMappingExtension
34
{
35
    private static $mappingKeys = [
36
        'content' => Content::class,
37
        'form' => Form::class,
38
        'gallery' => Gallery::class,
39
        'gallery_item' => GalleryItem::class,
40
        'hero' => Hero::class,
41
        'feature_columns' => FeatureColumns::class,
42
        'feature_columns_item' => FeatureColumnsItem::class,
43
        'feature_stacked' => FeatureStacked::class,
44
        'feature_stacked_item' => FeatureStackedItem::class,
45
        'feature_text_list' => FeatureTextList::class,
46
        'feature_text_list_item' => FeatureTextListItem::class,
47
        'nav_bar' => NavBar::class,
48
        'nav_bar_item' => NavBarItem::class,
49
        'tabs' => Tabs::class,
50
        'tabs_item' => TabsItem::class,
51
        'menu' => Menu::class,
52
        'menu_item' => MenuItem::class,
53
        'collection' => Collection::class,
54
        'simple_image' => SimpleImage::class,
55
        'layout_side_column' => SideColumn::class
56
    ];
57
58
    /**
59
     * @var MappingDriver
60
     */
61
    private $driver;
62
63
    /**
64
     * @param EntityManagerInterface $em
65
     */
66
    public function __construct(EntityManagerInterface $em)
67
    {
68
        $this->driver = $em->getConfiguration()->getMetadataDriverImpl();
69
    }
70
71
    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void
72
    {
73
        $classMetadata = $eventArgs->getClassMetadata();
74
        $reflection = $classMetadata->getReflectionClass();
75
        if ($reflection->getName() !== AbstractComponent::class) {
76
            return;
77
        }
78
79
        $this->addDiscriminatorMap($classMetadata);
80
    }
81
82
    protected function addDiscriminatorMap(ClassMetadata $class): void
83
    {
84
        // ! $class->discriminatorMap &&
85
        if ($class->isRootEntity() && ! $class->isInheritanceTypeNone()) {
86
            $this->addDefaultDiscriminatorMap($class);
87
        }
88
    }
89
90
    /**
91
     * Adds a default discriminator map if no one is given
92
     *
93
     * If an entity is of any inheritance type and does not contain a
94
     * discriminator map, then the map is generated automatically. This process
95
     * is expensive computation wise. We will be looking to manually specify this in future
96
     * with a default discriminator map and then further items to merge into it added
97
     * via the bundle config
98
     *
99
     * The automatically generated discriminator map contains the lowercase short name of
100
     * each class as key.
101
     *
102
     * @param ClassMetadata $class
103
     *
104
     * @throws MappingException
105
     */
106
    private function addDefaultDiscriminatorMap(ClassMetadata $class): void
107
    {
108
        $allClasses = $this->driver->getAllClassNames();
109
        $fqcn = $class->getName();
110
        $map = [$this->getShortName($class->name) => $fqcn];
111
112
        $duplicates = [];
113
        foreach ($allClasses as $subClassCandidate) {
114
            if (is_subclass_of($subClassCandidate, $fqcn)) {
115
                $shortName = $this->getShortName($subClassCandidate);
116
117
                if (isset($map[$shortName])) {
118
                    $duplicates[] = $shortName;
119
                }
120
121
                $map[$shortName] = $subClassCandidate;
122
            }
123
        }
124
125
        if ($duplicates) {
126
            throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map);
127
        }
128
129
        $class->setDiscriminatorMap($map);
130
    }
131
132
    /**
133
     * Gets the lower-case short name of a class.
134
     *
135
     * @param string $className
136
     *
137
     * @return string
138
     */
139
    private function getShortName($className): string
140
    {
141
        $nameConverter = new CamelCaseToSnakeCaseNameConverter();
142
        $name = array_search($className, self::$mappingKeys, true);
143
        if (!$name) {
144
            $parts = explode("\\", $className);
145
            $lastPart = end($parts);
146
            $name = $nameConverter->normalize($lastPart);
147
        }
148
        return $name;
149
    }
150
}
151