Test Failed
Push — develop ( 01a8a8...14ce66 )
by Guilherme
65:10
created

AbstractClassMetadataFactory::normalizeClassName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping;
6
7
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Doctrine\ORM\Mapping\ClassMetadataFactory.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use Doctrine\Common\Persistence\Mapping\MappingException as CommonMappingException;
9
use Doctrine\Common\Cache\Cache;
10
use Doctrine\Common\Util\ClassUtils;
11
use Doctrine\ORM\Reflection\ReflectionService;
12
use Doctrine\ORM\Reflection\RuntimeReflectionService;
13
use ReflectionException;
14
15
/**
16
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
17
 * metadata mapping information of a class which describes how a class should be mapped
18
 * to a relational database.
19
 *
20
 * This class was abstracted from the ORM ClassMetadataFactory.
21
 *
22
 * @since  2.2
23
 * @author Benjamin Eberlei <[email protected]>
24
 * @author Guilherme Blanco <[email protected]>
25
 * @author Jonathan Wage <[email protected]>
26
 * @author Roman Borschel <[email protected]>
27
 */
28
abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
29
{
30
    /**
31
     * Salt used by specific Object Manager implementation.
32
     *
33
     * @var string
34
     */
35
    protected $cacheSalt = '$CLASSMETADATA';
36
37
    /**
38
     * @var \Doctrine\Common\Cache\Cache|null
39
     */
40
    private $cacheDriver;
41
42
    /**
43
     * @var ClassMetadata[]
44
     */
45
    private $loadedMetadata = [];
46
47
    /**
48
     * @var bool
49
     */
50
    protected $initialized = false;
51
52
    /**
53
     * @var ReflectionService|null
54
     */
55
    protected $reflectionService;
56
57
    /**
58
     * Sets the cache driver used by the factory to cache ClassMetadata instances.
59
     *
60
     * @param \Doctrine\Common\Cache\Cache $cacheDriver
61
     *
62
     * @return void
63
     */
64
    public function setCacheDriver(?Cache $cacheDriver = null) : void
65
    {
66
        $this->cacheDriver = $cacheDriver;
67
    }
68
69
    /**
70
     * Gets the cache driver used by the factory to cache ClassMetadata instances.
71
     *
72
     * @return Cache|null
73
     */
74
    public function getCacheDriver() : ?Cache
75
    {
76
        return $this->cacheDriver;
77
    }
78
79
    /**
80
     * Returns an array of all the loaded metadata currently in memory.
81
     *
82
     * @return ClassMetadata[]
83
     */
84
    public function getLoadedMetadata() : array
85
    {
86
        return $this->loadedMetadata;
87
    }
88
89
    /**
90
     * Sets the reflectionService.
91
     *
92
     * @param ReflectionService $reflectionService
93
     *
94
     * @return void
95
     */
96
    public function setReflectionService(ReflectionService $reflectionService) : void
97
    {
98
        $this->reflectionService = $reflectionService;
99
    }
100
101
    /**
102
     * Gets the reflection service associated with this metadata factory.
103
     *
104
     * @return ReflectionService
105
     */
106
    public function getReflectionService() : ReflectionService
107
    {
108
        if ($this->reflectionService === null) {
109
            $this->reflectionService = new RuntimeReflectionService();
110
        }
111
112
        return $this->reflectionService;
113
    }
114
115
    /**
116
     * Checks whether the factory has the metadata for a class loaded already.
117
     *
118
     * @param string $className
119
     *
120
     * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
121
     */
122
    public function hasMetadataFor($className) : bool
123
    {
124
        return isset($this->loadedMetadata[$className]);
125
    }
126
127
    /**
128
     * Sets the metadata descriptor for a specific class.
129
     *
130
     * NOTE: This is only useful in very special cases, like when generating proxy classes.
131
     *
132
     * @param string        $className
133
     * @param ClassMetadata $class
134
     *
135
     * @return void
136
     */
137
    public function setMetadataFor($className, $class) : void
138
    {
139
        $this->loadedMetadata[$className] = $class;
140
    }
141
142
    /**
143
     * Forces the factory to load the metadata of all classes known to the underlying
144
     * mapping driver.
145
     *
146
     * @return array The ClassMetadata instances of all mapped classes.
147
     *
148
     * @throws \InvalidArgumentException
149
     * @throws \ReflectionException
150
     * @throws MappingException
151
     */
152
    public function getAllMetadata() : array
153
    {
154
        if (! $this->initialized) {
155
            $this->initialize();
156
        }
157
158
        $driver = $this->getDriver();
159
        $metadata = [];
160
161
        foreach ($driver->getAllClassNames() as $className) {
162
            $metadata[] = $this->getMetadataFor($className);
163
        }
164
165
        return $metadata;
166
    }
167
168
    /**
169
     * Gets the class metadata descriptor for a class.
170
     *
171
     * @param string $className The name of the class.
172
     *
173
     * @return ClassMetadata
174
     *
175
     * @throws \InvalidArgumentException
176
     * @throws ReflectionException
177
     * @throws CommonMappingException
178
     */
179
    public function getMetadataFor($className) : ClassMetadata
180
    {
181
        if (isset($this->loadedMetadata[$className])) {
182
            return $this->loadedMetadata[$className];
183
        }
184
185
        $realClassName = $this->normalizeClassName($className);
186
187
        if (isset($this->loadedMetadata[$realClassName])) {
188
            // We do not have the alias name in the map, include it
189
            return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
190
        }
191
192
        $metadataBuildingContext = $this->newClassMetadataBuildingContext();
193
        $loadingException        = null;
194
195
        try {
196
            if ($this->cacheDriver) {
197
                $cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt);
198
199
                if ($cached instanceof ClassMetadata) {
200
                    $this->loadedMetadata[$realClassName] = $cached;
201
202
                    $cached->wakeupReflection($metadataBuildingContext->getReflectionService());
203
                } else {
204
                    foreach ($this->loadMetadata($realClassName, $metadataBuildingContext) as $loadedClass) {
205
                        $loadedClassName = $loadedClass->getClassName();
206
207
                        $this->cacheDriver->save($loadedClassName . $this->cacheSalt, $loadedClass, null);
208
                    }
209
                }
210
            } else {
211
                $this->loadMetadata($realClassName, $metadataBuildingContext);
212
            }
213
        } catch (CommonMappingException $loadingException) {
214
            if (! $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName, $metadataBuildingContext)) {
215
                throw $loadingException;
216
            }
217
218
            $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
219
        }
