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

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