vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/AbstractClassMetadataFactory.php line 382

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