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 $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. // 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 (1 < \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($metadataDriver, MappingDriverChain::class)) {
  96. return $this->isInstanceOf($metadataDriver, $driverClass);
  97. }
  98. foreach ($metadataDriver->getDrivers() as $namespace => $driver) {
  99. if (0 === 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 (0 === 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($className, AnnotationDriver::class);
  116. }
  117. public function doesClassUsesAttributes(string $className): bool
  118. {
  119. return $this->doesClassUseDriver($className, AttributeDriver::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 = null, bool $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($annotationDriver, null);
  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($cmf, AbstractClassMetadataFactory::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($metadataDriver, MappingDriverChain::class)) {
  176. foreach ($metadataDriver->getDrivers() as $driver) {
  177. if ($this->isInstanceOf($driver, AnnotationDriver::class)) {
  178. $classNames->setValue($driver, null);
  179. }
  180. if ($this->isInstanceOf($driver, AttributeDriver::class)) {
  181. $classNames->setValue($driver, null);
  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 (0 === 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($metadata, ClassMetadata::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($object, string $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, $prefix, 0);
  255. if ($diff >= 0 && (null === $lowestCharacterDiff || $diff < $lowestCharacterDiff)) {
  256. $lowestCharacterDiff = $diff;
  257. $foundDriver = $driver;
  258. }
  259. }
  260. }
  261. return $foundDriver;
  262. }
  263. }