vendor/friendsofsymfony/rest-bundle/DependencyInjection/FOSRestExtension.php line 387

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the FOSRestBundle package.
  4. *
  5. * (c) FriendsOfSymfony <http://friendsofsymfony.github.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 FOS\RestBundle\DependencyInjection;
  11. use FOS\RestBundle\ErrorRenderer\SerializerErrorRenderer;
  12. use FOS\RestBundle\EventListener\ResponseStatusCodeListener;
  13. use FOS\RestBundle\View\ViewHandler;
  14. use Symfony\Component\Config\FileLocator;
  15. use Symfony\Component\DependencyInjection\Alias;
  16. use Symfony\Component\DependencyInjection\ChildDefinition;
  17. use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
  18. use Symfony\Component\DependencyInjection\ContainerBuilder;
  19. use Symfony\Component\DependencyInjection\ContainerInterface;
  20. use Symfony\Component\DependencyInjection\Definition;
  21. use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  22. use Symfony\Component\DependencyInjection\Reference;
  23. use Symfony\Component\Form\Extension\Core\Type\FormType;
  24. use Symfony\Component\HttpFoundation\Response;
  25. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  26. use Symfony\Component\Validator\Constraint;
  27. /**
  28. * @internal
  29. */
  30. class FOSRestExtension extends Extension
  31. {
  32. /**
  33. * {@inheritdoc}
  34. */
  35. public function getConfiguration(array $config, ContainerBuilder $container): Configuration
  36. {
  37. return new Configuration($container->getParameter('kernel.debug'));
  38. }
  39. public function load(array $configs, ContainerBuilder $container): void
  40. {
  41. $configuration = new Configuration($container->getParameter('kernel.debug'));
  42. $config = $this->processConfiguration($configuration, $configs);
  43. $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
  44. $loader->load('view.xml');
  45. $loader->load('request.xml');
  46. $loader->load('serializer.xml');
  47. foreach ($config['service'] as $key => $service) {
  48. if ('validator' === $service && empty($config['body_converter']['validate'])) {
  49. continue;
  50. }
  51. if (null !== $service) {
  52. if ('view_handler' === $key) {
  53. $container->setAlias('fos_rest.'.$key, new Alias($service, true));
  54. } else {
  55. $container->setAlias('fos_rest.'.$key, $service);
  56. }
  57. }
  58. }
  59. $this->loadForm($config, $loader, $container);
  60. $this->loadException($config, $loader, $container);
  61. $this->loadBodyConverter($config, $loader, $container);
  62. $this->loadView($config, $loader, $container);
  63. $this->loadBodyListener($config, $loader, $container);
  64. $this->loadFormatListener($config, $loader, $container);
  65. $this->loadVersioning($config, $loader, $container);
  66. $this->loadParamFetcherListener($config, $loader, $container);
  67. $this->loadAllowedMethodsListener($config, $loader, $container);
  68. $this->loadZoneMatcherListener($config, $loader, $container);
  69. // Needs RequestBodyParamConverter and View Handler loaded.
  70. $this->loadSerializer($config, $container);
  71. }
  72. private function loadForm(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  73. {
  74. if (!empty($config['disable_csrf_role'])) {
  75. $loader->load('forms.xml');
  76. $definition = $container->getDefinition('fos_rest.form.extension.csrf_disable');
  77. $definition->replaceArgument(1, $config['disable_csrf_role']);
  78. $definition->addTag('form.type_extension', ['extended_type' => FormType::class]);
  79. }
  80. }
  81. private function loadAllowedMethodsListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  82. {
  83. if ($config['allowed_methods_listener']['enabled']) {
  84. if (!empty($config['allowed_methods_listener']['service'])) {
  85. $service = $container->getDefinition('fos_rest.allowed_methods_listener');
  86. $service->clearTag('kernel.event_listener');
  87. }
  88. $loader->load('allowed_methods_listener.xml');
  89. $container->getDefinition('fos_rest.allowed_methods_loader')->replaceArgument(1, $config['cache_dir']);
  90. }
  91. }
  92. private function loadBodyListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  93. {
  94. if ($config['body_listener']['enabled']) {
  95. $loader->load('body_listener.xml');
  96. $service = $container->getDefinition('fos_rest.body_listener');
  97. if (!empty($config['body_listener']['service'])) {
  98. $service->clearTag('kernel.event_listener');
  99. }
  100. $service->replaceArgument(1, $config['body_listener']['throw_exception_on_unsupported_content_type']);
  101. $service->addMethodCall('setDefaultFormat', [$config['body_listener']['default_format']]);
  102. $container->getDefinition('fos_rest.decoder_provider')->replaceArgument(1, $config['body_listener']['decoders']);
  103. $decoderServicesMap = [];
  104. foreach ($config['body_listener']['decoders'] as $id) {
  105. $decoderServicesMap[$id] = new Reference($id);
  106. }
  107. $decodersServiceLocator = ServiceLocatorTagPass::register($container, $decoderServicesMap);
  108. $container->getDefinition('fos_rest.decoder_provider')->replaceArgument(0, $decodersServiceLocator);
  109. $arrayNormalizer = $config['body_listener']['array_normalizer'];
  110. if (null !== $arrayNormalizer['service']) {
  111. $bodyListener = $container->getDefinition('fos_rest.body_listener');
  112. $bodyListener->addArgument(new Reference($arrayNormalizer['service']));
  113. $bodyListener->addArgument($arrayNormalizer['forms']);
  114. }
  115. }
  116. }
  117. private function loadFormatListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  118. {
  119. if ($config['format_listener']['enabled'] && !empty($config['format_listener']['rules'])) {
  120. $loader->load('format_listener.xml');
  121. if (!empty($config['format_listener']['service'])) {
  122. $service = $container->getDefinition('fos_rest.format_listener');
  123. $service->clearTag('kernel.event_listener');
  124. }
  125. $container->setParameter(
  126. 'fos_rest.format_listener.rules',
  127. $config['format_listener']['rules']
  128. );
  129. }
  130. }
  131. private function loadVersioning(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  132. {
  133. if (!empty($config['versioning']['enabled'])) {
  134. $loader->load('versioning.xml');
  135. $versionListener = $container->getDefinition('fos_rest.versioning.listener');
  136. $versionListener->replaceArgument(1, $config['versioning']['default_version']);
  137. $resolvers = [];
  138. if ($config['versioning']['resolvers']['query']['enabled']) {
  139. $resolvers['query'] = $container->getDefinition('fos_rest.versioning.query_parameter_resolver');
  140. $resolvers['query']->replaceArgument(0, $config['versioning']['resolvers']['query']['parameter_name']);
  141. }
  142. if ($config['versioning']['resolvers']['custom_header']['enabled']) {
  143. $resolvers['custom_header'] = $container->getDefinition('fos_rest.versioning.header_resolver');
  144. $resolvers['custom_header']->replaceArgument(0, $config['versioning']['resolvers']['custom_header']['header_name']);
  145. }
  146. if ($config['versioning']['resolvers']['media_type']['enabled']) {
  147. $resolvers['media_type'] = $container->getDefinition('fos_rest.versioning.media_type_resolver');
  148. $resolvers['media_type']->replaceArgument(0, $config['versioning']['resolvers']['media_type']['regex']);
  149. }
  150. $chainResolver = $container->getDefinition('fos_rest.versioning.chain_resolver');
  151. foreach ($config['versioning']['guessing_order'] as $resolver) {
  152. if (isset($resolvers[$resolver])) {
  153. $chainResolver->addMethodCall('addResolver', [$resolvers[$resolver]]);
  154. }
  155. }
  156. }
  157. }
  158. private function loadParamFetcherListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  159. {
  160. if ($config['param_fetcher_listener']['enabled']) {
  161. if (!class_exists(Constraint::class)) {
  162. throw new \LogicException('Enabling the fos_rest.param_fetcher_listener option when the Symfony Validator component is not installed is not supported. Try installing the symfony/validator package.');
  163. }
  164. $loader->load('param_fetcher_listener.xml');
  165. if (!empty($config['param_fetcher_listener']['service'])) {
  166. $service = $container->getDefinition('fos_rest.param_fetcher_listener');
  167. $service->clearTag('kernel.event_listener');
  168. }
  169. if ($config['param_fetcher_listener']['force']) {
  170. $container->getDefinition('fos_rest.param_fetcher_listener')->replaceArgument(1, true);
  171. }
  172. }
  173. }
  174. private function loadBodyConverter(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  175. {
  176. if (!$this->isConfigEnabled($container, $config['body_converter'])) {
  177. return;
  178. }
  179. $loader->load('request_body_param_converter.xml');
  180. if (!empty($config['body_converter']['validation_errors_argument'])) {
  181. $container->getDefinition('fos_rest.converter.request_body')->replaceArgument(4, $config['body_converter']['validation_errors_argument']);
  182. }
  183. }
  184. private function loadView(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  185. {
  186. if (!empty($config['view']['jsonp_handler'])) {
  187. $handler = new ChildDefinition($config['service']['view_handler']);
  188. $handler->setPublic(true);
  189. $jsonpHandler = new Reference('fos_rest.view_handler.jsonp');
  190. $handler->addMethodCall('registerHandler', ['jsonp', [$jsonpHandler, 'createResponse']]);
  191. $container->setDefinition('fos_rest.view_handler', $handler);
  192. $container->getDefinition('fos_rest.view_handler.jsonp')->replaceArgument(0, $config['view']['jsonp_handler']['callback_param']);
  193. if (empty($config['view']['mime_types']['jsonp'])) {
  194. $config['view']['mime_types']['jsonp'] = $config['view']['jsonp_handler']['mime_type'];
  195. }
  196. }
  197. if ($config['view']['mime_types']['enabled']) {
  198. $loader->load('mime_type_listener.xml');
  199. if (!empty($config['mime_type_listener']['service'])) {
  200. $service = $container->getDefinition('fos_rest.mime_type_listener');
  201. $service->clearTag('kernel.event_listener');
  202. }
  203. $container->getDefinition('fos_rest.mime_type_listener')->replaceArgument(0, $config['view']['mime_types']['formats']);
  204. }
  205. if ($config['view']['view_response_listener']['enabled']) {
  206. $loader->load('view_response_listener.xml');
  207. $service = $container->getDefinition('fos_rest.view_response_listener');
  208. if (!empty($config['view_response_listener']['service'])) {
  209. $service->clearTag('kernel.event_listener');
  210. }
  211. $service->replaceArgument(1, $config['view']['view_response_listener']['force']);
  212. }
  213. $formats = [];
  214. foreach ($config['view']['formats'] as $format => $enabled) {
  215. if ($enabled) {
  216. $formats[$format] = false;
  217. }
  218. }
  219. if (!is_numeric($config['view']['failed_validation'])) {
  220. $config['view']['failed_validation'] = constant(sprintf('%s::%s', Response::class, $config['view']['failed_validation']));
  221. }
  222. if (!is_numeric($config['view']['empty_content'])) {
  223. $config['view']['empty_content'] = constant(sprintf('%s::%s', Response::class, $config['view']['empty_content']));
  224. }
  225. $defaultViewHandler = $container->getDefinition('fos_rest.view_handler.default');
  226. $defaultViewHandler->setFactory([ViewHandler::class, 'create']);
  227. $defaultViewHandler->setArguments([
  228. new Reference('router'),
  229. new Reference('fos_rest.serializer'),
  230. new Reference('request_stack'),
  231. $formats,
  232. $config['view']['failed_validation'],
  233. $config['view']['empty_content'],
  234. $config['view']['serialize_null'],
  235. ]);
  236. }
  237. private function loadException(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  238. {
  239. if ($config['exception']['enabled']) {
  240. $loader->load('exception.xml');
  241. if ($config['exception']['map_exception_codes']) {
  242. $container->register('fos_rest.exception.response_status_code_listener', ResponseStatusCodeListener::class)
  243. ->setArguments([
  244. new Reference('fos_rest.exception.codes_map'),
  245. ])
  246. ->addTag('kernel.event_subscriber');
  247. }
  248. $container->getDefinition('fos_rest.exception.codes_map')
  249. ->replaceArgument(0, $config['exception']['codes']);
  250. $container->getDefinition('fos_rest.exception.messages_map')
  251. ->replaceArgument(0, $config['exception']['messages']);
  252. $container->getDefinition('fos_rest.serializer.flatten_exception_handler')
  253. ->replaceArgument(2, $config['exception']['debug']);
  254. $container->getDefinition('fos_rest.serializer.flatten_exception_handler')
  255. ->replaceArgument(3, 'rfc7807' === $config['exception']['flatten_exception_format']);
  256. $container->getDefinition('fos_rest.serializer.flatten_exception_normalizer')
  257. ->replaceArgument(2, $config['exception']['debug']);
  258. $container->getDefinition('fos_rest.serializer.flatten_exception_normalizer')
  259. ->replaceArgument(3, 'rfc7807' === $config['exception']['flatten_exception_format']);
  260. if ($config['exception']['serializer_error_renderer']) {
  261. $format = new Definition();
  262. $format->setFactory([SerializerErrorRenderer::class, 'getPreferredFormat']);
  263. $format->setArguments([
  264. new Reference('request_stack'),
  265. ]);
  266. $debug = new Definition();
  267. $debug->setFactory([SerializerErrorRenderer::class, 'isDebug']);
  268. $debug->setArguments([
  269. new Reference('request_stack'),
  270. '%kernel.debug%',
  271. ]);
  272. $container->register('fos_rest.error_renderer.serializer', SerializerErrorRenderer::class)
  273. ->setArguments([
  274. new Reference('fos_rest.serializer'),
  275. $format,
  276. new Reference('error_renderer.html', ContainerInterface::NULL_ON_INVALID_REFERENCE),
  277. $debug,
  278. ]);
  279. $container->setAlias('error_renderer', 'fos_rest.error_renderer.serializer');
  280. }
  281. }
  282. }
  283. private function loadSerializer(array $config, ContainerBuilder $container): void
  284. {
  285. $bodyConverter = $container->hasDefinition('fos_rest.converter.request_body') ? $container->getDefinition('fos_rest.converter.request_body') : null;
  286. $viewHandler = $container->getDefinition('fos_rest.view_handler.default');
  287. $options = [];
  288. if (!empty($config['serializer']['version'])) {
  289. if ($bodyConverter) {
  290. $bodyConverter->replaceArgument(2, $config['serializer']['version']);
  291. }
  292. $options['exclusionStrategyVersion'] = $config['serializer']['version'];
  293. }
  294. if (!empty($config['serializer']['groups'])) {
  295. if ($bodyConverter) {
  296. $bodyConverter->replaceArgument(1, $config['serializer']['groups']);
  297. }
  298. $options['exclusionStrategyGroups'] = $config['serializer']['groups'];
  299. }
  300. $options['serializeNullStrategy'] = $config['serializer']['serialize_null'];
  301. $viewHandler->addArgument($options);
  302. }
  303. private function loadZoneMatcherListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void
  304. {
  305. if (!empty($config['zone'])) {
  306. $loader->load('zone_matcher_listener.xml');
  307. $zoneMatcherListener = $container->getDefinition('fos_rest.zone_matcher_listener');
  308. foreach ($config['zone'] as $zone) {
  309. $matcher = $this->createZoneRequestMatcher(
  310. $container,
  311. $zone['path'],
  312. $zone['host'],
  313. $zone['methods'],
  314. $zone['ips']
  315. );
  316. $zoneMatcherListener->addMethodCall('addRequestMatcher', [$matcher]);
  317. }
  318. }
  319. }
  320. private function createZoneRequestMatcher(ContainerBuilder $container, ?string $path = null, ?string $host = null, array $methods = [], array $ips = null): Reference
  321. {
  322. if ($methods) {
  323. $methods = array_map('strtoupper', (array) $methods);
  324. }
  325. $serialized = serialize([$path, $host, $methods, $ips]);
  326. $id = 'fos_rest.zone_request_matcher.'.md5($serialized).sha1($serialized);
  327. // only add arguments that are necessary
  328. $arguments = [$path, $host, $methods, $ips];
  329. while (count($arguments) > 0 && !end($arguments)) {
  330. array_pop($arguments);
  331. }
  332. $container
  333. ->setDefinition($id, new ChildDefinition('fos_rest.zone_request_matcher'))
  334. ->setArguments($arguments)
  335. ;
  336. return new Reference($id);
  337. }
  338. }