Failed Conditions
Pull Request — master (#2)
by Jonathan
02:45
created

AbstractClassMetadataFactory::getParentClasses()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
ccs 6
cts 7
cp 0.8571
cc 3
eloc 6
nc 3
nop 1
crap 3.0261
1
<?php
2
namespace Doctrine\Common\Persistence\Mapping;
3
4
use Doctrine\Common\Cache\Cache;
5
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
6
use Doctrine\Common\Util\ClassUtils;
7
use ReflectionException;
8
use function array_reverse;
9
use function array_unshift;
10
use function explode;
11
use function strpos;
12
13
/**
14
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
15
 * metadata mapping informations of a class which describes how a class should be mapped
16
 * to a relational database.
17
 *
18
 * This class was abstracted from the ORM ClassMetadataFactory.
19
 *
20
 */
21
abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
22
{
23
    /**
24
     * Salt used by specific Object Manager implementation.
25
     *
26
     * @var string
27
     */
28
    protected $cacheSalt = '$CLASSMETADATA';
29
30
    /** @var Cache|null */
31
    private $cacheDriver;
32
33
    /** @var ClassMetadata[] */
34
    private $loadedMetadata = [];
35
36
    /** @var bool */
37
    protected $initialized = false;
38
39
    /** @var ReflectionService|null */
40
    private $reflectionService = null;
41
42
    /**
43
     * Sets the cache driver used by the factory to cache ClassMetadata instances.
44
     *
45
     *
46
     * @return void
47
     */
48 4
    public function setCacheDriver(?Cache $cacheDriver = null)
49
    {
50 4
        $this->cacheDriver = $cacheDriver;
51 4
    }
52
53
    /**
54
     * Gets the cache driver used by the factory to cache ClassMetadata instances.
55
     *
56
     * @return Cache|null
57
     */
58 1
    public function getCacheDriver()
59
    {
60 1
        return $this->cacheDriver;
61
    }
62
63
    /**
64
     * Returns an array of all the loaded metadata currently in memory.
65
     *
66
     * @return ClassMetadata[]
67
     */
68
    public function getLoadedMetadata()
69
    {
70
        return $this->loadedMetadata;
71
    }
72
73
    /**
74
     * Forces the factory to load the metadata of all classes known to the underlying
75
     * mapping driver.
76
     *
77
     * @return ClassMetadata[] The ClassMetadata instances of all mapped classes.
78
     */
79
    public function getAllMetadata()
80
    {
81
        if (! $this->initialized) {
82
            $this->initialize();
83
        }
84
85
        $driver   = $this->getDriver();
86
        $metadata = [];
87
        foreach ($driver->getAllClassNames() as $className) {
88
            $metadata[] = $this->getMetadataFor($className);
89
        }
90
91
        return $metadata;
92
    }
93
94
    /**
95
     * Lazy initialization of this stuff, especially the metadata driver,
96
     * since these are not needed at all when a metadata cache is active.
97
     *
98
     * @return void
99
     */
100
    abstract protected function initialize();
101
102
    /**
103
     * Gets the fully qualified class-name from the namespace alias.
104
     *
105
     * @param string $namespaceAlias
106
     * @param string $simpleClassName
107
     *
108
     * @return string
109
     */
110
    abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName);
111
112
    /**
113
     * Returns the mapping driver implementation.
114
     *
115
     * @return MappingDriver
116
     */
117
    abstract protected function getDriver();
118
119
    /**
120
     * Wakes up reflection after ClassMetadata gets unserialized from cache.
121
     *
122
     *
123
     * @return void
124
     */
125
    abstract protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService);
126
127
    /**
128
     * Initializes Reflection after ClassMetadata was constructed.
129
     *
130
     *
131
     * @return void
132
     */
133
    abstract protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService);
134
135
    /**
136
     * Checks whether the class metadata is an entity.
137
     *
138
     * This method should return false for mapped superclasses or embedded classes.
139
     *
140
     *
141
     * @return bool
142
     */
