vendor/symfony/maker-bundle/src/Doctrine/DoctrineHelper.php line 172

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony MakerBundle package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bundle\MakerBundle\Doctrine;
  11. use Doctrine\DBAL\Connection;
  12. use Doctrine\ORM\EntityManagerInterface;
  13. use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
  14. use Doctrine\ORM\Mapping\Driver\AttributeDriver;
  15. use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
  16. use Doctrine\ORM\Mapping\NamingStrategy;
  17. use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
  18. use Doctrine\Persistence\ManagerRegistry;
  19. use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
  20. use Doctrine\Persistence\Mapping\ClassMetadata;
  21. use Doctrine\Persistence\Mapping\Driver\MappingDriver;
  22. use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
  23. use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException;
  24. use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
  25. use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil;
  26. /**
  27.  * @author Fabien Potencier <fabien@symfony.com>
  28.  * @author Ryan Weaver <ryan@knpuniversity.com>
  29.  * @author Sadicov Vladimir <sadikoff@gmail.com>
  30.  *
  31.  * @internal
  32.  */
  33. final class DoctrineHelper
  34. {
  35.     /**
  36.      * @var string
  37.      */
  38.     private $entityNamespace;
  39.     private $phpCompatUtil;
  40.     private $registry;
  41.     /**
  42.      * @var array|null
  43.      */
  44.     private $mappingDriversByPrefix;
  45.     private $attributeMappingSupport;
  46.     public function __construct(string $entityNamespacePhpCompatUtil $phpCompatUtilManagerRegistry $registry nullbool $attributeMappingSupport false, array $annotatedPrefixes null)
  47.     {
  48.         $this->entityNamespace trim($entityNamespace'\\');
  49.         $this->phpCompatUtil $phpCompatUtil;
  50.         $this->registry $registry;
  51.         $this->attributeMappingSupport $attributeMappingSupport;
  52.         $this->mappingDriversByPrefix $annotatedPrefixes;
  53.     }
  54.     public function getRegistry(): ManagerRegistry
  55.     {
  56.         // this should never happen: we will have checked for the
  57.         // DoctrineBundle dependency before calling this
  58.         if (null === $this->registry) {
  59.             throw new \Exception('Somehow the doctrine service is missing. Is DoctrineBundle installed?');
  60.         }
  61.         return $this->registry;
  62.     }
  63.     private function isDoctrineInstalled(): bool
  64.     {
  65.         return null !== $this->registry;
  66.     }
  67.     public function getEntityNamespace(): string
  68.     {
  69.         return $this->entityNamespace;
  70.     }
  71.     public function doesClassUseDriver(string $classNamestring $driverClass): bool
  72.     {
  73.         try {
  74.             /** @var EntityManagerInterface $em */
  75.             $em $this->getRegistry()->getManagerForClass($className);
  76.         } catch (\ReflectionException $exception) {
  77.             // this exception will be thrown by the registry if the class isn't created yet.
  78.             // an example case is the "make:entity" command, which needs to know which driver is used for the class to determine
  79.             // if the class should be generated with attributes or annotations. If this exception is thrown, we will check based on the
  80.             // namespaces for the given $className and compare it with the doctrine configuration to get the correct MappingDriver.
  81.             // extract the new class's namespace from the full $className to check the namespace of the new class against the doctrine configuration.
  82.             $classNameComponents explode('\\'$className);
  83.             if (\count($classNameComponents)) {
  84.                 array_pop($classNameComponents);
  85.             }
  86.             $classNamespace implode('\\'$classNameComponents);
  87.             return $this->isInstanceOf($this->getMappingDriverForNamespace($classNamespace), $driverClass);
  88.         }
  89.         if (null === $em) {
  90.             throw new \InvalidArgumentException(sprintf('Cannot find the entity manager for class "%s"'$className));
  91.         }
  92.         if (null === $this->mappingDriversByPrefix) {
  93.             // doctrine-bundle <= 2.2
  94.             $metadataDriver $em->getConfiguration()->getMetadataDriverImpl();
  95.             if (!$this->isInstanceOf($metadataDriverMappingDriverChain::class)) {
  96.                 return $this->isInstanceOf($metadataDriver$driverClass);
  97.             }
  98.             foreach ($metadataDriver->getDrivers() as $namespace => $driver) {
  99.                 if (=== strpos($className$namespace)) {
  100.                     return $this->isInstanceOf($driver$driverClass);
  101.                 }
  102.             }
  103.             return $this->isInstanceOf($metadataDriver->getDefaultDriver(), $driverClass);
  104.         }
  105.         $managerName array_search($em$this->getRegistry()->getManagers(), true);
  106.         foreach ($this->mappingDriversByPrefix[$managerName] as [$prefix$prefixDriver]) {
  107.             if (=== strpos($className$prefix)) {
  108.                 return $this->isInstanceOf($prefixDriver$driverClass);
  109.             }
  110.         }
  111.         return false;
  112.     }
  113.     public function isClassAnnotated(string $className): bool
  114.     {
  115.         return $this->doesClassUseDriver($classNameAnnotationDriver::class);
  116.     }
  117.     public function doesClassUsesAttributes(string $className): bool
  118.     {
  119.         return $this->doesClassUseDriver($classNameAttributeDriver::class);
  120.     }
  121.     public function isDoctrineSupportingAttributes(): bool
  122.     {
  123.         return $this->isDoctrineInstalled() && $this->attributeMappingSupport && $this->phpCompatUtil->canUseAttributes();
  124.     }
  125.     public function getEntitiesForAutocomplete(): array
  126.     {
  127.         $entities = [];
  128.         if ($this->isDoctrineInstalled()) {
  129.             $allMetadata $this->getMetadata();
  130.             foreach (array_keys($allMetadata) as $classname) {
  131.                 $entityClassDetails = new ClassNameDetails($classname$this->entityNamespace);
  132.                 $entities[] = $entityClassDetails->getRelativeName();
  133.             }
  134.         }
  135.         sort($entities);
  136.         return $entities;
  137.     }
  138.     /**
  139.      * @return array|ClassMetadata
  140.      */
  141.     public function getMetadata(string $classOrNamespace nullbool $disconnected false)
  142.     {
  143.         // Invalidating the cached AnnotationDriver::$classNames to find new Entity classes
  144.         foreach ($this->mappingDriversByPrefix ?? [] as $managerName => $prefixes) {
  145.             foreach ($prefixes as [$prefix$annotationDriver]) {
  146.                 if (null !== $annotationDriver) {
  147.                     if ($annotationDriver instanceof AnnotationDriver) {
  148.                         $classNames = (new \ReflectionClass(AnnotationDriver::class))->getProperty('classNames');
  149.                     } else {
  150.                         $classNames = (new \ReflectionClass(AttributeDriver::class))->getProperty('classNames');
  151.                     }
  152.                     $classNames->setAccessible(true);
  153.                     $classNames->setValue($annotationDrivernull);
  154.                 }
  155.             }
  156.         }
  157.         $metadata = [];
  158.         /** @var EntityManagerInterface $em */
  159.         foreach ($this->getRegistry()->getManagers() as $em) {
  160.             $cmf $em->getMetadataFactory();
  161.             if ($disconnected) {
  162.                 try {
  163.                     $loaded $cmf->getAllMetadata();
  164.                 } catch (ORMMappingException|PersistenceMappingException $e) {
  165.                     $loaded $this->isInstanceOf($cmfAbstractClassMetadataFactory::class) ? $cmf->getLoadedMetadata() : [];
  166.                 }
  167.                 $cmf = new DisconnectedClassMetadataFactory();
  168.                 $cmf->setEntityManager($em);
  169.                 foreach ($loaded as $m) {
  170.                     $cmf->setMetadataFor($m->getName(), $m);
  171.                 }
  172.                 if (null === $this->mappingDriversByPrefix) {
  173.                     // Invalidating the cached AnnotationDriver::$classNames to find new Entity classes
  174.                     $metadataDriver $em->getConfiguration()->getMetadataDriverImpl();
  175.                     if ($this->isInstanceOf($metadataDriverMappingDriverChain::class)) {
  176.                         foreach ($metadataDriver->getDrivers() as $driver) {
  177.                             if ($this->isInstanceOf($driverAnnotationDriver::class)) {
  178.                                 $classNames->setValue($drivernull);
  179.                             }
  180.                             if ($this->isInstanceOf($driverAttributeDriver::class)) {
  181.                                 $classNames->setValue($drivernull);
  182.                             }
  183.                         }
  184.                     }
  185.                 }
  186.             }
  187.             foreach ($cmf->getAllMetadata() as $m) {
  188.                 if (null === $classOrNamespace) {
  189.                     $metadata[$m->getName()] = $m;
  190.                 } else {
  191.                     if ($m->getName() === $classOrNamespace) {
  192.                         return $m;
  193.                     }
  194.                     if (=== strpos($m->getName(), $classOrNamespace)) {
  195.                         $metadata[$m->getName()] = $m;
  196.                     }
  197.                 }
  198.             }
  199.         }
  200.         return $metadata;
  201.     }
  202.     public function createDoctrineDetails(string $entityClassName): ?EntityDetails
  203.     {
  204.         $metadata $this->getMetadata($entityClassName);
  205.         if ($this->isInstanceOf($metadataClassMetadata::class)) {
  206.             return new EntityDetails($metadata);
  207.         }
  208.         return null;
  209.     }
  210.     public function isClassAMappedEntity(string $className): bool
  211.     {
  212.         if (!$this->isDoctrineInstalled()) {
  213.             return false;
  214.         }
  215.         return (bool) $this->getMetadata($className);
  216.     }
  217.     private function isInstanceOf($objectstring $class): bool
  218.     {
  219.         if (!\is_object($object)) {
  220.             return false;
  221.         }
  222.         return $object instanceof $class;
  223.     }
  224.     public function getPotentialTableName(string $className): string
  225.     {
  226.         $entityManager $this->getRegistry()->getManager();
  227.         if (!$entityManager instanceof EntityManagerInterface) {
  228.             throw new \RuntimeException('ObjectManager is not an EntityManagerInterface.');
  229.         }
  230.         /** @var NamingStrategy $namingStrategy */
  231.         $namingStrategy $entityManager->getConfiguration()->getNamingStrategy();
  232.         return $namingStrategy->classToTableName($className);
  233.     }
  234.     public function isKeyword(string $name): bool
  235.     {
  236.         /** @var Connection $connection */
  237.         $connection $this->getRegistry()->getConnection();
  238.         return $connection->getDatabasePlatform()->getReservedKeywordsList()->isKeyword($name);
  239.     }
  240.     /**
  241.      * this method tries to find the correct MappingDriver for the given namespace/class
  242.      * To determine which MappingDriver belongs to the class we check the prefixes configured in Doctrine and use the
  243.      * prefix that has the closest match to the given $namespace.
  244.      *
  245.      * this helper function is needed to create entities with the configuration of doctrine if they are not yet been registered
  246.      * in the ManagerRegistry
  247.      */
  248.     private function getMappingDriverForNamespace(string $namespace): ?MappingDriver
  249.     {
  250.         $lowestCharacterDiff null;
  251.         $foundDriver null;
  252.         foreach ($this->mappingDriversByPrefix ?? [] as $mappings) {
  253.             foreach ($mappings as [$prefix$driver]) {
  254.                 $diff substr_compare($namespace$prefix0);
  255.                 if ($diff >= && (null === $lowestCharacterDiff || $diff $lowestCharacterDiff)) {
  256.                     $lowestCharacterDiff $diff;
  257.                     $foundDriver $driver;
  258.                 }
  259.             }
  260.         }
  261.         return $foundDriver;
  262.     }
  263. }