From bf6655a534a6775d30cafa67bd801276bda1d98d Mon Sep 17 00:00:00 2001 From: polo Date: Tue, 13 Aug 2024 23:45:21 +0200 Subject: =?UTF-8?q?VERSION=200.2=20doctrine=20ORM=20et=20entit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Mapping/AbstractClassMetadataFactory.php | 499 +++++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php (limited to 'vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php') diff --git a/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php b/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php new file mode 100644 index 0000000..e8f6aca --- /dev/null +++ b/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php @@ -0,0 +1,499 @@ + + */ +abstract class AbstractClassMetadataFactory implements ClassMetadataFactory +{ + /** + * Salt used by specific Object Manager implementation. + * + * @var string + */ + protected $cacheSalt = '__CLASSMETADATA__'; + + /** @var CacheItemPoolInterface|null */ + private $cache; + + /** + * @var array + * @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 + */ + 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 + * @psalm-return list + */ + 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 $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 $className + * + * @return ClassMetadata + * @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>|class-string $class + * + * @psalm-return class-string + * + * @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>|class-string $className + * + * @psalm-return class-string + * + * @template T of object + */ + public function resolveClassName(string $className): string + { + $pos = strrpos($className, '\\' . Proxy::MARKER . '\\'); + + if ($pos === false) { + /** @psalm-var class-string */ + return $className; + } + + /** @psalm-var class-string */ + return substr($className, $pos + Proxy::MARKER_LENGTH + 2); + } + }; + } +} -- cgit v1.2.3