Failed Conditions
Push — master ( e747f7...5b15a6 )
by Guilherme
19:57
created

setReflectionService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping;
6
7
use Doctrine\Common\Cache\Cache;
8
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. Consider defining an alias.

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...
9
use Doctrine\Common\Persistence\Mapping\MappingException as CommonMappingException;
10
use Doctrine\ORM\Reflection\ReflectionService;
11
use Doctrine\ORM\Reflection\RuntimeReflectionService;
12
use Doctrine\ORM\Utility\StaticClassNameConverter;
13
use InvalidArgumentException;
14
use ReflectionException;
15
use function array_reverse;
16
17
/**
18
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
19
 * metadata mapping information of a class which describes how a class should be mapped
20
 * to a relational database.
21
 *
22
 * This class was abstracted from the ORM ClassMetadataFactory.
23
 */
24
abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
25
{
26
    /**
27
     * Salt used by specific Object Manager implementation.
28
     *
29
     * @var string
30
     */
31
    protected $cacheSalt = '$CLASSMETADATA';
32
33
    /** @var Cache|null */
34
    private $cacheDriver;
35
36
    /** @var ClassMetadata[] */
37
    private $loadedMetadata = [];
38
39
    /** @var bool */
40
    protected $initialized = false;
41
42
    /** @var ReflectionService|null */
43
    protected $reflectionService;
44
45
    /**
46
     * Sets the cache driver used by the factory to cache ClassMetadata instances.
47
     */
48 2277
    public function setCacheDriver(?Cache $cacheDriver = null) : void
49
    {
50 2277
        $this->cacheDriver = $cacheDriver;
51 2277
    }
52
53
    /**
54
     * Gets the cache driver used by the factory to cache ClassMetadata instances.
55
     */
56
    public function getCacheDriver() : ?Cache
57
    {
58
        return $this->cacheDriver;
59
    }
60
61
    /**
62
     * Returns an array of all the loaded metadata currently in memory.
63
     *
64
     * @return ClassMetadata[]
65
     */
66
    public function getLoadedMetadata() : array
67
    {
68
        return $this->loadedMetadata;
69
    }
70
71
    /**
72
     * Sets the reflectionService.
73
     */
74
    public function setReflectionService(ReflectionService $reflectionService) : void
75
    {
76
        $this->reflectionService = $reflectionService;
77
    }
78
79
    /**
80
     * Gets the reflection service associated with this metadata factory.
81
     */
82 1967
    public function getReflectionService() : ReflectionService
83
    {
84 1967
        if ($this->reflectionService === null) {
85 1967
            $this->reflectionService = new RuntimeReflectionService();
86
        }
87
88 1967
        return $this->reflectionService;
89
    }
90
91
    /**
92
     * Checks whether the factory has the metadata for a class loaded already.
93
     *
94
     * @param string $className
95
     *
96
     * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
97
     */
98 66
    public function hasMetadataFor($className) : bool
99
    {
100 66
        return isset($this->loadedMetadata[$className]);
101
    }
102
103
    /**
104
     * Sets the metadata descriptor for a specific class.
105
     *
106
     * NOTE: This is only useful in very special cases, like when generating proxy classes.
107
     *
108
     * @param string        $className
109
     * @param ClassMetadata $class
110
     */
111 5
    public function setMetadataFor($className, $class) : void
112
    {
113 5
        $this->loadedMetadata[$className] = $class;
114 5
    }
115
116
    /**
117
     * Forces the factory to load the metadata of all classes known to the underlying
118
     * mapping driver.
119
     *
120
     * @return ClassMetadata[] The ClassMetadata instances of all mapped classes.
121
     *
122
     * @throws InvalidArgumentException
123
     * @throws ReflectionException
124
     * @throws MappingException
125
     * @throws CommonMappingException
126
     */
127 56
    public function getAllMetadata() : array
128
    {
129 56
        if (! $this->initialized) {
130 56
            $this->initialize();
131
        }
132
133 56
        $driver   = $this->getDriver();
134 56
        $metadata = [];
135
136 56
        foreach ($driver->getAllClassNames() as $className) {
137 55
            $metadata[] = $this->getMetadataFor($className);
138
        }
139
140 56
        return $metadata;
0 ignored issues
show
introduced by
The expression return $metadata returns an array which contains values of type Doctrine\ORM\Mapping\ClassMetadata which are incompatible with the return type Doctrine\Common\Persistence\Mapping\ClassMetadata mandated by Doctrine\Common\Persiste...ctory::getAllMetadata().
Loading history...
141
    }
142
143
    /**
144
     * Gets the class metadata descriptor for a class.
145
     *
146
     * @param string $className The name of the class.
147
     *
148
     * @throws InvalidArgumentException
149
     * @throws CommonMappingException
150
     */
151 1972
    public function getMetadataFor($className) : ClassMetadata
152
    {
153 1972
        if (isset($this->loadedMetadata[$className])) {
154 1646
            return $this->loadedMetadata[$className];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->loadedMetadata[$className] returns the type Doctrine\ORM\Mapping\ClassMetadata which is incompatible with the return type mandated by Doctrine\Common\Persiste...ctory::getMetadataFor() of Doctrine\Common\Persistence\Mapping\ClassMetadata.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
155
        }
156
157 1967
        $entityClassName = StaticClassNameConverter::getRealClass($className);
158
159 1967
        if (isset($this->loadedMetadata[$entityClassName])) {
160
            // We do not have the alias name in the map, include it
161 214
            return $this->loadedMetadata[$className] = $this->loadedMetadata[$entityClassName];
162
        }
163
164 1967
        $metadataBuildingContext = $this->newClassMetadataBuildingContext();
165 1967
        $loadingException        = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $loadingException is dead and can be removed.
Loading history...
166
167
        try {
168 1967
            if ($this->cacheDriver) {
169 1859
                $cached = $this->cacheDriver->fetch($entityClassName . $this->cacheSalt);
170
171 1859
                if ($cached instanceof ClassMetadata) {
172 1632
                    $this->loadedMetadata[$entityClassName] = $cached;
173
174 1632
                    $cached->wakeupReflection($metadataBuildingContext->getReflectionService());
175
                } else {
176 1859
                    foreach ($this->loadMetadata($entityClassName, $metadataBuildingContext) as $loadedClass) {
177 267
                        $loadedClassName = $loadedClass->getClassName();
178
179 267
                        $this->cacheDriver->save($loadedClassName . $this->cacheSalt, $loadedClass, null);
180
                    }
181
                }
182
            } else {
183 1959
                $this->loadMetadata($entityClassName, $metadataBuildingContext);
184
            }
185 27
        } catch (CommonMappingException $loadingException) {
186 12
            $fallbackMetadataResponse = $this->onNotFoundMetadata($entityClassName, $metadataBuildingContext);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fallbackMetadataResponse is correct as $this->onNotFoundMetadat...etadataBuildingContext) targeting Doctrine\ORM\Mapping\Abs...y::onNotFoundMetadata() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
187
188 12
            if (! $fallbackMetadataResponse) {
0 ignored issues
show
introduced by
$fallbackMetadataResponse is of type null, thus it always evaluated to false.
Loading history...
189 10
                throw $loadingException;
190
            }
191
192 2
            $this->loadedMetadata[$entityClassName] = $fallbackMetadataResponse;
193
        }
194
195 1948
        if ($className !== $entityClassName) {
196
            // We do not have the alias name in the map, include it
197 3
            $this->loadedMetadata[$className] = $this->loadedMetadata[$entityClassName];
198
        }
199
200 1948
        $metadataBuildingContext->validate();
201
202 1948
        return $this->loadedMetadata[$entityClassName];
203
    }
