vendor/gedmo/doctrine-extensions/src/Mapping/ExtensionMetadataFactory.php line 155

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Doctrine Behavioral Extensions package.
  4. * (c) Gediminas Morkevicius <gediminas.morkevicius@gmail.com> http://www.gediminasm.org
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. namespace Gedmo\Mapping;
  9. use Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver as DoctrineBundleMappingDriver;
  10. use Doctrine\Common\Annotations\Reader;
  11. use Doctrine\Deprecations\Deprecation;
  12. use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as DocumentClassMetadata;
  13. use Doctrine\ORM\Mapping\ClassMetadata as EntityClassMetadata;
  14. use Doctrine\ORM\Mapping\ClassMetadataInfo as LegacyEntityClassMetadata;
  15. use Doctrine\Persistence\Mapping\ClassMetadata;
  16. use Doctrine\Persistence\Mapping\Driver\DefaultFileLocator;
  17. use Doctrine\Persistence\Mapping\Driver\MappingDriver;
  18. use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
  19. use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
  20. use Doctrine\Persistence\ObjectManager;
  21. use Gedmo\Exception\RuntimeException;
  22. use Gedmo\Mapping\Driver\AnnotationDriverInterface;
  23. use Gedmo\Mapping\Driver\AttributeAnnotationReader;
  24. use Gedmo\Mapping\Driver\AttributeDriverInterface;
  25. use Gedmo\Mapping\Driver\AttributeReader;
  26. use Gedmo\Mapping\Driver\Chain;
  27. use Gedmo\Mapping\Driver\File as FileDriver;
  28. use Psr\Cache\CacheItemPoolInterface;
  29. /**
  30. * The extension metadata factory is responsible for extension driver
  31. * initialization and fully reading the extension metadata
  32. *
  33. * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  34. *
  35. * @final since gedmo/doctrine-extensions 3.11
  36. */
  37. class ExtensionMetadataFactory
  38. {
  39. /**
  40. * Extension driver
  41. *
  42. * @var Driver
  43. */
  44. protected $driver;
  45. /**
  46. * Object manager, entity or document
  47. *
  48. * @var ObjectManager
  49. */
  50. protected $objectManager;
  51. /**
  52. * Extension namespace
  53. *
  54. * @var string
  55. */
  56. protected $extensionNamespace;
  57. /**
  58. * Metadata annotation reader
  59. *
  60. * @var Reader|AttributeReader|object|null
  61. */
  62. protected $annotationReader;
  63. private ?CacheItemPoolInterface $cacheItemPool = null;
  64. /**
  65. * @param Reader|AttributeReader|object|null $annotationReader
  66. *
  67. * @note Providing any object as the third argument is deprecated, as of 4.0 an {@see AttributeReader} will be required
  68. */
  69. public function __construct(ObjectManager $objectManager, string $extensionNamespace, ?object $annotationReader = null, ?CacheItemPoolInterface $cacheItemPool = null)
  70. {
  71. if (null !== $annotationReader) {
  72. if ($annotationReader instanceof Reader) {
  73. Deprecation::trigger(
  74. 'gedmo/doctrine-extensions',
  75. 'https://github.com/doctrine-extensions/DoctrineExtensions/pull/2772',
  76. 'Annotations support is deprecated, migrate your application to use attributes and pass an instance of %s to the %s constructor instead.',
  77. AttributeReader::class,
  78. static::class
  79. );
  80. } elseif (!$annotationReader instanceof AttributeReader) {
  81. Deprecation::trigger(
  82. 'gedmo/doctrine-extensions',
  83. 'https://github.com/doctrine-extensions/DoctrineExtensions/pull/2258',
  84. 'Providing an annotation reader which does not implement %s or is not an instance of %s to %s is deprecated.',
  85. Reader::class,
  86. AttributeReader::class,
  87. static::class
  88. );
  89. }
  90. }
  91. $this->objectManager = $objectManager;
  92. $this->annotationReader = $annotationReader;
  93. $this->extensionNamespace = $extensionNamespace;
  94. $omDriver = $objectManager->getConfiguration()->getMetadataDriverImpl();
  95. $this->driver = $this->getDriver($omDriver);
  96. $this->cacheItemPool = $cacheItemPool;
  97. }
  98. /**
  99. * Reads extension metadata
  100. *
  101. * @param ClassMetadata<object>&(DocumentClassMetadata<object>|EntityClassMetadata<object>|LegacyEntityClassMetadata<object>) $meta
  102. *
  103. * @return array<string, mixed> the metadata configuration
  104. */
  105. public function getExtensionMetadata($meta)
  106. {
  107. if ($meta->isMappedSuperclass) {
  108. return []; // ignore mappedSuperclasses for now
  109. }
  110. $config = [];
  111. $cmf = $this->objectManager->getMetadataFactory();
  112. $useObjectName = $meta->getName();
  113. // collect metadata from inherited classes
  114. if (null !== $meta->reflClass) {
  115. foreach (array_reverse(class_parents($meta->getName())) as $parentClass) {
  116. // read only inherited mapped classes
  117. if ($cmf->hasMetadataFor($parentClass) || !$cmf->isTransient($parentClass)) {
  118. assert(class_exists($parentClass));
  119. $class = $this->objectManager->getClassMetadata($parentClass);
  120. assert($class instanceof DocumentClassMetadata || $class instanceof EntityClassMetadata || $class instanceof LegacyEntityClassMetadata);
  121. $extendedMetadata = $this->driver->readExtendedMetadata($class, $config);
  122. if (\is_array($extendedMetadata)) {
  123. $config = $extendedMetadata;
  124. }
  125. // @todo: In the next major release remove the assignment to `$extendedMetadata`, the previous conditional
  126. // block and uncomment the following line.
  127. // $config = $this->driver->readExtendedMetadata($class, $config);
  128. $isBaseInheritanceLevel = !$class->isInheritanceTypeNone()
  129. && [] === $class->parentClasses
  130. && [] !== $config
  131. ;
  132. if ($isBaseInheritanceLevel) {
  133. $useObjectName = $class->getName();
  134. }
  135. }
  136. }
  137. $extendedMetadata = $this->driver->readExtendedMetadata($meta, $config);
  138. if (\is_array($extendedMetadata)) {
  139. $config = $extendedMetadata;
  140. }
  141. // @todo: In the next major release remove the assignment to `$extendedMetadata`, the previous conditional
  142. // block and uncomment the following line.
  143. // $config = $this->driver->readExtendedMetadata($meta, $config);
  144. }
  145. if ([] !== $config) {
  146. $config['useObjectClass'] = $useObjectName;
  147. }
  148. $this->storeConfiguration($meta->getName(), $config);
  149. return $config;
  150. }
  151. /**
  152. * Get the cache id
  153. *
  154. * @param string $className
  155. * @param string $extensionNamespace
  156. *
  157. * @return string
  158. */
  159. public static function getCacheId($className, $extensionNamespace)
  160. {
  161. return str_replace('\\', '_', $className).'_$'.strtoupper(str_replace('\\', '_', $extensionNamespace)).'_CLASSMETADATA';
  162. }
  163. /**
  164. * Get the extended driver instance which will
  165. * read the metadata required by extension
  166. *
  167. * @param MappingDriver $omDriver
  168. *
  169. * @throws RuntimeException if driver was not found in extension
  170. *
  171. * @return Driver
  172. */
  173. protected function getDriver($omDriver)
  174. {
  175. if ($omDriver instanceof DoctrineBundleMappingDriver) {
  176. $omDriver = $omDriver->getDriver();
  177. }
  178. $driver = null;
  179. $className = get_class($omDriver);
  180. $driverName = substr($className, strrpos($className, '\\') + 1);
  181. if ($omDriver instanceof MappingDriverChain || 'DriverChain' === $driverName) {
  182. $driver = new Chain();
  183. foreach ($omDriver->getDrivers() as $namespace => $nestedOmDriver) {
  184. $driver->addDriver($this->getDriver($nestedOmDriver), $namespace);
  185. }
  186. if (null !== $omDriver->getDefaultDriver()) {
  187. $driver->setDefaultDriver($this->getDriver($omDriver->getDefaultDriver()));
  188. }
  189. } else {
  190. $driverName = substr($driverName, 0, strpos($driverName, 'Driver'));
  191. $isSimplified = false;
  192. if ('Simplified' === substr($driverName, 0, 10)) {
  193. // support for simplified file drivers
  194. $driverName = substr($driverName, 10);
  195. $isSimplified = true;
  196. }
  197. // create driver instance
  198. $driverClassName = $this->extensionNamespace.'\Mapping\Driver\\'.$driverName;
  199. if (!class_exists($driverClassName)) {
  200. $originalDriverClassName = $driverClassName;
  201. // try to fall back to either an annotation or attribute driver depending on the available dependencies
  202. if (interface_exists(Reader::class)) {
  203. $driverClassName = $this->extensionNamespace.'\Mapping\Driver\Annotation';
  204. } elseif (\PHP_VERSION_ID >= 80000) {
  205. $driverClassName = $this->extensionNamespace.'\Mapping\Driver\Attribute';
  206. }
  207. if (!class_exists($driverClassName)) {
  208. if ($originalDriverClassName !== $driverClassName) {
  209. throw new RuntimeException("Failed to create mapping driver: ({$originalDriverClassName}), the extension driver nor a fallback annotation or attribute driver could be found.");
  210. }
  211. throw new RuntimeException("Failed to fallback to annotation driver: ({$driverClassName}), extension driver was not found.");
  212. }
  213. }
  214. $driver = new $driverClassName();
  215. $driver->setOriginalDriver($omDriver);
  216. if ($driver instanceof FileDriver) {
  217. if ($omDriver instanceof MappingDriver) {
  218. $driver->setLocator($omDriver->getLocator());
  219. // BC for Doctrine 2.2
  220. } elseif ($isSimplified) {
  221. $driver->setLocator(new SymfonyFileLocator($omDriver->getNamespacePrefixes(), $omDriver->getFileExtension()));
  222. } else {
  223. $driver->setLocator(new DefaultFileLocator($omDriver->getPaths(), $omDriver->getFileExtension()));
  224. }
  225. }
  226. if ($driver instanceof AttributeDriverInterface) {
  227. if (null === $this->annotationReader) {
  228. throw new RuntimeException("Cannot use metadata driver ({$driverClassName}), an annotation or attribute reader was not provided.");
  229. }
  230. if ($driver instanceof AnnotationDriverInterface) {
  231. $driver->setAnnotationReader($this->annotationReader);
  232. } else {
  233. if ($this->annotationReader instanceof AttributeReader) {
  234. $driver->setAnnotationReader($this->annotationReader);
  235. } else {
  236. $driver->setAnnotationReader(new AttributeAnnotationReader(new AttributeReader(), $this->annotationReader));
  237. }
  238. }
  239. }
  240. }
  241. return $driver;
  242. }
  243. /**
  244. * @param array<string, mixed> $config
  245. */
  246. private function storeConfiguration(string $className, array $config): void
  247. {
  248. if (null === $this->cacheItemPool) {
  249. return;
  250. }
  251. // Cache the result, even if it's empty, to prevent re-parsing non-existent annotations.
  252. $cacheId = self::getCacheId($className, $this->extensionNamespace);
  253. $item = $this->cacheItemPool->getItem($cacheId);
  254. $this->cacheItemPool->save($item->set($config));
  255. }
  256. }