vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php line 318
<?phpdeclare(strict_types=1);namespace Doctrine\Persistence\Mapping;use Doctrine\Persistence\Mapping\Driver\MappingDriver;use Doctrine\Persistence\Proxy;use Psr\Cache\CacheItemPoolInterface;use ReflectionClass;use ReflectionException;use function array_combine;use function array_keys;use function array_map;use function array_reverse;use function array_unshift;use function assert;use function class_exists;use function ltrim;use function str_replace;use function strpos;use function strrpos;use function substr;/*** The ClassMetadataFactory is used to create ClassMetadata objects that contain all the* metadata mapping informations of a class which describes how a class should be mapped* to a relational database.** This class was abstracted from the ORM ClassMetadataFactory.** @template CMTemplate of ClassMetadata* @template-implements ClassMetadataFactory<CMTemplate>*/abstract class AbstractClassMetadataFactory implements ClassMetadataFactory{/*** Salt used by specific Object Manager implementation.** @var string*/protected $cacheSalt = '__CLASSMETADATA__';/** @var CacheItemPoolInterface|null */private $cache;/*** @var array<string, ClassMetadata>* @psalm-var CMTemplate[]*/private $loadedMetadata = [];/** @var bool */protected $initialized = false;/** @var ReflectionService|null */private $reflectionService = null;/** @var ProxyClassNameResolver|null */private $proxyClassNameResolver = null;public function setCache(CacheItemPoolInterface $cache): void{$this->cache = $cache;}final protected function getCache(): ?CacheItemPoolInterface{return $this->cache;}/*** Returns an array of all the loaded metadata currently in memory.** @return ClassMetadata[]* @psalm-return CMTemplate[]*/public function getLoadedMetadata(){return $this->loadedMetadata;}/*** {@inheritDoc}*/public function getAllMetadata(){if (! $this->initialized) {$this->initialize();}$driver = $this->getDriver();$metadata = [];foreach ($driver->getAllClassNames() as $className) {$metadata[] = $this->getMetadataFor($className);}return $metadata;}public function setProxyClassNameResolver(ProxyClassNameResolver $resolver): void{$this->proxyClassNameResolver = $resolver;}/*** Lazy initialization of this stuff, especially the metadata driver,* since these are not needed at all when a metadata cache is active.** @return void*/abstract protected function initialize();/*** Returns the mapping driver implementation.** @return MappingDriver*/abstract protected function getDriver();/*** Wakes up reflection after ClassMetadata gets unserialized from cache.** @psalm-param CMTemplate $class** @return void*/abstract protected function wakeupReflection(ClassMetadata $class,ReflectionService $reflService);/*** Initializes Reflection after ClassMetadata was constructed.** @psalm-param CMTemplate $class** @return void*/abstract protected function initializeReflection(ClassMetadata $class,ReflectionService $reflService);/*** Checks whether the class metadata is an entity.** This method should return false for mapped superclasses or embedded classes.** @psalm-param CMTemplate $class** @return bool*/abstract protected function isEntity(ClassMetadata $class);/*** Removes the prepended backslash of a class string to conform with how php outputs class names** @psalm-param class-string $className** @psalm-return class-string*/private function normalizeClassName(string $className): string{return ltrim($className, '\\');}/*** {@inheritDoc}** @throws ReflectionException* @throws MappingException*/public function getMetadataFor(string $className){$className = $this->normalizeClassName($className);if (isset($this->loadedMetadata[$className])) {return $this->loadedMetadata[$className];}if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) {throw MappingException::classIsAnonymous($className);}if (! class_exists($className, false) && strpos($className, ':') !== false) {throw MappingException::nonExistingClass($className);}$realClassName = $this->getRealClass($className);if (isset($this->loadedMetadata[$realClassName])) {// We do not have the alias name in the map, include itreturn $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];}try {if ($this->cache !== null) {$cached = $this->cache->getItem($this->getCacheKey($realClassName))->get();if ($cached instanceof ClassMetadata) {/** @psalm-var CMTemplate $cached */$this->loadedMetadata[$realClassName] = $cached;$this->wakeupReflection($cached, $this->getReflectionService());} else {$loadedMetadata = $this->loadMetadata($realClassName);$classNames = array_combine(array_map([$this, 'getCacheKey'], $loadedMetadata),$loadedMetadata);foreach ($this->cache->getItems(array_keys($classNames)) as $item) {if (! isset($classNames[$item->getKey()])) {continue;}$item->set($this->loadedMetadata[$classNames[$item->getKey()]]);$this->cache->saveDeferred($item);}$this->cache->commit();}} else {$this->loadMetadata($realClassName);}} catch (MappingException $loadingException) {$fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName);if ($fallbackMetadataResponse === null) {throw $loadingException;}$this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;}if ($className !== $realClassName) {// We do not have the alias name in the map, include it$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];}return $this->loadedMetadata[$className];}/*** {@inheritDoc}*/public function hasMetadataFor(string $className){$className = $this->normalizeClassName($className);return isset($this->loadedMetadata[$className]);}/*** Sets the metadata descriptor for a specific class.** NOTE: This is only useful in very special cases, like when generating proxy classes.** @psalm-param class-string $className* @psalm-param CMTemplate $class** @return void*/public function setMetadataFor(string $className, ClassMetadata $class){$this->loadedMetadata[$this->normalizeClassName($className)] = $class;}/*** Gets an array of parent classes for the given entity class.** @psalm-param class-string $name** @return string[]* @psalm-return list<class-string>*/protected function getParentClasses(string $name){// Collect parent classes, ignoring transient (not-mapped) classes.$parentClasses = [];foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {if ($this->getDriver()->isTransient($parentClass)) {continue;}$parentClasses[] = $parentClass;}return $parentClasses;}/*** Loads the metadata of the class in question and all it's ancestors whose metadata* is still not loaded.** Important: The class $name does not necessarily exist at this point here.* Scenarios in a code-generation setup might have access to XML/YAML* Mapping files without the actual PHP code existing here. That is why the* {@see \Doctrine\Persistence\Mapping\ReflectionService} interface* should be used for reflection.** @param string $name The name of the class for which the metadata should get loaded.* @psalm-param class-string $name** @return array<int, string>* @psalm-return list<string>*/protected function loadMetadata(string $name){if (! $this->initialized) {$this->initialize();}$loaded = [];$parentClasses = $this->getParentClasses($name);$parentClasses[] = $name;// Move down the hierarchy of parent classes, starting from the topmost class$parent = null;$rootEntityFound = false;$visited = [];$reflService = $this->getReflectionService();foreach ($parentClasses as $className) {if (isset($this->loadedMetadata[$className])) {$parent = $this->loadedMetadata[$className];if ($this->isEntity($parent)) {$rootEntityFound = true;array_unshift($visited, $className);}continue;}$class = $this->newClassMetadataInstance($className);$this->initializeReflection($class, $reflService);$this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);$this->loadedMetadata[$className] = $class;$parent = $class;if ($this->isEntity($class)) {$rootEntityFound = true;array_unshift($visited, $className);}$this->wakeupReflection($class, $reflService);$loaded[] = $className;}return $loaded;}/*** Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions** Override this method to implement a fallback strategy for failed metadata loading** @return ClassMetadata|null* @psalm-return CMTemplate|null*/protected function onNotFoundMetadata(string $className){return null;}/*** Actually loads the metadata from the underlying metadata.** @param bool $rootEntityFound True when there is another entity (non-mapped superclass) class above the current class in the PHP class hierarchy.* @param list<class-string> $nonSuperclassParents All parent class names that are not marked as mapped superclasses, with the direct parent class being the first and the root entity class the last element.* @psalm-param CMTemplate $class* @psalm-param CMTemplate|null $parent** @return void*/abstract protected function doLoadMetadata(ClassMetadata $class,?ClassMetadata $parent,bool $rootEntityFound,array $nonSuperclassParents);/*** Creates a new ClassMetadata instance for the given class name.** @psalm-param class-string<T> $className** @return ClassMetadata<T>* @psalm-return CMTemplate** @template T of object*/abstract protected function newClassMetadataInstance(string $className);/*** {@inheritDoc}*/public function isTransient(string $className){if (! $this->initialized) {$this->initialize();}if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) {return false;}if (! class_exists($className, false) && strpos($className, ':') !== false) {throw MappingException::nonExistingClass($className);}/** @psalm-var class-string $className */return $this->getDriver()->isTransient($className);}/*** Sets the reflectionService.** @return void*/public function setReflectionService(ReflectionService $reflectionService){$this->reflectionService = $reflectionService;}/*** Gets the reflection service associated with this metadata factory.** @return ReflectionService*/public function getReflectionService(){if ($this->reflectionService === null) {$this->reflectionService = new RuntimeReflectionService();}return $this->reflectionService;}protected function getCacheKey(string $realClassName): string{return str_replace('\\', '__', $realClassName) . $this->cacheSalt;}/*** Gets the real class name of a class name that could be a proxy.** @psalm-param class-string<Proxy<T>>|class-string<T> $class** @psalm-return class-string<T>** @template T of object*/private function getRealClass(string $class): string{if ($this->proxyClassNameResolver === null) {$this->createDefaultProxyClassNameResolver();}assert($this->proxyClassNameResolver !== null);return $this->proxyClassNameResolver->resolveClassName($class);}private function createDefaultProxyClassNameResolver(): void{$this->proxyClassNameResolver = new class implements ProxyClassNameResolver {/*** @psalm-param class-string<Proxy<T>>|class-string<T> $className** @psalm-return class-string<T>** @template T of object*/public function resolveClassName(string $className): string{$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');if ($pos === false) {/** @psalm-var class-string<T> */return $className;}/** @psalm-var class-string<T> */return substr($className, $pos + Proxy::MARKER_LENGTH + 2);}};}}