Code

< 40 %
40-60 %
> 60 %
1
<?php
2
3
namespace Bankiru\Api\Doctrine;
4
5
use Bankiru\Api\Doctrine\Exception\MappingException;
6
use Bankiru\Api\Doctrine\Mapping\EntityMetadata;
7
use Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory;
8
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
9
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
10
use Doctrine\Common\Persistence\Mapping\ReflectionService;
11
use ReflectionException;
12
13
class EntityMetadataFactory extends AbstractClassMetadataFactory
14
{
15
    /** @var  EntityManager */
16
    private $manager;
17
    /** @var  MappingDriver */
18
    private $driver;
19
20
    /** @var string[] */
21
    private $aliases = [];
22
23
    public function registerAlias($namespaceAlias, $namespace)
24
    {
25
        if (array_key_exists($namespaceAlias, $this->aliases)) {
26
            throw new \LogicException(sprintf('Alias "%s" is already registered', $namespaceAlias));
27
        }
28
29
        $this->aliases[$namespaceAlias] = rtrim($namespace, '\\');
30
    }
31
32
    /**
33
     * @param EntityManager $manager
34
     */
35
    public function setEntityManager($manager)
36
    {
37
        $this->manager = $manager;
38
    }
39
40
    /**
41
     * {@inheritDoc}
42
     */
43
    protected function loadMetadata($name)
44
    {
45
        $loaded = parent::loadMetadata($name);
46
        array_map([$this, 'resolveDiscriminatorValue'], array_map([$this, 'getMetadataFor'], $loaded));
47
48
        return $loaded;
49
    }
50
51
    /** {@inheritdoc} */
52
    protected function initialize()
53
    {
54
        $this->driver      = $this->manager->getConfiguration()->getDriver();
55
        $this->initialized = true;
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     * @throws MappingException
61
     */
62
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
63
    {
64
        if (!array_key_exists($namespaceAlias, $this->aliases)) {
65
            throw MappingException::unknownAlias($namespaceAlias);
66
        }
67
68
        return $this->aliases[$namespaceAlias] . $simpleClassName;
69
    }
70
71
    /** {@inheritdoc} */
72
    protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService)
73
    {
74
        if (!($class instanceof EntityMetadata)) {
75
            throw new \LogicException('Metadata is not supported');
76
        }
77
78
        /** @var EntityMetadata $class */
79
        $class->wakeupReflection($reflService);
80
    }
81
82
    /**
83
     * Initializes Reflection after ClassMetadata was constructed.
84
     *
85
     * @param ClassMetadata     $class
86
     * @param ReflectionService $reflService
87
     *
88
     * @return void
89
     */
90
    protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService)
91
    {
92
        if (!($class instanceof EntityMetadata)) {
93
            throw new \LogicException('Metadata is not supported');
94
        }
95
96
        /** @var EntityMetadata $class */
97
        $class->initializeReflection($reflService);
98
    }
99
100
    /**
101
     * Checks whether the class metadata is an entity.
102
     *
103
     * This method should return false for mapped superclasses or embedded classes.
104
     *
105
     * @param ClassMetadata $class
106
     *
107
     * @return boolean
108
     */
109
    protected function isEntity(ClassMetadata $class)
110
    {
111
        return true;
112
    }
113
114
    /** {@inheritdoc} */
115
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents)
116
    {
117
        /* @var $class EntityMetadata */
118
        /* @var $parent EntityMetadata */
119
        if ($parent) {
120
            $this->addInheritedFields($class, $parent);
121
            $this->addInheritedRelations($class, $parent);
122
            $class->setIdentifier($parent->identifier);
123
            $class->apiFactory     = $parent->apiFactory;
124
            $class->clientName     = $parent->clientName;
125
            $class->methodProvider = $parent->methodProvider;
126
127
            $class->setIdGeneratorType($parent->generatorType);
128
            $class->setDiscriminatorField($parent->discriminatorField);
129
            $class->setDiscriminatorMap($parent->discriminatorMap);
130
131
            if ($parent->isMappedSuperclass) {
132
                $class->setCustomRepositoryClass($parent->repositoryClass);
133
            }
134
        }
135
136
        // Invoke driver
137
        try {
138
            $this->getDriver()->loadMetadataForClass($class->getName(), $class);
139
        } catch (ReflectionException $e) {
140
            throw MappingException::nonExistingClass($class->getName());
141
        }
142
143
        if ($class->isRootEntity() && !$class->discriminatorMap) {
144
            $this->addDefaultDiscriminatorMap($class);
145
        }
146
    }
147
148
    /**
149
     * Returns the mapping driver implementation.
150
     *
151
     * @return \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver
152
     */
153
    protected function getDriver()
154
    {
155
        return $this->driver;
156
    }
