Test Setup Failed
Push — develop ( 6f26e1...ba9041 )
by Guilherme
63:58
created

AbstractClassMetadataFactory::getParentClasses()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
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
    private $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
     * Forces the factory to load the metadata of all classes known to the underlying
91
     * mapping driver.
92
     *
93
     * @return array The ClassMetadata instances of all mapped classes.
94
     *
95
     * @throws \InvalidArgumentException
96
     * @throws \ReflectionException
97
     * @throws MappingException
98
     */
99
    public function getAllMetadata() : array
100
    {
101
        if (! $this->initialized) {
102
            $this->initialize();
103
        }
104
105
        $driver = $this->getDriver();
106
        $metadata = [];
107
108
        foreach ($driver->getAllClassNames() as $className) {
109
            $metadata[] = $this->getMetadataFor($className);
110
        }
111
112
        return $metadata;
113
    }
114
115
    /**
116
     * Lazy initialization of this stuff, especially the metadata driver,
117
     * since these are not needed at all when a metadata cache is active.
118
     *
119
     * @return void
120
     */
121
    abstract protected function initialize() : void;
122
123
    /**
124
     * Gets the fully qualified class-name from the namespace alias.
125
     *
126
     * @param string $namespaceAlias
127
     * @param string $simpleClassName
128
     *
129
     * @return string
130
     */
131
    abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) : string;
132
133
    /**
134
     * Returns the mapping driver implementation.
135
     *
136
     * @return Driver\MappingDriver
137
     */
138
    abstract protected function getDriver() : Driver\MappingDriver;
139
140
    /**
141
     * Wakes up reflection after ClassMetadata gets unserialized from cache.
142
     *
143
     * @param ClassMetadata     $class
144
     * @param ReflectionService $reflService
145
     *
146
     * @return void
147
     */
148
    abstract protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService) : void;
149
150
    /**
151
     * Initializes Reflection after ClassMetadata was constructed.
152
     *
153
     * @param ClassMetadata     $class
154
     * @param ReflectionService $reflService
155
     *
156
     * @return void
157
     */
158
    abstract protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService) : void;
159
160
    /**
161
     * Checks whether the class metadata is an entity.
162
     *
163
     * This method should return false for mapped superclasses or embedded classes.
164
     *
165
     * @param ClassMetadata $class
166
     *
167
     * @return bool
168
     */
169
    abstract protected function isEntity(ClassMetadata $class) : bool;
170
171
    /**
172
     * Gets the class metadata descriptor for a class.
173
     *
174
     * @param string $className The name of the class.
175
     *
176
     * @return ClassMetadata
177
     *
178
     * @throws \InvalidArgumentException
179
     * @throws ReflectionException
180
     * @throws MappingException
181
     */
182
    public function getMetadataFor($className) : ClassMetadata
183
    {
184
        if (isset($this->loadedMetadata[$className])) {
185
            return $this->loadedMetadata[$className];
186
        }
187
188
        // Check for namespace alias
189
        if (strpos($className, ':') !== false) {
190
            [$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...
191
192
            $realClassName = $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...
193
        } else {
194
            $realClassName = ClassUtils::getRealClass($className);
195
        }
196
197
        if (isset($this->loadedMetadata[$realClassName])) {
198
            // We do not have the alias name in the map, include it
199
            return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
200
        }
201
202
        $metadataBuildingContext = $this->newClassMetadataBuildingContext();
203
        $loadingException        = null;
204
205
        try {
206
            if ($this->cacheDriver) {
207
                $cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt);
208
209
                if ($cached instanceof ClassMetadata) {
210
                    $this->loadedMetadata[$realClassName] = $cached;
211
212
                    $this->wakeupReflection($cached, $this->getReflectionService());
213
                } else {
214
                    foreach ($this->loadMetadata($realClassName, $metadataBuildingContext) as $loadedClassName) {
215
                        $this->cacheDriver->save(
216
                            $loadedClassName . $this->cacheSalt,
217
                            $this->loadedMetadata[$loadedClassName],
218
                            null
219
                        );
220
                    }
221
                }
222
            } else {
223
                $this->loadMetadata($realClassName, $metadataBuildingContext);
224
            }
225
        } catch (CommonMappingException $loadingException) {
226
            if (! $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName, $metadataBuildingContext)) {
227
                throw $loadingException;
228
            }
229
230
            $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
231
        }
232
233
        if ($className !== $realClassName) {
234
            // We do not have the alias name in the map, include it
235
            $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
236
        }
237
238
        $metadataBuildingContext->validate();
239
240
        return $this->loadedMetadata[$className];
241
    }
242
243
    /**
244
     * Checks whether the factory has the metadata for a class loaded already.
245
     *
246
     * @param string $className
247
     *
248
     * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
249
     */
250
    public function hasMetadataFor($className) : bool
251
    {
252
        return isset($this->loadedMetadata[$className]);
253
    }
254
255
    /**
256
     * Sets the metadata descriptor for a specific class.
257
     *
258
     * NOTE: This is only useful in very special cases, like when generating proxy classes.
259
     *
260
     * @param string        $className
261
     * @param ClassMetadata $class
262
     *
263
     * @return void
264
     */
265
    public function setMetadataFor($className, $class) : void
