AbstractDoctrineExtension.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.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\DoctrineAbstractBundle\DependencyInjection;
  11. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\Definition;
  14. use Symfony\Component\DependencyInjection\Reference;
  15. use Symfony\Component\Config\Resource\FileResource;
  16. /**
  17. * This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need.
  18. */
  19. abstract class AbstractDoctrineExtension extends Extension
  20. {
  21. /**
  22. * Used inside metadata driver method to simplify aggregation of data.
  23. *
  24. * @var array
  25. */
  26. protected $aliasMap = array();
  27. /**
  28. * Used inside metadata driver method to simplify aggregation of data.
  29. *
  30. * @var array
  31. */
  32. protected $drivers = array();
  33. /*
  34. * @param array $entityManager A configured ORM entity manager.
  35. * @param ContainerBuilder $container A ContainerBuilder instance
  36. */
  37. protected function loadMappingInformation(array $objectManager, $container)
  38. {
  39. if (isset($objectManager['mappings'])) {
  40. // fix inconsistency between yaml and xml naming
  41. if (isset($objectManager['mappings']['mapping'])) {
  42. if (isset($objectManager['mappings']['mapping'][0])) {
  43. $objectManager['mappings'] = $objectManager['mappings']['mapping'];
  44. } else {
  45. $objectManager['mappings'] = array($objectManager['mappings']['mapping']);
  46. }
  47. }
  48. foreach ($objectManager['mappings'] as $mappingName => $mappingConfig) {
  49. if (is_string($mappingConfig)) {
  50. $mappingConfig = array('type' => $mappingConfig);
  51. }
  52. if (!isset($mappingConfig['dir'])) {
  53. $mappingConfig['dir'] = false;
  54. }
  55. if (!isset($mappingConfig['type'])) {
  56. $mappingConfig['type'] = false;
  57. }
  58. if (!isset($mappingConfig['prefix'])) {
  59. $mappingConfig['prefix'] = false;
  60. }
  61. $mappingConfig['dir'] = $container->getParameterBag()->resolveValue($mappingConfig['dir']);
  62. // a bundle configuration is detected by realizing that the specified dir is not absolute and existing
  63. if (isset($mappingConfig['is-bundle'])) {
  64. $mappingConfig['is_bundle'] = $mappingConfig['is-bundle'];
  65. }
  66. if (!isset($mappingConfig['is_bundle'])) {
  67. $mappingConfig['is_bundle'] = !file_exists($mappingConfig['dir']);
  68. }
  69. if (isset($mappingConfig['name'])) {
  70. $mappingName = $mappingConfig['name'];
  71. } else if ($mappingConfig === null) {
  72. $mappingConfig = array();
  73. }
  74. if ($mappingConfig['is_bundle']) {
  75. $bundle = null;
  76. foreach ($container->getParameter('kernel.bundles') as $name => $class) {
  77. if ($mappingName === $name) {
  78. $bundle = new \ReflectionClass($class);
  79. break;
  80. }
  81. }
  82. if (null === $bundle) {
  83. throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName));
  84. }
  85. $mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container);
  86. if (!$mappingConfig) {
  87. continue;
  88. }
  89. }
  90. $this->assertValidMappingConfiguration($mappingConfig, $objectManager['name']);
  91. $this->setMappingDriverConfig($mappingConfig, $mappingName);
  92. $this->setMappingDriverAlias($mappingConfig, $mappingName);
  93. }
  94. }
  95. }
  96. /**
  97. * Register the alias for this mapping driver.
  98. *
  99. * Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks.
  100. *
  101. * @param array $mappingConfig
  102. * @param string $mappingName
  103. * @return void
  104. */
  105. protected function setMappingDriverAlias($mappingConfig, $mappingName)
  106. {
  107. if (isset($mappingConfig['alias'])) {
  108. $this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix'];
  109. } else {
  110. $this->aliasMap[$mappingName] = $mappingConfig['prefix'];
  111. }
  112. }
  113. /**
  114. * Register the mapping driver configuration for later use with the object managers metadata driver chain.
  115. *
  116. * @param array $mappingConfig
  117. * @param string $mappingName
  118. * @return void
  119. */
  120. protected function setMappingDriverConfig(array $mappingConfig, $mappingName)
  121. {
  122. if (is_dir($mappingConfig['dir'])) {
  123. if (!isset($this->drivers[$mappingConfig['type']])) {
  124. $this->drivers[$mappingConfig['type']] = array();
  125. }
  126. $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingConfig['dir']);
  127. } else {
  128. throw new \InvalidArgumentException("Invalid mapping path given. ".
  129. "Cannot load mapping/bundle named '" . $mappingName . "'.");
  130. }
  131. }
  132. /**
  133. * If this is a bundle controlled mapping all the missing information can be autodetected by this method.
  134. *
  135. * Returns false when autodetection failed, an array of the completed information otherwise.
  136. *
  137. * @param array $bundleConfig
  138. * @param \ReflectionClass $bundle
  139. * @param Container $container
  140. *
  141. * @return array|false
  142. */
  143. protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, $container)
  144. {
  145. $bundleDir = dirname($bundle->getFilename());
  146. if (!$bundleConfig['type']) {
  147. $bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container);
  148. }
  149. if (!$bundleConfig['type']) {
  150. // skip this bundle, no mapping information was found.
  151. return false;
  152. }
  153. if (!$bundleConfig['dir']) {
  154. if (in_array($bundleConfig['type'], array('annotation', 'static-php'))) {
  155. $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingObjectDefaultName();
  156. } else {
  157. $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory();
  158. }
  159. } else {
  160. $bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir'];
  161. }
  162. if (!$bundleConfig['prefix']) {
  163. $bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName();
  164. }
  165. return $bundleConfig;
  166. }
  167. /**
  168. * Register all the collected mapping information with the object manager by registering the appropiate mapping drivers.
  169. *
  170. * @param array $objectManager
  171. * @param Container $container
  172. */
  173. protected function registerMappingDrivers($objectManager, $container)
  174. {
  175. // configure metadata driver for each bundle based on the type of mapping files found
  176. if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'] . '_metadata_driver'))) {
  177. $chainDriverDef = $container->getDefinition($this->getObjectManagerElementName($objectManager['name'] . '_metadata_driver'));
  178. } else {
  179. $chainDriverDef = new Definition('%'.$this->getObjectManagerElementName('metadata.driver_chain_class%'));
  180. $chainDriverDef->setPublic(false);
  181. }
  182. foreach ($this->drivers as $driverType => $driverPaths) {
  183. $mappingService = $this->getObjectManagerElementName($objectManager['name'] . '_'.$driverType.'_metadata_driver');
  184. if ($container->hasDefinition($mappingService)) {
  185. $mappingDriverDef = $container->getDefinition($mappingService);
  186. $args = $mappingDriverDef->getArguments();
  187. if ($driverType == 'annotation') {
  188. $args[1] = array_merge($driverPaths, $args[1]);
  189. } else {
  190. $args[0] = array_merge($driverPaths, $args[0]);
  191. }
  192. $mappingDriverDef->setArguments($args);
  193. } else if ($driverType == 'annotation') {
  194. $mappingDriverDef = new Definition('%'.$this->getObjectManagerElementName('metadata.' . $driverType . '_class%'), array(
  195. new Reference($this->getObjectManagerElementName('metadata.annotation_reader')),
  196. array_values($driverPaths)
  197. ));
  198. } else {
  199. $mappingDriverDef = new Definition('%'.$this->getObjectManagerElementName('metadata.' . $driverType . '_class%'), array(
  200. array_values($driverPaths)
  201. ));
  202. }
  203. $mappingDriverDef->setPublic(false);
  204. $container->setDefinition($mappingService, $mappingDriverDef);
  205. foreach ($driverPaths as $prefix => $driverPath) {
  206. $chainDriverDef->addMethodCall('addDriver', array(new Reference($mappingService), $prefix));
  207. }
  208. }
  209. $container->setDefinition($this->getObjectManagerElementName($objectManager['name'] . '_metadata_driver'), $chainDriverDef);
  210. }
  211. /**
  212. * Assertion if the specified mapping information is valid.
  213. *
  214. * @param array $mappingConfig
  215. * @param string $objectManagerName
  216. */
  217. protected function assertValidMappingConfiguration(array $mappingConfig, $objectManagerName)
  218. {
  219. if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) {
  220. throw new \InvalidArgumentException("Mapping definitions for manager '".$objectManagerName."' ".
  221. "require at least the 'type', 'dir' and 'prefix' options.");
  222. }
  223. if (!file_exists($mappingConfig['dir'])) {
  224. throw new \InvalidArgumentException("Specified non-existing directory '" . $mappingConfig['dir'] . "' as mapping source.");
  225. }
  226. if (!in_array($mappingConfig['type'], array('xml', 'yml', 'annotation', 'php', 'staticphp'))) {
  227. throw new \InvalidArgumentException("Can only configure 'xml', 'yml', 'annotation', 'php' or ".
  228. "'static-php' through the DoctrineBundle. Use your own bundle to configure other metadata drivers. " .
  229. "You can register them by adding a a new driver to the ".
  230. "'" . $this->getObjectManagerElementName($objectManagerName . ".metadata_driver")."' service definition."
  231. );
  232. }
  233. }
  234. /**
  235. * Detects what metadata driver to use for the supplied directory.
  236. *
  237. * @param string $dir A directory path
  238. * @param ContainerBuilder $container A ContainerBuilder configuration
  239. *
  240. * @return string|null A metadata driver short name, if one can be detected
  241. */
  242. protected function detectMetadataDriver($dir, ContainerBuilder $container)
  243. {
  244. // add the closest existing directory as a resource
  245. $configPath = $this->getMappingResourceConfigDirectory();
  246. $resource = $dir.'/'.$configPath;
  247. while (!is_dir($resource)) {
  248. $resource = dirname($resource);
  249. }
  250. $container->addResource(new FileResource($resource));
  251. if (($files = glob($dir.'/'.$configPath.'/*.xml')) && count($files)) {
  252. return 'xml';
  253. } elseif (($files = glob($dir.'/'.$configPath.'/*.yml')) && count($files)) {
  254. return 'yml';
  255. } elseif (($files = glob($dir.'/'.$configPath.'/*.php')) && count($files)) {
  256. return 'php';
  257. }
  258. // add the directory itself as a resource
  259. $container->addResource(new FileResource($dir));
  260. if (is_dir($dir.'/'.$this->getMappingObjectDefaultName())) {
  261. return 'annotation';
  262. }
  263. return null;
  264. }
  265. /**
  266. * Prefixes the relative dependency injection container path with the object manager prefix.
  267. *
  268. * @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager'
  269. *
  270. * @param string $name
  271. * @return string
  272. */
  273. abstract protected function getObjectManagerElementName($name);
  274. /**
  275. * Noun that describes the mapped objects such as Entity or Document.
  276. *
  277. * Will be used for autodetection of persistent objects directory.
  278. *
  279. * @return string
  280. */
  281. abstract protected function getMappingObjectDefaultName();
  282. /**
  283. * Relative path from the bundle root to the directory where mapping files reside.
  284. *
  285. * @return string
  286. */
  287. abstract protected function getMappingResourceConfigDirectory();
  288. }