157
158
    /**
159
     * Creates a new ClassMetadata instance for the given class name.
160
     *
161
     * @param string $className
162
     *
163
     * @return ClassMetadata
164
     */
165
    protected function newClassMetadataInstance($className)
166
    {
167
        return new EntityMetadata($className);
168
    }
169
170
    /**
171
     * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
172
     * map classes and looking for a fitting one.
173
     *
174
     * @param EntityMetadata $metadata
175
     *
176
     * @return void
177
     *
178
     * @throws MappingException
179
     */
180
    private function resolveDiscriminatorValue(EntityMetadata $metadata)
181
    {
182
        if ($metadata->discriminatorValue
183
            || !$metadata->discriminatorMap
184
            || $metadata->isMappedSuperclass
185
            || !$metadata->reflClass
186
            || $metadata->reflClass->isAbstract()
187
        ) {
188
            return;
189
        }
190
        // minor optimization: avoid loading related metadata when not needed
191
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
192
            if ($discriminatorClass === $metadata->name) {
193
                $metadata->discriminatorValue = $discriminatorValue;
194
195
                return;
196
            }
197
        }
198
        // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata
199
        foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) {
200
            if ($metadata->name === $this->getMetadataFor($discriminatorClass)->getName()) {
201
                $metadata->discriminatorValue = $discriminatorValue;
202
203
                return;
204
            }
205
        }
206
207
        throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->name, $metadata->rootEntityName);
208
    }
209
210
    /**
211
     * Adds a default discriminator map if no one is given
212
     *
213
     * If an entity is of any inheritance type and does not contain a
214
     * discriminator map, then the map is generated automatically. This process
215
     * is expensive computation wise.
216
     *
217
     * The automatically generated discriminator map contains the lowercase short name of
218
     * each class as key.
219
     *
220
     * @param EntityMetadata $class
221
     *
222
     * @throws MappingException
223
     */
224
    private function addDefaultDiscriminatorMap(EntityMetadata $class)
225
    {
226
        $allClasses = $this->driver->getAllClassNames();
227
        $fqcn       = $class->getName();
228
        $map        = [$this->getShortName($class->name) => $fqcn];
229
        $duplicates = [];
230
        foreach ($allClasses as $subClassCandidate) {
231
            if (is_subclass_of($subClassCandidate, $fqcn)) {
232
                $shortName = $this->getShortName($subClassCandidate);
233
                if (isset($map[$shortName])) {
234
                    $duplicates[] = $shortName;
235
                }
236
                $map[$shortName] = $subClassCandidate;
237
            }
238
        }
239
        if ($duplicates) {
240
            throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map);
241
        }
242
        $class->setDiscriminatorMap($map);
243
    }
244
245
    /**
246
     * Gets the lower-case short name of a class.
247
     *
248
     * @param string $className
249
     *
250
     * @return string
251
     */
252
    private function getShortName($className)
253
    {
254
        if (strpos($className, "\\") === false) {
255
            return strtolower($className);
256
        }
257
        $parts = explode("\\", $className);
258
259
        return strtolower(end($parts));
260
    }
261
262
    /**
263
     * Adds inherited fields to the subclass mapping.
264
     *
265
     * @param EntityMetadata $subClass
266
     * @param EntityMetadata $parentClass
267
     *
268
     * @return void
269
     */
270
    private function addInheritedFields(EntityMetadata $subClass, EntityMetadata $parentClass)
271
    {
272
        foreach ($parentClass->fields as $mapping) {
273
            if (!isset($mapping['inherited']) && !$parentClass->isMappedSuperclass) {
274
                $mapping['inherited'] = $parentClass->name;
275
            }
276
            if (!isset($mapping['declared'])) {
277
                $mapping['declared'] = $parentClass->name;
278
            }
279
            $subClass->addInheritedFieldMapping($mapping);
280
        }
281
        foreach ($parentClass->reflFields as $name => $field) {
282
            $subClass->reflFields[$name] = $field;
283
        }
284
    }
285
286
    /**
287
     * Adds inherited association mappings to the subclass mapping.
288
     *
289
     * @param EntityMetadata $subClass
290
     * @param EntityMetadata $parentClass
291
     *
292
     * @return void
293
     *
294
     * @throws MappingException
295
     */
296
    private function addInheritedRelations(EntityMetadata $subClass, EntityMetadata $parentClass)
297
    {
298
        foreach ($parentClass->associations as $mapping) {
299
            if (!isset($mapping['inherited']) && !$parentClass->isMappedSuperclass) {
300
                $mapping['inherited'] = $parentClass->name;
301
            }
302
            if (!isset($mapping['declared'])) {
303
                $mapping['declared'] = $parentClass->name;
304
            }
305
            $subClass->addInheritedAssociationMapping($mapping);
306
        }
307
    }
308
}
309