vendor/api-platform/core/src/Symfony/Bundle/DependencyInjection/Configuration.php line 647

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the API Platform project.
  4. *
  5. * (c) Kévin Dunglas <dunglas@gmail.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. declare(strict_types=1);
  11. namespace ApiPlatform\Symfony\Bundle\DependencyInjection;
  12. use ApiPlatform\Core\Annotation\ApiResource as LegacyApiResource;
  13. use ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface;
  14. use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata;
  15. use ApiPlatform\Exception\FilterValidationException;
  16. use ApiPlatform\Exception\InvalidArgumentException;
  17. use ApiPlatform\Metadata\ApiResource;
  18. use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
  19. use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle;
  20. use Doctrine\ORM\EntityManagerInterface;
  21. use Doctrine\ORM\OptimisticLockException;
  22. use Elasticsearch\Client as ElasticsearchClient;
  23. use FOS\UserBundle\FOSUserBundle;
  24. use GraphQL\GraphQL;
  25. use Symfony\Bundle\FullStack;
  26. use Symfony\Bundle\MakerBundle\MakerBundle;
  27. use Symfony\Bundle\MercureBundle\MercureBundle;
  28. use Symfony\Bundle\TwigBundle\TwigBundle;
  29. use Symfony\Component\Config\Definition\BaseNode;
  30. use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
  31. use Symfony\Component\Config\Definition\Builder\TreeBuilder;
  32. use Symfony\Component\Config\Definition\ConfigurationInterface;
  33. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  34. use Symfony\Component\HttpFoundation\Response;
  35. use Symfony\Component\Messenger\MessageBusInterface;
  36. use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerExceptionInterface;
  37. use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
  38. /**
  39. * The configuration of the bundle.
  40. *
  41. * @author Kévin Dunglas <dunglas@gmail.com>
  42. * @author Baptiste Meyer <baptiste.meyer@gmail.com>
  43. */
  44. final class Configuration implements ConfigurationInterface
  45. {
  46. /**
  47. * {@inheritdoc}
  48. */
  49. public function getConfigTreeBuilder(): TreeBuilder
  50. {
  51. if (method_exists(TreeBuilder::class, 'getRootNode')) {
  52. $treeBuilder = new TreeBuilder('api_platform');
  53. $rootNode = $treeBuilder->getRootNode();
  54. } else {
  55. $treeBuilder = new TreeBuilder();
  56. $rootNode = $treeBuilder->root('api_platform');
  57. }
  58. $rootNode
  59. ->beforeNormalization()
  60. ->ifTrue(static function ($v) {
  61. return false === ($v['enable_swagger'] ?? null);
  62. })
  63. ->then(static function ($v) {
  64. $v['swagger']['versions'] = [];
  65. return $v;
  66. })
  67. ->end()
  68. ->children()
  69. ->scalarNode('title')
  70. ->info('The title of the API.')
  71. ->cannotBeEmpty()
  72. ->defaultValue('')
  73. ->end()
  74. ->scalarNode('description')
  75. ->info('The description of the API.')
  76. ->cannotBeEmpty()
  77. ->defaultValue('')
  78. ->end()
  79. ->scalarNode('version')
  80. ->info('The version of the API.')
  81. ->cannotBeEmpty()
  82. ->defaultValue('0.0.0')
  83. ->end()
  84. ->booleanNode('show_webby')->defaultTrue()->info('If true, show Webby on the documentation page')->end()
  85. ->booleanNode('metadata_backward_compatibility_layer')->defaultTrue()->info('If true, declared services are using legacy interfaces for the following services: "api_platform.iri_converter", "api_platform.openapi.factory", "api_platform.identifiers_extractor".')->end()
  86. ->scalarNode('default_operation_path_resolver')
  87. ->defaultValue('api_platform.operation_path_resolver.underscore')
  88. ->setDeprecated(...$this->buildDeprecationArgs('2.1', 'The use of the `default_operation_path_resolver` has been deprecated in 2.1 and will be removed in 3.0. Use `path_segment_name_generator` instead.'))
  89. ->info('Specify the default operation path resolver to use for generating resources operations path.')
  90. ->end()
  91. ->scalarNode('name_converter')->defaultNull()->info('Specify a name converter to use.')->end()
  92. ->scalarNode('asset_package')->defaultNull()->info('Specify an asset package name to use.')->end()
  93. ->scalarNode('path_segment_name_generator')->defaultValue('api_platform.path_segment_name_generator.underscore')->info('Specify a path name generator to use.')->end()
  94. ->booleanNode('allow_plain_identifiers')
  95. ->defaultFalse()
  96. ->info('Allow plain identifiers, for example "id" instead of "@id" when denormalizing a relation.')
  97. ->setDeprecated(...$this->buildDeprecationArgs('2.7', 'The use of `allow_plain_identifiers` has been deprecated in 2.7 and will be removed in 3.0.'))
  98. ->end()
  99. ->arrayNode('validator')
  100. ->addDefaultsIfNotSet()
  101. ->children()
  102. ->variableNode('serialize_payload_fields')->defaultValue([])->info('Set to null to serialize all payload fields when a validation error is thrown, or set the fields you want to include explicitly.')->end()
  103. ->booleanNode('query_parameter_validation')->defaultValue(true)->end()
  104. ->end()
  105. ->end()
  106. ->arrayNode('eager_loading')
  107. ->canBeDisabled()
  108. ->addDefaultsIfNotSet()
  109. ->children()
  110. ->booleanNode('fetch_partial')->defaultFalse()->info('Fetch only partial data according to serialization groups. If enabled, Doctrine ORM entities will not work as expected if any of the other fields are used.')->end()
  111. ->integerNode('max_joins')->defaultValue(30)->info('Max number of joined relations before EagerLoading throws a RuntimeException')->end()
  112. ->booleanNode('force_eager')->defaultTrue()->info('Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode.')->end()
  113. ->end()
  114. ->end()
  115. ->booleanNode('enable_fos_user')
  116. ->defaultValue(class_exists(FOSUserBundle::class))
  117. ->setDeprecated(...$this->buildDeprecationArgs('2.5', 'FOSUserBundle is not actively maintained anymore. Enabling the FOSUserBundle integration has been deprecated in 2.5 and will be removed in 3.0.'))
  118. ->info('Enable the FOSUserBundle integration.')
  119. ->end()
  120. ->booleanNode('enable_nelmio_api_doc')
  121. ->defaultFalse()
  122. ->setDeprecated(...$this->buildDeprecationArgs('2.2', 'Enabling the NelmioApiDocBundle integration has been deprecated in 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.'))
  123. ->info('Enable the NelmioApiDocBundle integration.')
  124. ->end()
  125. ->booleanNode('enable_swagger')->defaultTrue()->info('Enable the Swagger documentation and export.')->end()
  126. ->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger UI')->end()
  127. ->booleanNode('enable_re_doc')->defaultValue(class_exists(TwigBundle::class))->info('Enable ReDoc')->end()
  128. ->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end()
  129. ->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end()
  130. ->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end()
  131. ->arrayNode('collection')
  132. ->addDefaultsIfNotSet()
  133. ->children()
  134. ->scalarNode('exists_parameter_name')->defaultValue('exists')->cannotBeEmpty()->info('The name of the query parameter to filter on nullable field values.')->end()
  135. ->scalarNode('order')->defaultValue('ASC')->info('The default order of results.')->end() // Default ORDER is required for postgresql and mysql >= 5.7 when using LIMIT/OFFSET request
  136. ->scalarNode('order_parameter_name')->defaultValue('order')->cannotBeEmpty()->info('The name of the query parameter to order results.')->end()
  137. ->enumNode('order_nulls_comparison')->defaultNull()->values(array_merge(array_keys(OrderFilterInterface::NULLS_DIRECTION_MAP), [null]))->info('The nulls comparison strategy.')->end()
  138. ->arrayNode('pagination')
  139. ->canBeDisabled()
  140. ->addDefaultsIfNotSet()
  141. ->children()
  142. ->booleanNode('enabled')
  143. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `collection.pagination.enabled` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_enabled` instead.'))
  144. ->defaultTrue()
  145. ->info('To enable or disable pagination for all resource collections by default.')
  146. ->end()
  147. ->booleanNode('partial')
  148. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `collection.pagination.partial` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_partial` instead.'))
  149. ->defaultFalse()
  150. ->info('To enable or disable partial pagination for all resource collections by default when pagination is enabled.')
  151. ->end()
  152. ->booleanNode('client_enabled')
  153. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `collection.pagination.client_enabled` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_client_enabled` instead.'))
  154. ->defaultFalse()
  155. ->info('To allow the client to enable or disable the pagination.')
  156. ->end()
  157. ->booleanNode('client_items_per_page')
  158. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `collection.pagination.client_items_per_page` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_client_items_per_page` instead.'))
  159. ->defaultFalse()
  160. ->info('To allow the client to set the number of items per page.')
  161. ->end()
  162. ->booleanNode('client_partial')
  163. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `collection.pagination.client_partial` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_client_partial` instead.'))
  164. ->defaultFalse()
  165. ->info('To allow the client to enable or disable partial pagination.')
  166. ->end()
  167. ->integerNode('items_per_page')
  168. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `collection.pagination.items_per_page` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_items_per_page` instead.'))
  169. ->defaultValue(30)
  170. ->info('The default number of items per page.')
  171. ->end()
  172. ->integerNode('maximum_items_per_page')
  173. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `collection.pagination.maximum_items_per_page` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_maximum_items_per_page` instead.'))
  174. ->defaultNull()
  175. ->info('The maximum number of items per page.')
  176. ->end()
  177. ->scalarNode('page_parameter_name')->defaultValue('page')->cannotBeEmpty()->info('The default name of the parameter handling the page number.')->end()
  178. ->scalarNode('enabled_parameter_name')->defaultValue('pagination')->cannotBeEmpty()->info('The name of the query parameter to enable or disable pagination.')->end()
  179. ->scalarNode('items_per_page_parameter_name')->defaultValue('itemsPerPage')->cannotBeEmpty()->info('The name of the query parameter to set the number of items per page.')->end()
  180. ->scalarNode('partial_parameter_name')->defaultValue('partial')->cannotBeEmpty()->info('The name of the query parameter to enable or disable partial pagination.')->end()
  181. ->end()
  182. ->end()
  183. ->end()
  184. ->end()
  185. ->arrayNode('mapping')
  186. ->addDefaultsIfNotSet()
  187. ->children()
  188. ->arrayNode('paths')
  189. ->prototype('scalar')->end()
  190. ->end()
  191. ->end()
  192. ->end()
  193. ->arrayNode('resource_class_directories')
  194. ->prototype('scalar')->end()
  195. ->end()
  196. ->end();
  197. $this->addDoctrineOrmSection($rootNode);
  198. $this->addDoctrineMongoDbOdmSection($rootNode);
  199. $this->addOAuthSection($rootNode);
  200. $this->addGraphQlSection($rootNode);
  201. $this->addSwaggerSection($rootNode);
  202. $this->addHttpCacheSection($rootNode);
  203. $this->addMercureSection($rootNode);
  204. $this->addMessengerSection($rootNode);
  205. $this->addElasticsearchSection($rootNode);
  206. $this->addOpenApiSection($rootNode);
  207. $this->addMakerSection($rootNode);
  208. $this->addExceptionToStatusSection($rootNode);
  209. $this->addFormatSection($rootNode, 'formats', [
  210. 'jsonld' => ['mime_types' => ['application/ld+json']],
  211. 'json' => ['mime_types' => ['application/json']], // Swagger support
  212. 'html' => ['mime_types' => ['text/html']], // Swagger UI support
  213. ]);
  214. $this->addFormatSection($rootNode, 'patch_formats', []);
  215. $this->addFormatSection($rootNode, 'error_formats', [
  216. 'jsonproblem' => ['mime_types' => ['application/problem+json']],
  217. 'jsonld' => ['mime_types' => ['application/ld+json']],
  218. ]);
  219. $this->addDefaultsSection($rootNode);
  220. return $treeBuilder;
  221. }
  222. private function addDoctrineOrmSection(ArrayNodeDefinition $rootNode): void
  223. {
  224. $rootNode
  225. ->children()
  226. ->arrayNode('doctrine')
  227. ->{class_exists(DoctrineBundle::class) && interface_exists(EntityManagerInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  228. ->end()
  229. ->end();
  230. }
  231. private function addDoctrineMongoDbOdmSection(ArrayNodeDefinition $rootNode): void
  232. {
  233. $rootNode
  234. ->children()
  235. ->arrayNode('doctrine_mongodb_odm')
  236. ->{class_exists(DoctrineMongoDBBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  237. ->end()
  238. ->end();
  239. }
  240. private function addOAuthSection(ArrayNodeDefinition $rootNode): void
  241. {
  242. $rootNode
  243. ->children()
  244. ->arrayNode('oauth')
  245. ->canBeEnabled()
  246. ->addDefaultsIfNotSet()
  247. ->children()
  248. ->scalarNode('clientId')->defaultValue('')->info('The oauth client id.')->end()
  249. ->scalarNode('clientSecret')
  250. ->defaultValue('')
  251. ->info('The OAuth client secret. Never use this parameter in your production environment. It exposes crucial security information. This feature is intended for dev/test environments only. Enable "oauth.pkce" instead')
  252. ->end()
  253. ->booleanNode('pkce')->defaultFalse()->info('Enable the oauth PKCE.')->end()
  254. ->scalarNode('type')->defaultValue('oauth2')->info('The oauth type.')->end()
  255. ->scalarNode('flow')->defaultValue('application')->info('The oauth flow grant type.')->end()
  256. ->scalarNode('tokenUrl')->defaultValue('')->info('The oauth token url.')->end()
  257. ->scalarNode('authorizationUrl')->defaultValue('')->info('The oauth authentication url.')->end()
  258. ->scalarNode('refreshUrl')->defaultValue('')->info('The oauth refresh url.')->end()
  259. ->arrayNode('scopes')
  260. ->prototype('scalar')->end()
  261. ->end()
  262. ->end()
  263. ->end()
  264. ->end();
  265. }
  266. private function addGraphQlSection(ArrayNodeDefinition $rootNode): void
  267. {
  268. $rootNode
  269. ->children()
  270. ->arrayNode('graphql')
  271. ->{class_exists(GraphQL::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  272. ->addDefaultsIfNotSet()
  273. ->children()
  274. ->scalarNode('default_ide')->defaultValue('graphiql')->end()
  275. ->arrayNode('graphiql')
  276. ->{class_exists(GraphQL::class) && class_exists(TwigBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  277. ->end()
  278. ->arrayNode('graphql_playground')
  279. ->{class_exists(GraphQL::class) && class_exists(TwigBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  280. ->end()
  281. ->scalarNode('nesting_separator')->defaultValue('_')->info('The separator to use to filter nested fields.')->end()
  282. ->arrayNode('collection')
  283. ->addDefaultsIfNotSet()
  284. ->children()
  285. ->arrayNode('pagination')
  286. ->canBeDisabled()
  287. ->end()
  288. ->end()
  289. ->end()
  290. ->end()
  291. ->end()
  292. ->end();
  293. }
  294. private function addSwaggerSection(ArrayNodeDefinition $rootNode): void
  295. {
  296. $defaultVersions = [2, 3];
  297. $rootNode
  298. ->children()
  299. ->arrayNode('swagger')
  300. ->addDefaultsIfNotSet()
  301. ->children()
  302. ->arrayNode('versions')
  303. ->info('The active versions of Open API to be exported or used in the swagger_ui. The first value is the default.')
  304. ->defaultValue($defaultVersions)
  305. ->beforeNormalization()
  306. ->always(static function ($v) {
  307. if (!\is_array($v)) {
  308. $v = [$v];
  309. }
  310. foreach ($v as &$version) {
  311. $version = (int) $version;
  312. }
  313. return $v;
  314. })
  315. ->end()
  316. ->validate()
  317. ->ifTrue(static function ($v) use ($defaultVersions) {
  318. return $v !== array_intersect($v, $defaultVersions);
  319. })
  320. ->thenInvalid(sprintf('Only the versions %s are supported. Got %s.', implode(' and ', $defaultVersions), '%s'))
  321. ->end()
  322. ->prototype('scalar')->end()
  323. ->end()
  324. ->arrayNode('api_keys')
  325. ->prototype('array')
  326. ->children()
  327. ->scalarNode('name')
  328. ->info('The name of the header or query parameter containing the api key.')
  329. ->end()
  330. ->enumNode('type')
  331. ->info('Whether the api key should be a query parameter or a header.')
  332. ->values(['query', 'header'])
  333. ->end()
  334. ->end()
  335. ->end()
  336. ->end()
  337. ->variableNode('swagger_ui_extra_configuration')
  338. ->defaultValue([])
  339. ->validate()
  340. ->ifTrue(static function ($v) { return false === \is_array($v); })
  341. ->thenInvalid('The swagger_ui_extra_configuration parameter must be an array.')
  342. ->end()
  343. ->info('To pass extra configuration to Swagger UI, like docExpansion or filter.')
  344. ->end()
  345. ->end()
  346. ->end()
  347. ->end();
  348. }
  349. private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void
  350. {
  351. $rootNode
  352. ->children()
  353. ->arrayNode('http_cache')
  354. ->addDefaultsIfNotSet()
  355. ->children()
  356. ->booleanNode('etag')
  357. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `http_cache.etag` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.etag` instead.'))
  358. ->defaultTrue()
  359. ->info('Automatically generate etags for API responses.')
  360. ->end()
  361. ->integerNode('max_age')
  362. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `http_cache.max_age` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.max_age` instead.'))
  363. ->defaultNull()
  364. ->info('Default value for the response max age.')
  365. ->end()
  366. ->integerNode('shared_max_age')
  367. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `http_cache.shared_max_age` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.shared_max_age` instead.'))
  368. ->defaultNull()
  369. ->info('Default value for the response shared (proxy) max age.')
  370. ->end()
  371. ->arrayNode('vary')
  372. ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'The use of the `http_cache.vary` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.vary` instead.'))
  373. ->defaultValue(['Accept'])
  374. ->prototype('scalar')->end()
  375. ->info('Default values of the "Vary" HTTP header.')
  376. ->end()
  377. ->booleanNode('public')->defaultNull()->info('To make all responses public by default.')->end()
  378. ->arrayNode('invalidation')
  379. ->info('Enable the tags-based cache invalidation system.')
  380. ->canBeEnabled()
  381. ->children()
  382. ->arrayNode('varnish_urls')
  383. ->defaultValue([])
  384. ->prototype('scalar')->end()
  385. ->info('URLs of the Varnish servers to purge using cache tags when a resource is updated.')
  386. ->end()
  387. ->integerNode('max_header_length')
  388. ->defaultValue(7500)
  389. ->info('Max header length supported by the server')
  390. ->end()
  391. ->variableNode('request_options')
  392. ->defaultValue([])
  393. ->validate()
  394. ->ifTrue(static function ($v) { return false === \is_array($v); })
  395. ->thenInvalid('The request_options parameter must be an array.')
  396. ->end()
  397. ->info('To pass options to the client charged with the request.')
  398. ->end()
  399. ->scalarNode('purger')
  400. ->defaultValue('api_platform.http_cache.purger.varnish')
  401. ->info('Specify a varnish purger to use (available values: "api_platform.http_cache.purger.varnish.ban" or "api_platform.http_cache.purger.varnish.xkey").')
  402. ->end()
  403. ->arrayNode('xkey')
  404. ->addDefaultsIfNotSet()
  405. ->children()
  406. ->scalarNode('glue')
  407. ->defaultValue(' ')
  408. ->info('xkey glue between keys')
  409. ->end()
  410. ->end()
  411. ->end()
  412. ->end()
  413. ->end()
  414. ->end()
  415. ->end()
  416. ->end();
  417. }
  418. private function addMercureSection(ArrayNodeDefinition $rootNode): void
  419. {
  420. $rootNode
  421. ->children()
  422. ->arrayNode('mercure')
  423. ->{class_exists(MercureBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  424. ->children()
  425. ->scalarNode('hub_url')
  426. ->defaultNull()
  427. ->info('The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle\'s default hub.')
  428. ->end()
  429. ->end()
  430. ->end()
  431. ->end();
  432. }
  433. private function addMessengerSection(ArrayNodeDefinition $rootNode): void
  434. {
  435. $rootNode
  436. ->children()
  437. ->arrayNode('messenger')
  438. ->{!class_exists(FullStack::class) && interface_exists(MessageBusInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  439. ->end()
  440. ->end();
  441. }
  442. private function addElasticsearchSection(ArrayNodeDefinition $rootNode): void
  443. {
  444. $rootNode
  445. ->children()
  446. ->arrayNode('elasticsearch')
  447. ->canBeEnabled()
  448. ->addDefaultsIfNotSet()
  449. ->children()
  450. ->booleanNode('enabled')
  451. ->defaultFalse()
  452. ->validate()
  453. ->ifTrue()
  454. ->then(static function (bool $v): bool {
  455. if (!class_exists(ElasticsearchClient::class)) {
  456. throw new InvalidConfigurationException('The elasticsearch/elasticsearch package is required for Elasticsearch support.');
  457. }
  458. return $v;
  459. })
  460. ->end()
  461. ->end()
  462. ->arrayNode('hosts')
  463. ->beforeNormalization()->castToArray()->end()
  464. ->defaultValue([])
  465. ->prototype('scalar')->end()
  466. ->end()
  467. ->arrayNode('mapping')
  468. ->normalizeKeys(false)
  469. ->useAttributeAsKey('resource_class')
  470. ->prototype('array')
  471. ->children()
  472. ->scalarNode('index')->defaultNull()->end()
  473. ->scalarNode('type')->defaultValue(DocumentMetadata::DEFAULT_TYPE)->end()
  474. ->end()
  475. ->end()
  476. ->end()
  477. ->end()
  478. ->end()
  479. ->end();
  480. }
  481. private function addOpenApiSection(ArrayNodeDefinition $rootNode): void
  482. {
  483. $rootNode
  484. ->children()
  485. ->arrayNode('openapi')
  486. ->addDefaultsIfNotSet()
  487. ->children()
  488. ->arrayNode('contact')
  489. ->addDefaultsIfNotSet()
  490. ->children()
  491. ->scalarNode('name')->defaultNull()->info('The identifying name of the contact person/organization.')->end()
  492. ->scalarNode('url')->defaultNull()->info('The URL pointing to the contact information. MUST be in the format of a URL.')->end()
  493. ->scalarNode('email')->defaultNull()->info('The email address of the contact person/organization. MUST be in the format of an email address.')->end()
  494. ->end()
  495. ->end()
  496. ->booleanNode('backward_compatibility_layer')->defaultTrue()->info('Enable this to decorate the "api_platform.swagger.normalizer.documentation" instead of decorating the OpenAPI factory.')->end()
  497. ->scalarNode('termsOfService')->defaultNull()->info('A URL to the Terms of Service for the API. MUST be in the format of a URL.')->end()
  498. ->arrayNode('license')
  499. ->addDefaultsIfNotSet()
  500. ->children()
  501. ->scalarNode('name')->defaultNull()->info('The license name used for the API.')->end()
  502. ->scalarNode('url')->defaultNull()->info('URL to the license used for the API. MUST be in the format of a URL.')->end()
  503. ->end()
  504. ->end()
  505. ->variableNode('swagger_ui_extra_configuration')
  506. ->defaultValue([])
  507. ->validate()
  508. ->ifTrue(static function ($v) { return false === \is_array($v); })
  509. ->thenInvalid('The swagger_ui_extra_configuration parameter must be an array.')
  510. ->end()
  511. ->info('To pass extra configuration to Swagger UI, like docExpansion or filter.')
  512. ->end()
  513. ->end()
  514. ->end()
  515. ->end();
  516. }
  517. /**
  518. * @throws InvalidConfigurationException
  519. */
  520. private function addExceptionToStatusSection(ArrayNodeDefinition $rootNode): void
  521. {
  522. $rootNode
  523. ->children()
  524. ->arrayNode('exception_to_status')
  525. ->defaultValue([
  526. SerializerExceptionInterface::class => Response::HTTP_BAD_REQUEST,
  527. InvalidArgumentException::class => Response::HTTP_BAD_REQUEST,
  528. FilterValidationException::class => Response::HTTP_BAD_REQUEST,
  529. OptimisticLockException::class => Response::HTTP_CONFLICT,
  530. ])
  531. ->info('The list of exceptions mapped to their HTTP status code.')
  532. ->normalizeKeys(false)
  533. ->useAttributeAsKey('exception_class')
  534. ->beforeNormalization()
  535. ->ifArray()
  536. ->then(static function (array $exceptionToStatus) {
  537. foreach ($exceptionToStatus as &$httpStatusCode) {
  538. if (\is_int($httpStatusCode)) {
  539. continue;
  540. }
  541. if (\defined($httpStatusCodeConstant = sprintf('%s::%s', Response::class, $httpStatusCode))) {
  542. @trigger_error(sprintf('Using a string "%s" as a constant of the "%s" class is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3. Use the Symfony\'s custom YAML extension for PHP constants instead (i.e. "!php/const %s").', $httpStatusCode, Response::class, $httpStatusCodeConstant), \E_USER_DEPRECATED);
  543. $httpStatusCode = \constant($httpStatusCodeConstant);
  544. }
  545. }
  546. return $exceptionToStatus;
  547. })
  548. ->end()
  549. ->prototype('integer')->end()
  550. ->validate()
  551. ->ifArray()
  552. ->then(static function (array $exceptionToStatus) {
  553. foreach ($exceptionToStatus as $httpStatusCode) {
  554. if ($httpStatusCode < 100 || $httpStatusCode >= 600) {
  555. throw new InvalidConfigurationException(sprintf('The HTTP status code "%s" is not valid.', $httpStatusCode));
  556. }
  557. }
  558. return $exceptionToStatus;
  559. })
  560. ->end()
  561. ->end()
  562. ->end();
  563. }
  564. private function addFormatSection(ArrayNodeDefinition $rootNode, string $key, array $defaultValue): void
  565. {
  566. $rootNode
  567. ->children()
  568. ->arrayNode($key)
  569. ->defaultValue($defaultValue)
  570. ->info('The list of enabled formats. The first one will be the default.')
  571. ->normalizeKeys(false)
  572. ->useAttributeAsKey('format')
  573. ->beforeNormalization()
  574. ->ifArray()
  575. ->then(static function ($v) {
  576. foreach ($v as $format => $value) {
  577. if (isset($value['mime_types'])) {
  578. continue;
  579. }
  580. $v[$format] = ['mime_types' => $value];
  581. }
  582. return $v;
  583. })
  584. ->end()
  585. ->prototype('array')
  586. ->children()
  587. ->arrayNode('mime_types')->prototype('scalar')->end()->end()
  588. ->end()
  589. ->end()
  590. ->end()
  591. ->end();
  592. }
  593. private function addDefaultsSection(ArrayNodeDefinition $rootNode): void
  594. {
  595. $nameConverter = new CamelCaseToSnakeCaseNameConverter();
  596. $defaultsNode = $rootNode->children()->arrayNode('defaults');
  597. $defaultsNode
  598. ->ignoreExtraKeys(false)
  599. ->beforeNormalization()
  600. ->always(static function (array $defaults) use ($nameConverter) {
  601. $normalizedDefaults = [];
  602. foreach ($defaults as $option => $value) {
  603. $option = $nameConverter->normalize($option);
  604. $normalizedDefaults[$option] = $value;
  605. }
  606. return $normalizedDefaults;
  607. });
  608. // TODO: test defaults with things that are no in the constructor
  609. if (class_exists(ApiResource::class)) {
  610. $reflection = new \ReflectionClass(ApiResource::class);
  611. foreach ($reflection->getConstructor()->getParameters() as $parameter) {
  612. $defaultsNode->children()->variableNode($nameConverter->normalize($parameter->getName()));
  613. }
  614. return;
  615. }
  616. [$publicProperties, $configurableAttributes] = LegacyApiResource::getConfigMetadata();
  617. foreach (array_merge($publicProperties, $configurableAttributpies) as $attribute => $_) {
  618. $snakeCased = $nameConverter->normalize($attribute);
  619. $defaultsNode->children()->variableNode($snakeCased);
  620. }
  621. }
  622. private function addMakerSection(ArrayNodeDefinition $rootNode): void
  623. {
  624. $rootNode
  625. ->children()
  626. ->arrayNode('maker')
  627. ->{class_exists(MakerBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
  628. ->end()
  629. ->end();
  630. }
  631. private function buildDeprecationArgs(string $version, string $message): array
  632. {
  633. return method_exists(BaseNode::class, 'getDeprecation')
  634. ? ['api-platform/core', $version, $message]
  635. : [$message];
  636. }
  637. }
  638. class_alias(Configuration::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Configuration::class);