vendor/nelmio/api-doc-bundle/src/DependencyInjection/NelmioApiDocExtension.php line 239

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the NelmioApiDocBundle package.
  4. *
  5. * (c) Nelmio
  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 Nelmio\ApiDocBundle\DependencyInjection;
  11. use FOS\RestBundle\Controller\Annotations\ParamInterface;
  12. use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface;
  13. use JMS\Serializer\Visitor\SerializationVisitorInterface;
  14. use Nelmio\ApiDocBundle\ApiDocGenerator;
  15. use Nelmio\ApiDocBundle\Describer\ExternalDocDescriber;
  16. use Nelmio\ApiDocBundle\Describer\OpenApiPhpDescriber;
  17. use Nelmio\ApiDocBundle\Describer\RouteDescriber;
  18. use Nelmio\ApiDocBundle\ModelDescriber\BazingaHateoasModelDescriber;
  19. use Nelmio\ApiDocBundle\ModelDescriber\JMSModelDescriber;
  20. use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface;
  21. use Nelmio\ApiDocBundle\Processor\MapQueryStringProcessor;
  22. use Nelmio\ApiDocBundle\Processor\MapRequestPayloadProcessor;
  23. use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber;
  24. use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\RouteArgumentDescriberInterface;
  25. use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapQueryParameterDescriber;
  26. use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapQueryStringDescriber;
  27. use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapRequestPayloadDescriber;
  28. use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder;
  29. use OpenApi\Generator;
  30. use Symfony\Component\Config\FileLocator;
  31. use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
  32. use Symfony\Component\DependencyInjection\ContainerBuilder;
  33. use Symfony\Component\DependencyInjection\ContainerInterface;
  34. use Symfony\Component\DependencyInjection\Definition;
  35. use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
  36. use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  37. use Symfony\Component\DependencyInjection\Reference;
  38. use Symfony\Component\DependencyInjection\ServiceLocator;
  39. use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
  40. use Symfony\Component\HttpKernel\Attribute\MapQueryString;
  41. use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
  42. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  43. use Symfony\Component\Routing\RouteCollection;
  44. final class NelmioApiDocExtension extends Extension implements PrependExtensionInterface
  45. {
  46. /**
  47. * {@inheritdoc}
  48. */
  49. public function prepend(ContainerBuilder $container): void
  50. {
  51. $container->prependExtensionConfig('framework', ['property_info' => ['enabled' => true]]);
  52. $bundles = $container->getParameter('kernel.bundles');
  53. // JMS Serializer support
  54. if (isset($bundles['JMSSerializerBundle'])) {
  55. $container->prependExtensionConfig('nelmio_api_doc', ['models' => ['use_jms' => true]]);
  56. }
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. public function load(array $configs, ContainerBuilder $container): void
  62. {
  63. $config = $this->processConfiguration(new Configuration(), $configs);
  64. $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../../config'));
  65. $loader->load('services.xml');
  66. // Filter routes
  67. $routesDefinition = (new Definition(RouteCollection::class))
  68. ->setFactory([new Reference('router'), 'getRouteCollection']);
  69. $container->setParameter('nelmio_api_doc.areas', array_keys($config['areas']));
  70. $container->setParameter('nelmio_api_doc.media_types', $config['media_types']);
  71. $container->setParameter('nelmio_api_doc.use_validation_groups', $config['use_validation_groups']);
  72. // Register the OpenAPI Generator as a service.
  73. $container->register('nelmio_api_doc.open_api.generator', Generator::class)
  74. ->setPublic(false);
  75. $cachePool = $config['cache']['pool'] ?? null;
  76. $cacheItemId = $config['cache']['item_id'] ?? 'openapi_doc';
  77. foreach ($config['areas'] as $area => $areaConfig) {
  78. $areaCachePool = $areaConfig['cache']['pool'] ?? $cachePool;
  79. $areaCacheItemId = $areaConfig['cache']['item_id'] ?? sprintf('%s.%s', $cacheItemId, $area);
  80. $nameAliases = $this->findNameAliases($config['models']['names'], $area);
  81. $container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class)
  82. ->setPublic(true)
  83. ->addMethodCall('setAlternativeNames', [$nameAliases])
  84. ->addMethodCall('setMediaTypes', [$config['media_types']])
  85. ->addMethodCall('setLogger', [new Reference('logger')])
  86. ->addMethodCall('setOpenApiVersion', [$config['documentation']['openapi'] ?? null])
  87. ->addTag('monolog.logger', ['channel' => 'nelmio_api_doc'])
  88. ->setArguments([
  89. new TaggedIteratorArgument(sprintf('nelmio_api_doc.describer.%s', $area)),
  90. new TaggedIteratorArgument('nelmio_api_doc.model_describer'),
  91. null !== $areaCachePool ? new Reference($areaCachePool) : null,
  92. $areaCacheItemId,
  93. new Reference('nelmio_api_doc.open_api.generator'),
  94. ]);
  95. $container->register(sprintf('nelmio_api_doc.describers.route.%s', $area), RouteDescriber::class)
  96. ->setPublic(false)
  97. ->setArguments([
  98. new Reference(sprintf('nelmio_api_doc.routes.%s', $area)),
  99. new Reference('nelmio_api_doc.controller_reflector'),
  100. new TaggedIteratorArgument('nelmio_api_doc.route_describer'),
  101. ])
  102. ->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -400]);
  103. $container->register(sprintf('nelmio_api_doc.describers.openapi_php.%s', $area), OpenApiPhpDescriber::class)
  104. ->setPublic(false)
  105. ->setArguments([
  106. new Reference(sprintf('nelmio_api_doc.routes.%s', $area)),
  107. new Reference('nelmio_api_doc.controller_reflector'),
  108. new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), // We cannot use the cached version of the annotation reader since the construction of the annotations is context dependant...
  109. new Reference('logger'),
  110. ])
  111. ->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -200]);
  112. $container->register(sprintf('nelmio_api_doc.describers.config.%s', $area), ExternalDocDescriber::class)
  113. ->setPublic(false)
  114. ->setArguments([
  115. $areaConfig['documentation'],
  116. true,
  117. ])
  118. ->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => 990]);
  119. unset($areaConfig['documentation']);
  120. if (0 === count($areaConfig['path_patterns'])
  121. && 0 === count($areaConfig['host_patterns'])
  122. && 0 === count($areaConfig['name_patterns'])
  123. && false === $areaConfig['with_annotation']
  124. && false === $areaConfig['disable_default_routes']
  125. ) {
  126. $container->setDefinition(sprintf('nelmio_api_doc.routes.%s', $area), $routesDefinition)
  127. ->setPublic(false);
  128. } else {
  129. $container->register(sprintf('nelmio_api_doc.routes.%s', $area), RouteCollection::class)
  130. ->setPublic(false)
  131. ->setFactory([
  132. (new Definition(FilteredRouteCollectionBuilder::class))
  133. ->setArguments(
  134. [
  135. new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), // Here we use the cached version as we don't deal with @OA annotations in this service
  136. new Reference('nelmio_api_doc.controller_reflector'),
  137. $area,
  138. $areaConfig,
  139. ]
  140. ),
  141. 'filter',
  142. ])
  143. ->addArgument($routesDefinition);
  144. }
  145. }
  146. $container->register('nelmio_api_doc.generator_locator', ServiceLocator::class)
  147. ->setPublic(false)
  148. ->addTag('container.service_locator')
  149. ->addArgument(array_combine(
  150. array_keys($config['areas']),
  151. array_map(function ($area) { return new Reference(sprintf('nelmio_api_doc.generator.%s', $area)); }, array_keys($config['areas']))
  152. ));
  153. $container->getDefinition('nelmio_api_doc.model_describers.object')
  154. ->setArgument(3, $config['media_types']);
  155. // Add autoconfiguration for model describer
  156. $container->registerForAutoconfiguration(ModelDescriberInterface::class)
  157. ->addTag('nelmio_api_doc.model_describer');
  158. // Import services needed for each library
  159. $loader->load('php_doc.xml');
  160. if (interface_exists(ParamInterface::class)) {
  161. $loader->load('fos_rest.xml');
  162. $container->getDefinition('nelmio_api_doc.route_describers.fos_rest')
  163. ->setArgument(1, $config['media_types']);
  164. }
  165. if (PHP_VERSION_ID > 80100) {
  166. // Add autoconfiguration for route argument describer
  167. $container->registerForAutoconfiguration(RouteArgumentDescriberInterface::class)
  168. ->addTag('nelmio_api_doc.route_argument_describer');
  169. $container->register('nelmio_api_doc.route_describers.route_argument', RouteArgumentDescriber::class)
  170. ->setPublic(false)
  171. ->addTag('nelmio_api_doc.route_describer', ['priority' => -225])
  172. ->setArguments([
  173. new Reference('argument_metadata_factory'),
  174. new TaggedIteratorArgument('nelmio_api_doc.route_argument_describer'),
  175. ])
  176. ;
  177. if (class_exists(MapQueryString::class)) {
  178. $container->register('nelmio_api_doc.route_argument_describer.map_query_string', SymfonyMapQueryStringDescriber::class)
  179. ->setPublic(false)
  180. ->addTag('nelmio_api_doc.route_argument_describer', ['priority' => 0]);
  181. $container->register('nelmio_api_doc.swagger.processor.map_query_string', MapQueryStringProcessor::class)
  182. ->setPublic(false)
  183. ->addTag('nelmio_api_doc.swagger.processor', ['priority' => 0]);
  184. }
  185. if (class_exists(MapRequestPayload::class)) {
  186. $container->register('nelmio_api_doc.route_argument_describer.map_request_payload', SymfonyMapRequestPayloadDescriber::class)
  187. ->setPublic(false)
  188. ->addTag('nelmio_api_doc.route_argument_describer', ['priority' => 0]);
  189. $container->register('nelmio_api_doc.swagger.processor.map_request_payload', MapRequestPayloadProcessor::class)
  190. ->setPublic(false)
  191. ->addTag('nelmio_api_doc.swagger.processor', ['priority' => 0]);
  192. }
  193. if (class_exists(MapQueryParameter::class)) {
  194. $container->register('nelmio_api_doc.route_argument_describer.map_query_parameter', SymfonyMapQueryParameterDescriber::class)
  195. ->setPublic(false)
  196. ->addTag('nelmio_api_doc.route_argument_describer', ['priority' => 0]);
  197. }
  198. }
  199. $bundles = $container->getParameter('kernel.bundles');
  200. if (!isset($bundles['TwigBundle']) || !class_exists('Symfony\Component\Asset\Packages')) {
  201. $container->removeDefinition('nelmio_api_doc.controller.swagger_ui');
  202. $container->removeDefinition('nelmio_api_doc.render_docs.html');
  203. $container->removeDefinition('nelmio_api_doc.render_docs.html.asset');
  204. }
  205. // ApiPlatform support
  206. if (isset($bundles['ApiPlatformBundle']) && class_exists('ApiPlatform\Documentation\Documentation')) {
  207. $loader->load('api_platform.xml');
  208. }
  209. // JMS metadata support
  210. if ($config['models']['use_jms']) {
  211. $jmsNamingStrategy = interface_exists(SerializationVisitorInterface::class) ? null : new Reference('jms_serializer.naming_strategy');
  212. $contextFactory = interface_exists(SerializationContextFactoryInterface::class) ? new Reference('jms_serializer.serialization_context_factory') : null;
  213. $container->register('nelmio_api_doc.model_describers.jms', JMSModelDescriber::class)
  214. ->setPublic(false)
  215. ->setArguments([
  216. new Reference('jms_serializer.metadata_factory'),
  217. new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE),
  218. $config['media_types'],
  219. $jmsNamingStrategy,
  220. $container->getParameter('nelmio_api_doc.use_validation_groups'),
  221. $contextFactory,
  222. ])
  223. ->addTag('nelmio_api_doc.model_describer', ['priority' => 50]);
  224. // Bazinga Hateoas metadata support
  225. if (isset($bundles['BazingaHateoasBundle'])) {
  226. $container->register('nelmio_api_doc.model_describers.jms.bazinga_hateoas', BazingaHateoasModelDescriber::class)
  227. ->setDecoratedService('nelmio_api_doc.model_describers.jms', 'nelmio_api_doc.model_describers.jms.inner')
  228. ->setPublic(false)
  229. ->setArguments([
  230. new Reference('hateoas.configuration.metadata_factory'),
  231. new Reference('nelmio_api_doc.model_describers.jms.inner'),
  232. ]);
  233. }
  234. } else {
  235. $container->removeDefinition('nelmio_api_doc.model_describers.object_fallback');
  236. }
  237. // Import the base configuration
  238. $container->getDefinition('nelmio_api_doc.describers.config')->replaceArgument(0, $config['documentation']);
  239. // Compatibility Symfony
  240. $controllerNameConverter = null;
  241. if ($container->hasDefinition('.legacy_controller_name_converter')) { // 4.4
  242. $controllerNameConverter = $container->getDefinition('.legacy_controller_name_converter');
  243. } elseif ($container->hasDefinition('controller_name_converter')) { // < 4.4
  244. $controllerNameConverter = $container->getDefinition('controller_name_converter');
  245. }
  246. if (null !== $controllerNameConverter) {
  247. $container->getDefinition('nelmio_api_doc.controller_reflector')->setArgument(1, $controllerNameConverter);
  248. }
  249. }
  250. private function findNameAliases(array $names, string $area): array
  251. {
  252. $nameAliases = array_filter($names, function (array $aliasInfo) use ($area) {
  253. return empty($aliasInfo['areas']) || in_array($area, $aliasInfo['areas'], true);
  254. });
  255. $aliases = [];
  256. foreach ($nameAliases as $nameAlias) {
  257. $aliases[$nameAlias['alias']] = [
  258. 'type' => $nameAlias['type'],
  259. 'groups' => $nameAlias['groups'],
  260. ];
  261. }
  262. return $aliases;
  263. }
  264. }