<?php 
 
namespace Doctrine\Persistence\Mapping; 
 
use Doctrine\Common\Cache\Cache; 
use Doctrine\Persistence\Mapping\Driver\MappingDriver; 
use Doctrine\Persistence\Proxy; 
use ReflectionException; 
use function array_reverse; 
use function array_unshift; 
use function class_exists; 
use function explode; 
use function interface_exists; 
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. 
 */ 
abstract class AbstractClassMetadataFactory implements ClassMetadataFactory 
{ 
    /** 
     * Salt used by specific Object Manager implementation. 
     * 
     * @var string 
     */ 
    protected $cacheSalt = '$CLASSMETADATA'; 
 
    /** @var Cache|null */ 
    private $cacheDriver; 
 
    /** @var ClassMetadata[] */ 
    private $loadedMetadata = []; 
 
    /** @var bool */ 
    protected $initialized = false; 
 
    /** @var ReflectionService|null */ 
    private $reflectionService = null; 
 
    /** 
     * Sets the cache driver used by the factory to cache ClassMetadata instances. 
     * 
     * @return void 
     */ 
    public function setCacheDriver(?Cache $cacheDriver = null) 
    { 
        $this->cacheDriver = $cacheDriver; 
    } 
 
    /** 
     * Gets the cache driver used by the factory to cache ClassMetadata instances. 
     * 
     * @return Cache|null 
     */ 
    public function getCacheDriver() 
    { 
        return $this->cacheDriver; 
    } 
 
    /** 
     * Returns an array of all the loaded metadata currently in memory. 
     * 
     * @return ClassMetadata[] 
     */ 
    public function getLoadedMetadata() 
    { 
        return $this->loadedMetadata; 
    } 
 
    /** 
     * Forces the factory to load the metadata of all classes known to the underlying 
     * mapping driver. 
     * 
     * @return ClassMetadata[] The ClassMetadata instances of all mapped classes. 
     */ 
    public function getAllMetadata() 
    { 
        if (! $this->initialized) { 
            $this->initialize(); 
        } 
 
        $driver   = $this->getDriver(); 
        $metadata = []; 
        foreach ($driver->getAllClassNames() as $className) { 
            $metadata[] = $this->getMetadataFor($className); 
        } 
 
        return $metadata; 
    } 
 
    /** 
     * 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(); 
 
    /** 
     * Gets the fully qualified class-name from the namespace alias. 
     * 
     * @param string $namespaceAlias 
     * @param string $simpleClassName 
     * 
     * @return string 
     */ 
    abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName); 
 
    /** 
     * Returns the mapping driver implementation. 
     * 
     * @return MappingDriver 
     */ 
    abstract protected function getDriver(); 
 
    /** 
     * Wakes up reflection after ClassMetadata gets unserialized from cache. 
     * 
     * @return void 
     */ 
    abstract protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService); 
 
    /** 
     * Initializes Reflection after ClassMetadata was constructed. 
     * 
     * @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. 
     * 
     * @return bool 
     */ 
    abstract protected function isEntity(ClassMetadata $class); 
 
    /** 
     * Gets the class metadata descriptor for a class. 
     * 
     * @param string $className The name of the class. 
     * 
     * @return ClassMetadata 
     * 
     * @throws ReflectionException 
     * @throws MappingException 
     */ 
    public function getMetadataFor($className) 
    { 
        if (isset($this->loadedMetadata[$className])) { 
            return $this->loadedMetadata[$className]; 
        } 
 
        // Check for namespace alias 
        if (strpos($className, ':') !== false) { 
            [$namespaceAlias, $simpleClassName] = explode(':', $className, 2); 
 
            $realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); 
        } else { 
            $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]; 
        } 
 
        $loadingException = null; 
 
        try { 
            if ($this->cacheDriver) { 
                $cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt); 
                if ($cached instanceof ClassMetadata) { 
                    $this->loadedMetadata[$realClassName] = $cached; 
 
                    $this->wakeupReflection($cached, $this->getReflectionService()); 
                } else { 
                    foreach ($this->loadMetadata($realClassName) as $loadedClassName) { 
                        $this->cacheDriver->save( 
                            $loadedClassName . $this->cacheSalt, 
                            $this->loadedMetadata[$loadedClassName] 
                        ); 
                    } 
                } 
            } else { 
                $this->loadMetadata($realClassName); 
            } 
        } catch (MappingException $loadingException) { 
            $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName); 
 
            if (! $fallbackMetadataResponse) { 
                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]; 
    } 
 
    /** 
     * Checks whether the factory has the metadata for a class loaded already. 
     * 
     * @param string $className 
     * 
     * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise. 
     */ 
    public function hasMetadataFor($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. 
     * 
     * @param string        $className 
     * @param ClassMetadata $class 
     * 
     * @return void 
     */ 
    public function setMetadataFor($className, $class) 
    { 
        $this->loadedMetadata[$className] = $class; 
    } 
 
    /** 
     * Gets an array of parent classes for the given entity class. 
     * 
     * @param string $name 
     * 
     * @return string[] 
     */ 
    protected function getParentClasses($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\Common\Persistence\Mapping\ReflectionService} interface 
     * should be used for reflection. 
     * 
     * @param string $name The name of the class for which the metadata should get loaded. 
     * 
     * @return string[] 
     */ 
    protected function loadMetadata($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 
     * 
     * @param string $className 
     * 
     * @return ClassMetadata|null 
     */ 
    protected function onNotFoundMetadata($className) 
    { 
        return null; 
    } 
 
    /** 
     * Actually loads the metadata from the underlying metadata. 
     * 
     * @param ClassMetadata      $class 
     * @param ClassMetadata|null $parent 
     * @param bool               $rootEntityFound 
     * @param string[]           $nonSuperclassParents All parent class names 
     *                                                 that are not marked as mapped superclasses. 
     * 
     * @return void 
     */ 
    abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents); 
 
    /** 
     * Creates a new ClassMetadata instance for the given class name. 
     * 
     * @param string $className 
     * 
     * @return ClassMetadata 
     */ 
    abstract protected function newClassMetadataInstance($className); 
 
    /** 
     * {@inheritDoc} 
     */ 
    public function isTransient($class) 
    { 
        if (! $this->initialized) { 
            $this->initialize(); 
        } 
 
        // Check for namespace alias 
        if (strpos($class, ':') !== false) { 
            [$namespaceAlias, $simpleClassName] = explode(':', $class, 2); 
            $class                              = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); 
        } 
 
        return $this->getDriver()->isTransient($class); 
    } 
 
    /** 
     * 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; 
    } 
 
    /** 
     * Gets the real class name of a class name that could be a proxy. 
     */ 
    private function getRealClass(string $class) : string 
    { 
        $pos = strrpos($class, '\\' . Proxy::MARKER . '\\'); 
 
        if ($pos === false) { 
            return $class; 
        } 
 
        return substr($class, $pos + Proxy::MARKER_LENGTH + 2); 
    } 
} 
 
class_exists(\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory::class); 
interface_exists(ClassMetadata::class); 
interface_exists(ReflectionService::class);