220
221
        if ($className !== $realClassName) {
222
            // We do not have the alias name in the map, include it
223
            $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
224
        }
225
226
        $metadataBuildingContext->validate();
227
228
        return $this->loadedMetadata[$className];
229
    }
230
231
    /**
232
     * Loads the metadata of the class in question and all it's ancestors whose metadata
233
     * is still not loaded.
234
     *
235
     * Important: The class $name does not necessarily exist at this point here.
236
     * Scenarios in a code-generation setup might have access to XML/YAML
237
     * Mapping files without the actual PHP code existing here. That is why the
238
     * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface
239
     * should be used for reflection.
240
     *
241
     * @param string $name The name of the class for which the metadata should
242
     *                                                              get loaded.
243
     * @param ClassMetadataBuildingContext $metadataBuildingContext
244
     *
245
     * @return array<ClassMetadata>
246
     *
247
     * @throws \InvalidArgumentException
248
     */
249
    protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array
250
    {
251
        if ( ! $this->initialized) {
252
            $this->initialize();
253
        }
254
255
        $loaded = [];
256
257
        $parentClasses = $this->getParentClasses($name);
258
        $parentClasses[] = $name;
259
260
        // Move down the hierarchy of parent classes, starting from the topmost class
261
        $parent = null;
262
263
        foreach ($parentClasses as $className) {
264
            if (isset($this->loadedMetadata[$className])) {
265
                $parent = $this->loadedMetadata[$className];
266
267
                continue;
268
            }
269
270
            $class = $this->doLoadMetadata($className, $parent, $metadataBuildingContext);
271
272
            $this->loadedMetadata[$className] = $class;
273
274
            $parent = $class;
275
276
            $loaded[] = $class;
277
        }
278
279
        return $loaded;
280
    }