143
    abstract protected function isEntity(ClassMetadata $class);
144
145
    /**
146
     * Gets the class metadata descriptor for a class.
147
     *
148
     * @param string $className The name of the class.
149
     *
150
     * @return ClassMetadata
151
     *
152
     * @throws ReflectionException
153
     * @throws MappingException
154
     */
155 10
    public function getMetadataFor($className)
156
    {
157 10
        if (isset($this->loadedMetadata[$className])) {
158
            return $this->loadedMetadata[$className];
159
        }
160
161
        // Check for namespace alias
162 10
        if (strpos($className, ':') !== false) {
163 2
            list($namespaceAlias, $simpleClassName) = explode(':', $className, 2);
164
165 2
            $realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
166
        } else {
167 8
            $realClassName = ClassUtils::getRealClass($className);
168
        }
169
170 10
        if (isset($this->loadedMetadata[$realClassName])) {
171
            // We do not have the alias name in the map, include it
172
            return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
173
        }
174
175 10
        $loadingException = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $loadingException is dead and can be removed.
Loading history...
176
177
        try {
178 10
            if ($this->cacheDriver) {
179 3
                $cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt);
180 3
                if ($cached instanceof ClassMetadata) {
181 1
                    $this->loadedMetadata[$realClassName] = $cached;
182
183 1
                    $this->wakeupReflection($cached, $this->getReflectionService());
184
                } else {
185 2
                    foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
186 1
                        $this->cacheDriver->save(
187 1
                            $loadedClassName . $this->cacheSalt,
188 1
                            $this->loadedMetadata[$loadedClassName],
189 2
                            null
190
                        );
191
                    }
192
                }
193
            } else {
194 9
                $this->loadMetadata($realClassName);
195
            }
196 5
        } catch (MappingException $loadingException) {
197 5
            $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fallbackMetadataResponse is correct as $this->onNotFoundMetadata($realClassName) targeting Doctrine\Common\Persiste...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...
198
199 5
            if (! $fallbackMetadataResponse) {
0 ignored issues
show
introduced by
$fallbackMetadataResponse is of type null, thus it always evaluated to false.
Loading history...
200 3
                throw $loadingException;
201
            }
202
203 2
            $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
204
        }
205
206 7
        if ($className !== $realClassName) {
207
            // We do not have the alias name in the map, include it
208 1
            $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
209
        }
210
211 7
        return $this->loadedMetadata[$className];
212
    }
213
214
    /**
215
     * Checks whether the factory has the metadata for a class loaded already.
216
     *
217
     * @param string $className
218
     *
219
     * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
220
     */
221 3
    public function hasMetadataFor($className)
222
    {
223 3
        return isset($this->loadedMetadata[$className]);
224
    }
225
226
    /**
227
     * Sets the metadata descriptor for a specific class.
228
     *
229
     * NOTE: This is only useful in very special cases, like when generating proxy classes.
230
     *
231
     * @param string        $className
232
     * @param ClassMetadata $class
233
     *
234
     * @return void
235
     */
236
    public function setMetadataFor($className, $class)
237
    {
238
        $this->loadedMetadata[$className] = $class;
239
    }
240
241
    /**
242
     * Gets an array of parent classes for the given entity class.
243
     *
244
     * @param string $name
245
     *
246
     * @return string[]
247
     */
248 9
    protected function getParentClasses($name)
249
    {
250
        // Collect parent classes, ignoring transient (not-mapped) classes.
251 9
        $parentClasses = [];
252
253 9
        foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
254 3
            if ($this->getDriver()->isTransient($parentClass)) {
255
                continue;
256
            }
257
258 3
            $parentClasses[] = $parentClass;
259
        }
260
261 4
        return $parentClasses;
262
    }
