Passed
Push — develop ( f481b8...ef422d )
by Daniel
05:16
created

DiscriminatorMappingExtension::getShortName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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