281
282
    /**
283
     * {@inheritDoc}
284
     */
285
    public function isTransient($className) : bool
286
    {
287
        if ( ! $this->initialized) {
288
            $this->initialize();
289
        }
290
291
        return $this->getDriver()->isTransient($this->normalizeClassName($className));
292
    }
293
294
    /**
295
     * Gets an array of parent classes for the given entity class.
296
     *
297
     * @param string $name
298
     *
299
     * @return array
300
     *
301
     * @throws \InvalidArgumentException
302
     */
303
    protected function getParentClasses($name) : array
304
    {
305
        // Collect parent classes, ignoring transient (not-mapped) classes.
306
        $parentClasses = [];
307
308
        foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
309
            if ( ! $this->getDriver()->isTransient($parentClass)) {
310
                $parentClasses[] = $parentClass;
311
            }
312
        }
313
314
        return $parentClasses;
315
    }
316
317
    /**
318
     * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
319
     *
320
     * Override this method to implement a fallback strategy for failed metadata loading
321
     *
322
     * @param string                       $className
323
     * @param ClassMetadataBuildingContext $metadataBuildingContext
324
     *
325
     * @return \Doctrine\ORM\Mapping\ClassMetadata|null
326
     */
327
    protected function onNotFoundMetadata(
328
        string $className,
329
        ClassMetadataBuildingContext $metadataBuildingContext
330
    ) : ?ClassMetadata
331
    {
332
        return null;
333
    }
334
335
    /**
336
     * @param string $className
337
     *
338
     * @return string
339
     */
340
    private function normalizeClassName(string $className) : string
341
    {
342
        if (strpos($className, ':') !== false) {
343
            [$namespaceAlias, $simpleClassName] = explode(':', $className, 2);
0 ignored issues
show
Bug introduced by
The variable $namespaceAlias does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $simpleClassName does not exist. Did you mean $className?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
344
345
            return $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
0 ignored issues
show
Bug introduced by
The variable $simpleClassName does not exist. Did you mean $className?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
346
        }
347
348
        return ClassUtils::getRealClass($className);
349
    }
350
351
    /**
352
     * Lazy initialization of this stuff, especially the metadata driver,
353
     * since these are not needed at all when a metadata cache is active.
354
     *
355
     * @return void
356
     */
357
    abstract protected function initialize() : void;
358
359
    /**
360
     * Gets the fully qualified class-name from the namespace alias.
361
     *
362
     * @param string $namespaceAlias
363
     * @param string $simpleClassName
364
     *
365
     * @return string
366
     */
367
    abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) : string;
368
369
    /**
370
     * Returns the mapping driver implementation.
371
     *
372
     * @return Driver\MappingDriver
373
     */
374
    abstract protected function getDriver() : Driver\MappingDriver;
375
376
    /**
377
     * Checks whether the class metadata is an entity.
378
     *
379
     * This method should return false for mapped superclasses or embedded classes.
380
     *
381
     * @param ClassMetadata $class
382
     *
383
     * @return bool
384
     */
385
    abstract protected function isEntity(ClassMetadata $class) : bool;
386
387
    /**
388
     * Creates a new ClassMetadata instance for the given class name.
389
     *
390
     * @param string                       $className
391
     * @param ClassMetadata|null           $parent
392
     * @param ClassMetadataBuildingContext $metadataBuildingContext
393
     *
394
     * @return ClassMetadata
395
     */
396
    abstract protected function doLoadMetadata(
397
        string $className,
398
        ?ClassMetadata $parent,
399
        ClassMetadataBuildingContext $metadataBuildingContext
400
    ) : ClassMetadata;
401
402
    /**
403
     * Creates a new ClassMetadataBuildingContext instance.
404
     *
405
     * @return ClassMetadataBuildingContext
406
     */
407
    abstract protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext;
408
}
409