263
264
    /**
265
     * Loads the metadata of the class in question and all it's ancestors whose metadata
266
     * is still not loaded.
267
     *
268
     * Important: The class $name does not necessarily exist at this point here.
269
     * Scenarios in a code-generation setup might have access to XML/YAML
270
     * Mapping files without the actual PHP code existing here. That is why the
271
     * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface
272
     * should be used for reflection.
273
     *
274
     * @param string $name The name of the class for which the metadata should get loaded.
275
     *
276
     * @return string[]
277
     */
278 9
    protected function loadMetadata($name)
279
    {
280 9
        if (! $this->initialized) {
281 9
            $this->initialize();
282
        }
283
284 9
        $loaded = [];
285
286 9
        $parentClasses   = $this->getParentClasses($name);
287 4
        $parentClasses[] = $name;
288
289
        // Move down the hierarchy of parent classes, starting from the topmost class
290 4
        $parent          = null;
291 4
        $rootEntityFound = false;
292 4
        $visited         = [];
293 4
        $reflService     = $this->getReflectionService();
294 4
        foreach ($parentClasses as $className) {
295 4
            if (isset($this->loadedMetadata[$className])) {
296
                $parent = $this->loadedMetadata[$className];
297
                if ($this->isEntity($parent)) {
298
                    $rootEntityFound = true;
299
                    array_unshift($visited, $className);
300
                }
301
                continue;
302
            }
303
304 4
            $class = $this->newClassMetadataInstance($className);
305 4
            $this->initializeReflection($class, $reflService);
306
307 4
            $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);
308
309 4
            $this->loadedMetadata[$className] = $class;
310
311 4
            $parent = $class;
312
313 4
            if ($this->isEntity($class)) {
314 4
                $rootEntityFound = true;
315 4
                array_unshift($visited, $className);
316
            }
317
318 4
            $this->wakeupReflection($class, $reflService);
319
320 4
            $loaded[] = $className;
321
        }
322
323 4
        return $loaded;
324
    }
325
326
    /**
327
     * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
328
     *
329
     * Override this method to implement a fallback strategy for failed metadata loading
330
     *
331
     * @param string $className
332
     *
333
     * @return ClassMetadata|null
334
     */
335
    protected function onNotFoundMetadata($className)
336
    {
337
        return null;
338
    }
339
340
    /**
341
     * Actually loads the metadata from the underlying metadata.
342
     *
343
     * @param ClassMetadata      $class
344
     * @param ClassMetadata|null $parent
345
     * @param bool               $rootEntityFound
346
     * @param string[]           $nonSuperclassParents All parent class names
347
     *                                                 that are not marked as mapped superclasses.
348
     *
349
     * @return void
350
     */
351
    abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents);
352
353
    /**
354
     * Creates a new ClassMetadata instance for the given class name.
355
     *
356
     * @param string $className
357
     *
358
     * @return ClassMetadata
359
     */
360
    abstract protected function newClassMetadataInstance($className);
361
362
    /**
363
     * {@inheritDoc}
364
     */
365
    public function isTransient($class)
366
    {
367
        if (! $this->initialized) {
368
            $this->initialize();
369
        }
370
371
        // Check for namespace alias
372
        if (strpos($class, ':') !== false) {
373
            list($namespaceAlias, $simpleClassName) = explode(':', $class, 2);
374
            $class                                  = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
375
        }
376
377
        return $this->getDriver()->isTransient($class);
378
    }
379
380
    /**
381
     * Sets the reflectionService.
382
     *
383
     *
384
     * @return void
385
     */
386
    public function setReflectionService(ReflectionService $reflectionService)
387
    {
388
        $this->reflectionService = $reflectionService;
389
    }
390
391
    /**
392
     * Gets the reflection service associated with this metadata factory.
393
     *
394
     * @return ReflectionService
395
     */
396 10
    public function getReflectionService()
397
    {
398 10
        if ($this->reflectionService === null) {
399 10
            $this->reflectionService = new RuntimeReflectionService();
400
        }
401 10
        return $this->reflectionService;
402
    }
403
}
404