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

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 $entityNamespace, PhpCompatUtil $phpCompatUtil, ManagerRegistry $registry = null, bool $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 $className, string $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. return $this->isInstanceOf($this->getMappingDriverForNamespace($className), $driverClass);
  82. }
  83. if (null === $em) {
  84. throw new \InvalidArgumentException(sprintf('Cannot find the entity manager for class "%s"', $className));
  85. }
  86. if (null === $this->mappingDriversByPrefix) {
  87. // doctrine-bundle <= 2.2
  88. $metadataDriver = $em->getConfiguration()->getMetadataDriverImpl();
  89. if (!$this->isInstanceOf($metadataDriver, MappingDriverChain::class)) {
  90. return $this->isInstanceOf($metadataDriver, $driverClass);
  91. }
  92. foreach ($metadataDriver->getDrivers() as $namespace => $driver) {
  93. if (0 === strpos($className, $namespace)) {
  94. return $this->isInstanceOf($driver, $driverClass);
  95. }
  96. }
  97. return $this->isInstanceOf($metadataDriver->getDefaultDriver(), $driverClass);
  98. }
  99. $managerName = array_search($em, $this->getRegistry()->getManagers(), true);
  100. foreach ($this->mappingDriversByPrefix[$managerName] as [$prefix, $prefixDriver]) {
  101. if (0 === strpos($className, $prefix)) {
  102. return $this->isInstanceOf($prefixDriver, $driverClass);
  103. }
  104. }
  105. return false;
  106. }
  107. public function isClassAnnotated(string $className): bool
  108. {
  109. return $this->doesClassUseDriver($className, AnnotationDriver::class);
  110. }
  111. public function doesClassUsesAttributes(string $className): bool
  112. {
  113. return $this->doesClassUseDriver($className, AttributeDriver::class);
  114. }
  115. public function isDoctrineSupportingAttributes(): bool
  116. {
  117. return $this->isDoctrineInstalled() && $this->attributeMappingSupport && $this->phpCompatUtil->canUseAttributes();
  118. }
  119. public function getEntitiesForAutocomplete(): array
  120. {
  121. $entities = [];
  122. if ($this->isDoctrineInstalled()) {
  123. $allMetadata = $this->getMetadata();
  124. foreach (array_keys($allMetadata) as $classname) {
  125. $entityClassDetails = new ClassNameDetails($classname, $this->entityNamespace);
  126. $entities[] = $entityClassDetails->getRelativeName();
  127. }
  128. }
  129. sort($entities);
  130. return $entities;
  131. }
  132. /**
  133. * @return array|ClassMetadata
  134. */
  135. public function getMetadata(string $classOrNamespace = null, bool $disconnected = false)
  136. {
  137. // Invalidating the cached AnnotationDriver::$classNames to find new Entity classes
  138. foreach ($this->mappingDriversByPrefix ?? [] as $managerName => $prefixes) {
  139. foreach ($prefixes as [$prefix, $annotationDriver]) {
  140. if (null !== $annotationDriver) {
  141. if ($annotationDriver instanceof AnnotationDriver) {
  142. $classNames = (new \ReflectionClass(AnnotationDriver::class))->getProperty('classNames');
  143. } else {
  144. $classNames = (new \ReflectionClass(AttributeDriver::class))->getProperty('classNames');
  145. }
  146. $classNames->setAccessible(true);
  147. $classNames->setValue($annotationDriver, null);
  148. }
  149. }
  150. }
  151. $metadata = [];
  152. /** @var EntityManagerInterface $em */
  153. foreach ($this->getRegistry()->getManagers() as $em) {
  154. $cmf = $em->getMetadataFactory();
  155. if ($disconnected) {
  156. try {
  157. $loaded = $cmf->getAllMetadata();
  158. } catch (ORMMappingException|PersistenceMappingException $e) {
  159. $loaded = $this->isInstanceOf($cmf, AbstractClassMetadataFactory::class) ? $cmf->getLoadedMetadata() : [];
  160. }
  161. $cmf = new DisconnectedClassMetadataFactory();
  162. $cmf->setEntityManager($em);
  163. foreach ($loaded as $m) {
  164. $cmf->setMetadataFor($m->getName(), $m);
  165. }
  166. if (null === $this->mappingDriversByPrefix) {
  167. // Invalidating the cached AnnotationDriver::$classNames to find new Entity classes
  168. $metadataDriver = $em->getConfiguration()->getMetadataDriverImpl();
  169. if ($this->isInstanceOf($metadataDriver, MappingDriverChain::class)) {
  170. foreach ($metadataDriver->getDrivers() as $driver) {
  171. if ($this->isInstanceOf($driver, AnnotationDriver::class)) {
  172. $classNames->setValue($driver, null);
  173. }
  174. if ($this->isInstanceOf($driver, AttributeDriver::class)) {
  175. $classNames->setValue($driver, null);
  176. }
  177. }
  178. }
  179. }
  180. }
  181. foreach ($cmf->getAllMetadata() as $m) {
  182. if (null === $classOrNamespace) {
  183. $metadata[$m->getName()] = $m;
  184. } else {
  185. if ($m->getName() === $classOrNamespace) {
  186. return $m;
  187. }
  188. if (0 === strpos($m->getName(), $classOrNamespace)) {
  189. $metadata[$m->getName()] = $m;
  190. }
  191. }
  192. }
  193. }
  194. return $metadata;
  195. }
  196. public function createDoctrineDetails(string $entityClassName): ?EntityDetails
  197. {
  198. $metadata = $this->getMetadata($entityClassName);
  199. if ($this->isInstanceOf($metadata, ClassMetadata::class)) {
  200. return new EntityDetails($metadata);
  201. }
  202. return null;
  203. }
  204. public function isClassAMappedEntity(string $className): bool
  205. {
  206. if (!$this->isDoctrineInstalled()) {
  207. return false;
  208. }
  209. return (bool) $this->getMetadata($className);
  210. }
  211. private function isInstanceOf($object, string $class): bool
  212. {
  213. if (!\is_object($object)) {
  214. return false;
  215. }
  216. return $object instanceof $class;
  217. }
  218. public function getPotentialTableName(string $className): string
  219. {
  220. $entityManager = $this->getRegistry()->getManager();
  221. if (!$entityManager instanceof EntityManagerInterface) {
  222. throw new \RuntimeException('ObjectManager is not an EntityManagerInterface.');
  223. }
  224. /** @var NamingStrategy $namingStrategy */
  225. $namingStrategy = $entityManager->getConfiguration()->getNamingStrategy();
  226. return $namingStrategy->classToTableName($className);
  227. }
  228. public function isKeyword(string $name): bool
  229. {
  230. /** @var Connection $connection */
  231. $connection = $this->getRegistry()->getConnection();
  232. return $connection->getDatabasePlatform()->getReservedKeywordsList()->isKeyword($name);
  233. }
  234. /**
  235. * this method tries to find the correct MappingDriver for the given namespace/class
  236. * To determine which MappingDriver belongs to the class we check the prefixes configured in Doctrine and use the
  237. * prefix that has the closest match to the given $namespace.
  238. *
  239. * this helper function is needed to create entities with the configuration of doctrine if they are not yet been registered
  240. * in the ManagerRegistry
  241. */
  242. private function getMappingDriverForNamespace(string $namespace): ?MappingDriver
  243. {
  244. $lowestCharacterDiff = null;
  245. $foundDriver = null;
  246. foreach ($this->mappingDriversByPrefix ?? [] as $mappings) {
  247. foreach ($mappings as [$prefix, $driver]) {
  248. $diff = substr_compare($namespace, $prefix, 0);
  249. if ($diff >= 0 && (null === $lowestCharacterDiff || $diff < $lowestCharacterDiff)) {
  250. $lowestCharacterDiff = $diff;
  251. $foundDriver = $driver;
  252. }
  253. }
  254. }
  255. return $foundDriver;
  256. }
  257. }