- <?php
- declare(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 it
-             return $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);
-             }
-         };
-     }
- }
-