204
205
    /**
206
     * Loads the metadata of the class in question and all it's ancestors whose metadata
207
     * is still not loaded.
208
     *
209
     * Important: The class $name does not necessarily exist at this point here.
210
     * Scenarios in a code-generation setup might have access to XML
211
     * Mapping files without the actual PHP code existing here. That is why the
212
     * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface
213
     * should be used for reflection.
214
     *
215
     * @param string $name The name of the class for which the metadata should
216
     *                                                              get loaded.
217
     *
218
     * @return ClassMetadata[]
219
     *
220
     * @throws InvalidArgumentException
221
     */
222 389
    protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array
223
    {
224 389
        if (! $this->initialized) {
225 384
            $this->initialize();
226
        }
227
228 389
        $loaded = [];
229
230 389
        $parentClasses   = $this->getParentClasses($name);
231 378
        $parentClasses[] = $name;
232
233
        // Move down the hierarchy of parent classes, starting from the topmost class
234 378
        $parent = null;
235
236 378
        foreach ($parentClasses as $className) {
237 378
            if (isset($this->loadedMetadata[$className])) {
238 71
                $parent = $this->loadedMetadata[$className];
239
240 71
                continue;
241
            }
242
243 378
            $class = $this->doLoadMetadata($className, $parent, $metadataBuildingContext);
244
245 366
            $this->loadedMetadata[$className] = $class;
246
247 366
            $parent = $class;
248
249 366
            $loaded[] = $class;
250
        }
251
252 364
        return $loaded;
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258 48
    public function isTransient($className) : bool
259
    {
260 48
        if (! $this->initialized) {
261 15
            $this->initialize();
262
        }
263
264 48
        $entityClassName = StaticClassNameConverter::getRealClass($className);
265
266 48
        return $this->getDriver()->isTransient($entityClassName);
267
    }
268
269
    /**
270
     * Gets an array of parent classes for the given entity class.
271
     *
272
     * @param string $name
273
     *
274
     * @return string[]
275
     *
276
     * @throws InvalidArgumentException
277
     */
278 389
    protected function getParentClasses($name) : array
279
    {
280
        // Collect parent classes, ignoring transient (not-mapped) classes.
281 389
        $parentClasses = [];
282
283 389
        foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
284 104
            if (! $this->getDriver()->isTransient($parentClass)) {
285 99
                $parentClasses[] = $parentClass;
286
            }
287
        }
288
289 378
        return $parentClasses;
290
    }
291
292
    /**
293
     * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
294
     *
295
     * Override this method to implement a fallback strategy for failed metadata loading
296
     */
297
    protected function onNotFoundMetadata(
298
        string $className,
299
        ClassMetadataBuildingContext $metadataBuildingContext
300
    ) : ?ClassMetadata {
301
        return null;
302
    }
303
304
    /**
305
     * Lazy initialization of this stuff, especially the metadata driver,
306
     * since these are not needed at all when a metadata cache is active.
307
     */
308
    abstract protected function initialize() : void;
309
310
    /**
311
     * Returns the mapping driver implementation.
312
     */
313
    abstract protected function getDriver() : Driver\MappingDriver;
314
315
    /**
316
     * Checks whether the class metadata is an entity.
317
     *
318
     * This method should return false for mapped superclasses or embedded classes.
319
     */
320
    abstract protected function isEntity(ClassMetadata $class) : bool;
321
322
    /**
323
     * Creates a new ClassMetadata instance for the given class name.
324
     */
325
    abstract protected function doLoadMetadata(
326
        string $className,
327
        ?ClassMetadata $parent,
328
        ClassMetadataBuildingContext $metadataBuildingContext
329
    ) : ClassMetadata;
330
331
    /**
332
     * Creates a new ClassMetadataBuildingContext instance.
333
     */
334
    abstract protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext;
335
}
336