266
    {
267
        $this->loadedMetadata[$className] = $class;
268
    }
269
270
    /**
271
     * Gets an array of parent classes for the given entity class.
272
     *
273
     * @param string $name
274
     *
275
     * @return array
276
     *
277
     * @throws \InvalidArgumentException
278
     */
279
    protected function getParentClasses($name) : array
280
    {
281
        // Collect parent classes, ignoring transient (not-mapped) classes.
282
        $parentClasses = [];
283
284
        foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
285
            if ( ! $this->getDriver()->isTransient($parentClass)) {
286
                $parentClasses[] = $parentClass;
287
            }
288
        }
289
290
        return $parentClasses;
291
    }
292
293
    /**
294
     * Loads the metadata of the class in question and all it's ancestors whose metadata
295
     * is still not loaded.
296
     *
297
     * Important: The class $name does not necessarily exist at this point here.
298
     * Scenarios in a code-generation setup might have access to XML/YAML
299
     * Mapping files without the actual PHP code existing here. That is why the
300
     * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface
301
     * should be used for reflection.
302
     *
303
     * @param string $name The name of the class for which the metadata should
304
     *                                                              get loaded.
305
     * @param ClassMetadataBuildingContext $metadataBuildingContext
306
     *
307
     * @return array
308
     *
309
     * @throws \InvalidArgumentException
310
     */
311
    protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array
312
    {
313
        if ( ! $this->initialized) {
314
            $this->initialize();
315
        }
316
317
        $loaded = [];
318
319
        $parentClasses = $this->getParentClasses($name);
320
        $parentClasses[] = $name;
321
322
        // Move down the hierarchy of parent classes, starting from the topmost class
323
        $parent      = null;
324
        $reflService = $this->getReflectionService();
325
326
        foreach ($parentClasses as $className) {
327
            if (isset($this->loadedMetadata[$className])) {
328
                $parent = $this->loadedMetadata[$className];
329
330
                continue;
331
            }
332
333
            $class = $this->newClassMetadataInstance($className, $metadataBuildingContext);
334
335
            if ($parent) {
336
                $class->setParent($parent);
337
            }
338
339
            $this->initializeReflection($class, $reflService);
340
            $this->doLoadMetadata($class, $metadataBuildingContext);
341
342
            $this->loadedMetadata[$className] = $class;
343
344
            $parent = $class;
345
346
            $this->wakeupReflection($class, $reflService);
347
348
            $loaded[] = $className;
349
        }
350
351
        return $loaded;
352
    }
353
354
    /**
355
     * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
356
     *
357
     * Override this method to implement a fallback strategy for failed metadata loading
358
     *
359
     * @param string                       $className
360
     * @param ClassMetadataBuildingContext $metadataBuildingContext
361
     *
362
     * @return \Doctrine\ORM\Mapping\ClassMetadata|null
363
     */
364
    protected function onNotFoundMetadata(
365
        string $className,
366
        ClassMetadataBuildingContext $metadataBuildingContext
367
    ) : ?ClassMetadata
368
    {
369
        return null;
370
    }
371
372
    /**
373
     * Actually loads the metadata from the underlying metadata.
374
     *
375
     * @param ClassMetadata                $class
376
     * @param ClassMetadataBuildingContext $metadataBuildingContext
377
     *
378
     * @return void
379
     */
380
    abstract protected function doLoadMetadata(
381
        ClassMetadata $class,
382
        ClassMetadataBuildingContext $metadataBuildingContext
383
    ) : void;
384
385
    /**
386
     * Creates a new ClassMetadata instance for the given class name.
387
     *
388
     * @param string                       $className
389
     * @param ClassMetadataBuildingContext $metadataBuildingContext
390
     *
391
     * @return ClassMetadata
392
     */
393
    abstract protected function newClassMetadataInstance(
394
        string $className,
395
        ClassMetadataBuildingContext $metadataBuildingContext
396
    ) : ClassMetadata;
397
398
    /**
399
     * Creates a new ClassMetadataBuildingContext instance.
400
     *
401
     * @return ClassMetadataBuildingContext
402
     */
403
    abstract protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext;
404
405
    /**
406
     * {@inheritDoc}
407
     */
408
    public function isTransient($class) : bool
409
    {
410
        if ( ! $this->initialized) {
411
            $this->initialize();
412
        }
413
414
        // Check for namespace alias
415
        if (strpos($class, ':') !== false) {
416
            [$namespaceAlias, $simpleClassName] = explode(':', $class, 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 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...
417
418
            $class = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
419
        }
420
421
        return $this->getDriver()->isTransient($class);
422
    }
423
424
    /**
425
     * Sets the reflectionService.
426
     *
427
     * @param ReflectionService $reflectionService
428
     *
429
     * @return void
430
     */
431
    public function setReflectionService(ReflectionService $reflectionService) : void
432
    {
433
        $this->reflectionService = $reflectionService;
434
    }
435
436
    /**
437
     * Gets the reflection service associated with this metadata factory.
438
     *
439
     * @return ReflectionService
440
     */
441
    public function getReflectionService() : ReflectionService
442
    {
443
        if ($this->reflectionService === null) {
444
            $this->reflectionService = new RuntimeReflectionService();
445
        }
446
447
        return $this->reflectionService;
448
    }
449
}
450