vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php line 260

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