PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php

http://github.com/symfony/symfony
PHP | 481 lines | 307 code | 49 blank | 125 comment | 31 complexity | 77764967d2f220bf92fb368f5ace4c94 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony 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\Bridge\Doctrine\DependencyInjection;
  11. use Symfony\Component\Config\Resource\GlobResource;
  12. use Symfony\Component\DependencyInjection\Alias;
  13. use Symfony\Component\DependencyInjection\ContainerBuilder;
  14. use Symfony\Component\DependencyInjection\Definition;
  15. use Symfony\Component\DependencyInjection\Reference;
  16. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  17. /**
  18. * This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need.
  19. *
  20. * @author Benjamin Eberlei <kontakt@beberlei.de>
  21. */
  22. abstract class AbstractDoctrineExtension extends Extension
  23. {
  24. /**
  25. * Used inside metadata driver method to simplify aggregation of data.
  26. */
  27. protected $aliasMap = [];
  28. /**
  29. * Used inside metadata driver method to simplify aggregation of data.
  30. */
  31. protected $drivers = [];
  32. /**
  33. * @param array $objectManager A configured object manager
  34. *
  35. * @throws \InvalidArgumentException
  36. */
  37. protected function loadMappingInformation(array $objectManager, ContainerBuilder $container)
  38. {
  39. if ($objectManager['auto_mapping']) {
  40. // automatically register bundle mappings
  41. foreach (array_keys($container->getParameter('kernel.bundles')) as $bundle) {
  42. if (!isset($objectManager['mappings'][$bundle])) {
  43. $objectManager['mappings'][$bundle] = [
  44. 'mapping' => true,
  45. 'is_bundle' => true,
  46. ];
  47. }
  48. }
  49. }
  50. foreach ($objectManager['mappings'] as $mappingName => $mappingConfig) {
  51. if (null !== $mappingConfig && false === $mappingConfig['mapping']) {
  52. continue;
  53. }
  54. $mappingConfig = array_replace([
  55. 'dir' => false,
  56. 'type' => false,
  57. 'prefix' => false,
  58. ], (array) $mappingConfig);
  59. $mappingConfig['dir'] = $container->getParameterBag()->resolveValue($mappingConfig['dir']);
  60. // a bundle configuration is detected by realizing that the specified dir is not absolute and existing
  61. if (!isset($mappingConfig['is_bundle'])) {
  62. $mappingConfig['is_bundle'] = !is_dir($mappingConfig['dir']);
  63. }
  64. if ($mappingConfig['is_bundle']) {
  65. $bundle = null;
  66. foreach ($container->getParameter('kernel.bundles') as $name => $class) {
  67. if ($mappingName === $name) {
  68. $bundle = new \ReflectionClass($class);
  69. break;
  70. }
  71. }
  72. if (null === $bundle) {
  73. throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName));
  74. }
  75. $mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container);
  76. if (!$mappingConfig) {
  77. continue;
  78. }
  79. } elseif (!$mappingConfig['type']) {
  80. $mappingConfig['type'] = $this->detectMappingType($mappingConfig['dir'], $container);
  81. }
  82. $this->assertValidMappingConfiguration($mappingConfig, $objectManager['name']);
  83. $this->setMappingDriverConfig($mappingConfig, $mappingName);
  84. $this->setMappingDriverAlias($mappingConfig, $mappingName);
  85. }
  86. }
  87. /**
  88. * Register the alias for this mapping driver.
  89. *
  90. * Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks.
  91. */
  92. protected function setMappingDriverAlias(array $mappingConfig, string $mappingName)
  93. {
  94. if (isset($mappingConfig['alias'])) {
  95. $this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix'];
  96. } else {
  97. $this->aliasMap[$mappingName] = $mappingConfig['prefix'];
  98. }
  99. }
  100. /**
  101. * Register the mapping driver configuration for later use with the object managers metadata driver chain.
  102. *
  103. * @throws \InvalidArgumentException
  104. */
  105. protected function setMappingDriverConfig(array $mappingConfig, string $mappingName)
  106. {
  107. $mappingDirectory = $mappingConfig['dir'];
  108. if (!is_dir($mappingDirectory)) {
  109. throw new \InvalidArgumentException(sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName));
  110. }
  111. $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory;
  112. }
  113. /**
  114. * If this is a bundle controlled mapping all the missing information can be autodetected by this method.
  115. *
  116. * Returns false when autodetection failed, an array of the completed information otherwise.
  117. *
  118. * @return array|false
  119. */
  120. protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container)
  121. {
  122. $bundleDir = \dirname($bundle->getFileName());
  123. if (!$bundleConfig['type']) {
  124. $bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container);
  125. }
  126. if (!$bundleConfig['type']) {
  127. // skip this bundle, no mapping information was found.
  128. return false;
  129. }
  130. if (!$bundleConfig['dir']) {
  131. if (\in_array($bundleConfig['type'], ['annotation', 'staticphp', 'attribute'])) {
  132. $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingObjectDefaultName();
  133. } else {
  134. $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory();
  135. }
  136. } else {
  137. $bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir'];
  138. }
  139. if (!$bundleConfig['prefix']) {
  140. $bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName();
  141. }
  142. return $bundleConfig;
  143. }
  144. /**
  145. * Register all the collected mapping information with the object manager by registering the appropriate mapping drivers.
  146. */
  147. protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container)
  148. {
  149. // configure metadata driver for each bundle based on the type of mapping files found
  150. if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'))) {
  151. $chainDriverDef = $container->getDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'));
  152. } else {
  153. $chainDriverDef = new Definition($this->getMetadataDriverClass('driver_chain'));
  154. $chainDriverDef->setPublic(false);
  155. }
  156. foreach ($this->drivers as $driverType => $driverPaths) {
  157. $mappingService = $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver');
  158. if ($container->hasDefinition($mappingService)) {
  159. $mappingDriverDef = $container->getDefinition($mappingService);
  160. $args = $mappingDriverDef->getArguments();
  161. if ('annotation' == $driverType) {
  162. $args[1] = array_merge(array_values($driverPaths), $args[1]);
  163. } else {
  164. $args[0] = array_merge(array_values($driverPaths), $args[0]);
  165. }
  166. $mappingDriverDef->setArguments($args);
  167. } elseif ('attribute' === $driverType) {
  168. $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
  169. array_values($driverPaths),
  170. ]);
  171. } elseif ('annotation' == $driverType) {
  172. $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
  173. new Reference($this->getObjectManagerElementName('metadata.annotation_reader')),
  174. array_values($driverPaths),
  175. ]);
  176. } else {
  177. $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
  178. array_values($driverPaths),
  179. ]);
  180. }
  181. $mappingDriverDef->setPublic(false);
  182. if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml')) {
  183. $mappingDriverDef->setArguments([array_flip($driverPaths)]);
  184. $mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']);
  185. }
  186. $container->setDefinition($mappingService, $mappingDriverDef);
  187. foreach ($driverPaths as $prefix => $driverPath) {
  188. $chainDriverDef->addMethodCall('addDriver', [new Reference($mappingService), $prefix]);
  189. }
  190. }
  191. $container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef);
  192. }
  193. /**
  194. * Assertion if the specified mapping information is valid.
  195. *
  196. * @throws \InvalidArgumentException
  197. */
  198. protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName)
  199. {
  200. if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) {
  201. throw new \InvalidArgumentException(sprintf('Mapping definitions for Doctrine manager "%s" require at least the "type", "dir" and "prefix" options.', $objectManagerName));
  202. }
  203. if (!is_dir($mappingConfig['dir'])) {
  204. throw new \InvalidArgumentException(sprintf('Specified non-existing directory "%s" as Doctrine mapping source.', $mappingConfig['dir']));
  205. }
  206. if (!\in_array($mappingConfig['type'], ['xml', 'yml', 'annotation', 'php', 'staticphp', 'attribute'])) {
  207. throw new \InvalidArgumentException(sprintf('Can only configure "xml", "yml", "annotation", "php", "staticphp" or "attribute" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. You can register them by adding a new driver to the "%s" service definition.', $this->getObjectManagerElementName($objectManagerName.'_metadata_driver')));
  208. }
  209. }
  210. /**
  211. * Detects what metadata driver to use for the supplied directory.
  212. *
  213. * @return string|null A metadata driver short name, if one can be detected
  214. */
  215. protected function detectMetadataDriver(string $dir, ContainerBuilder $container)
  216. {
  217. $configPath = $this->getMappingResourceConfigDirectory();
  218. $extension = $this->getMappingResourceExtension();
  219. if (glob($dir.'/'.$configPath.'/*.'.$extension.'.xml', \GLOB_NOSORT)) {
  220. $driver = 'xml';
  221. } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.yml', \GLOB_NOSORT)) {
  222. $driver = 'yml';
  223. } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.php', \GLOB_NOSORT)) {
  224. $driver = 'php';
  225. } else {
  226. // add the closest existing directory as a resource
  227. $resource = $dir.'/'.$configPath;
  228. while (!is_dir($resource)) {
  229. $resource = \dirname($resource);
  230. }
  231. $container->fileExists($resource, false);
  232. if ($container->fileExists($dir.'/'.$this->getMappingObjectDefaultName(), false)) {
  233. return $this->detectMappingType($dir, $container);
  234. }
  235. return null;
  236. }
  237. $container->fileExists($dir.'/'.$configPath, false);
  238. return $driver;
  239. }
  240. /**
  241. * Detects what mapping type to use for the supplied directory.
  242. *
  243. * @return string A mapping type 'attribute' or 'annotation'
  244. */
  245. private function detectMappingType(string $directory, ContainerBuilder $container): string
  246. {
  247. if (\PHP_VERSION_ID < 80000) {
  248. return 'annotation';
  249. }
  250. $type = 'attribute';
  251. $glob = new GlobResource($directory, '*', true);
  252. $container->addResource($glob);
  253. foreach ($glob as $file) {
  254. $content = file_get_contents($file);
  255. if (preg_match('/^#\[.*Entity\b/m', $content)) {
  256. break;
  257. }
  258. if (preg_match('/^ \* @.*Entity\b/m', $content)) {
  259. $type = 'annotation';
  260. break;
  261. }
  262. }
  263. return $type;
  264. }
  265. /**
  266. * Loads a configured object manager metadata, query or result cache driver.
  267. *
  268. * @throws \InvalidArgumentException in case of unknown driver type
  269. */
  270. protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName)
  271. {
  272. $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container);
  273. }
  274. /**
  275. * Loads a cache driver.
  276. *
  277. * @return string
  278. *
  279. * @throws \InvalidArgumentException
  280. */
  281. protected function loadCacheDriver(string $cacheName, string $objectManagerName, array $cacheDriver, ContainerBuilder $container)
  282. {
  283. $cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName);
  284. switch ($cacheDriver['type']) {
  285. case 'service':
  286. $container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false));
  287. return $cacheDriverServiceId;
  288. case 'memcached':
  289. $memcachedClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcached.class').'%';
  290. $memcachedInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcached_instance.class').'%';
  291. $memcachedHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.memcached_host').'%';
  292. $memcachedPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcached_port').'%';
  293. $cacheDef = new Definition($memcachedClass);
  294. $memcachedInstance = new Definition($memcachedInstanceClass);
  295. $memcachedInstance->addMethodCall('addServer', [
  296. $memcachedHost, $memcachedPort,
  297. ]);
  298. $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)), $memcachedInstance);
  299. $cacheDef->addMethodCall('setMemcached', [new Reference($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)))]);
  300. break;
  301. case 'redis':
  302. $redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%';
  303. $redisInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.redis_instance.class').'%';
  304. $redisHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.redis_host').'%';
  305. $redisPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.redis_port').'%';
  306. $cacheDef = new Definition($redisClass);
  307. $redisInstance = new Definition($redisInstanceClass);
  308. $redisInstance->addMethodCall('connect', [
  309. $redisHost, $redisPort,
  310. ]);
  311. $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)), $redisInstance);
  312. $cacheDef->addMethodCall('setRedis', [new Reference($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)))]);
  313. break;
  314. case 'apc':
  315. case 'apcu':
  316. case 'array':
  317. case 'xcache':
  318. case 'wincache':
  319. case 'zenddata':
  320. $cacheDef = new Definition('%'.$this->getObjectManagerElementName(sprintf('cache.%s.class', $cacheDriver['type'])).'%');
  321. break;
  322. default:
  323. throw new \InvalidArgumentException(sprintf('"%s" is an unrecognized Doctrine cache driver.', $cacheDriver['type']));
  324. }
  325. $cacheDef->setPublic(false);
  326. if (!isset($cacheDriver['namespace'])) {
  327. // generate a unique namespace for the given application
  328. if ($container->hasParameter('cache.prefix.seed')) {
  329. $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
  330. } else {
  331. $seed = '_'.$container->getParameter('kernel.project_dir');
  332. $seed .= '.'.$container->getParameter('kernel.container_class');
  333. }
  334. $namespace = 'sf_'.$this->getMappingResourceExtension().'_'.$objectManagerName.'_'.ContainerBuilder::hash($seed);
  335. $cacheDriver['namespace'] = $namespace;
  336. }
  337. $cacheDef->addMethodCall('setNamespace', [$cacheDriver['namespace']]);
  338. $container->setDefinition($cacheDriverServiceId, $cacheDef);
  339. return $cacheDriverServiceId;
  340. }
  341. /**
  342. * Returns a modified version of $managerConfigs.
  343. *
  344. * The manager called $autoMappedManager will map all bundles that are not mapped by other managers.
  345. *
  346. * @return array
  347. */
  348. protected function fixManagersAutoMappings(array $managerConfigs, array $bundles)
  349. {
  350. if ($autoMappedManager = $this->validateAutoMapping($managerConfigs)) {
  351. foreach (array_keys($bundles) as $bundle) {
  352. foreach ($managerConfigs as $manager) {
  353. if (isset($manager['mappings'][$bundle])) {
  354. continue 2;
  355. }
  356. }
  357. $managerConfigs[$autoMappedManager]['mappings'][$bundle] = [
  358. 'mapping' => true,
  359. 'is_bundle' => true,
  360. ];
  361. }
  362. $managerConfigs[$autoMappedManager]['auto_mapping'] = false;
  363. }
  364. return $managerConfigs;
  365. }
  366. /**
  367. * Prefixes the relative dependency injection container path with the object manager prefix.
  368. *
  369. * @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager'
  370. *
  371. * @return string
  372. */
  373. abstract protected function getObjectManagerElementName(string $name);
  374. /**
  375. * Noun that describes the mapped objects such as Entity or Document.
  376. *
  377. * Will be used for autodetection of persistent objects directory.
  378. *
  379. * @return string
  380. */
  381. abstract protected function getMappingObjectDefaultName();
  382. /**
  383. * Relative path from the bundle root to the directory where mapping files reside.
  384. *
  385. * @return string
  386. */
  387. abstract protected function getMappingResourceConfigDirectory();
  388. /**
  389. * Extension used by the mapping files.
  390. *
  391. * @return string
  392. */
  393. abstract protected function getMappingResourceExtension();
  394. /**
  395. * The class name used by the various mapping drivers.
  396. */
  397. abstract protected function getMetadataDriverClass(string $driverType): string;
  398. /**
  399. * Search for a manager that is declared as 'auto_mapping' = true.
  400. *
  401. * @throws \LogicException
  402. */
  403. private function validateAutoMapping(array $managerConfigs): ?string
  404. {
  405. $autoMappedManager = null;
  406. foreach ($managerConfigs as $name => $manager) {
  407. if (!$manager['auto_mapping']) {
  408. continue;
  409. }
  410. if (null !== $autoMappedManager) {
  411. throw new \LogicException(sprintf('You cannot enable "auto_mapping" on more than one manager at the same time (found in "%s" and "%s"").', $autoMappedManager, $name));
  412. }
  413. $autoMappedManager = $name;
  414. }
  415. return $autoMappedManager;
  416. }
  417. }