• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 15966359730

30 Jun 2025 07:13AM UTC coverage: 22.064% (-0.02%) from 22.082%
15966359730

push

github

soyuka
ci: distribution update on new tag only

11518 of 52203 relevant lines covered (22.06%)

22.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.4
/src/Symfony/Bundle/DependencyInjection/Configuration.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Symfony\Bundle\DependencyInjection;
15

16
use ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface;
17
use ApiPlatform\Metadata\ApiResource;
18
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
19
use ApiPlatform\Metadata\Post;
20
use ApiPlatform\Metadata\Put;
21
use ApiPlatform\Symfony\Controller\MainController;
22
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
23
use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle;
24
use Doctrine\ORM\EntityManagerInterface;
25
use Doctrine\ORM\OptimisticLockException;
26
use GraphQL\GraphQL;
27
use Symfony\Bundle\FullStack;
28
use Symfony\Bundle\MakerBundle\MakerBundle;
29
use Symfony\Bundle\MercureBundle\MercureBundle;
30
use Symfony\Bundle\TwigBundle\TwigBundle;
31
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
32
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
33
use Symfony\Component\Config\Definition\ConfigurationInterface;
34
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
35
use Symfony\Component\HttpFoundation\Response;
36
use Symfony\Component\Messenger\MessageBusInterface;
37
use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerExceptionInterface;
38
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
39
use Symfony\Component\Yaml\Yaml;
40

41
/**
42
 * The configuration of the bundle.
43
 *
44
 * @author Kévin Dunglas <dunglas@gmail.com>
45
 * @author Baptiste Meyer <baptiste.meyer@gmail.com>
46
 */
47
final class Configuration implements ConfigurationInterface
48
{
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function getConfigTreeBuilder(): TreeBuilder
53
    {
54
        $treeBuilder = new TreeBuilder('api_platform');
42✔
55
        $rootNode = $treeBuilder->getRootNode();
42✔
56

57
        $rootNode
42✔
58
            ->beforeNormalization()
42✔
59
                ->ifTrue(static function ($v) {
42✔
60
                    return false === ($v['enable_swagger'] ?? null);
42✔
61
                })
42✔
62
                ->then(static function ($v) {
42✔
63
                    $v['swagger']['versions'] = [];
2✔
64

65
                    return $v;
2✔
66
                })
42✔
67
            ->end()
42✔
68
            ->children()
42✔
69
                ->scalarNode('title')
42✔
70
                    ->info('The title of the API.')
42✔
71
                    ->cannotBeEmpty()
42✔
72
                    ->defaultValue('')
42✔
73
                ->end()
42✔
74
                ->scalarNode('description')
42✔
75
                    ->info('The description of the API.')
42✔
76
                    ->cannotBeEmpty()
42✔
77
                    ->defaultValue('')
42✔
78
                ->end()
42✔
79
                ->scalarNode('version')
42✔
80
                    ->info('The version of the API.')
42✔
81
                    ->cannotBeEmpty()
42✔
82
                    ->defaultValue('0.0.0')
42✔
83
                ->end()
42✔
84
                ->booleanNode('show_webby')->defaultTrue()->info('If true, show Webby on the documentation page')->end()
42✔
85
                ->booleanNode('use_symfony_listeners')->defaultFalse()->info(sprintf('Uses Symfony event listeners instead of the %s.', MainController::class))->end()
42✔
86
                ->scalarNode('name_converter')->defaultNull()->info('Specify a name converter to use.')->end()
42✔
87
                ->scalarNode('asset_package')->defaultNull()->info('Specify an asset package name to use.')->end()
42✔
88
                ->scalarNode('path_segment_name_generator')->defaultValue('api_platform.metadata.path_segment_name_generator.underscore')->info('Specify a path name generator to use.')->end()
42✔
89
                ->scalarNode('inflector')->defaultValue('api_platform.metadata.inflector')->info('Specify an inflector to use.')->end()
42✔
90
                ->arrayNode('validator')
42✔
91
                    ->addDefaultsIfNotSet()
42✔
92
                    ->children()
42✔
93
                        ->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()
42✔
94
                        ->booleanNode('query_parameter_validation')
42✔
95
                            ->defaultValue(true)
42✔
96
                            ->setDeprecated('api-platform/symfony', '4.2', 'Will be removed in API Platform 5.0.')
42✔
97
                        ->end()
42✔
98
                    ->end()
42✔
99
                ->end()
42✔
100
                ->arrayNode('eager_loading')
42✔
101
                    ->canBeDisabled()
42✔
102
                    ->addDefaultsIfNotSet()
42✔
103
                    ->children()
42✔
104
                        ->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()
42✔
105
                        ->integerNode('max_joins')->defaultValue(30)->info('Max number of joined relations before EagerLoading throws a RuntimeException')->end()
42✔
106
                        ->booleanNode('force_eager')->defaultTrue()->info('Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode.')->end()
42✔
107
                    ->end()
42✔
108
                ->end()
42✔
109
                ->booleanNode('handle_symfony_errors')->defaultFalse()->info('Allows to handle symfony exceptions.')->end()
42✔
110
                ->booleanNode('enable_swagger')->defaultTrue()->info('Enable the Swagger documentation and export.')->end()
42✔
111
                ->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger UI')->end()
42✔
112
                ->booleanNode('enable_re_doc')->defaultValue(class_exists(TwigBundle::class))->info('Enable ReDoc')->end()
42✔
113
                ->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end()
42✔
114
                ->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end()
42✔
115
                ->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end()
42✔
116
                ->booleanNode('enable_link_security')->defaultFalse()->info('Enable security for Links (sub resources)')->end()
42✔
117
                ->arrayNode('collection')
42✔
118
                    ->addDefaultsIfNotSet()
42✔
119
                    ->children()
42✔
120
                        ->scalarNode('exists_parameter_name')->defaultValue('exists')->cannotBeEmpty()->info('The name of the query parameter to filter on nullable field values.')->end()
42✔
121
                        ->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
42✔
122
                        ->scalarNode('order_parameter_name')->defaultValue('order')->cannotBeEmpty()->info('The name of the query parameter to order results.')->end()
42✔
123
                        ->enumNode('order_nulls_comparison')->defaultNull()->values(interface_exists(OrderFilterInterface::class) ? array_merge(array_keys(OrderFilterInterface::NULLS_DIRECTION_MAP), [null]) : [null])->info('The nulls comparison strategy.')->end()
42✔
124
                        ->arrayNode('pagination')
42✔
125
                            ->canBeDisabled()
42✔
126
                            ->addDefaultsIfNotSet()
42✔
127
                            ->children()
42✔
128
                                ->scalarNode('page_parameter_name')->defaultValue('page')->cannotBeEmpty()->info('The default name of the parameter handling the page number.')->end()
42✔
129
                                ->scalarNode('enabled_parameter_name')->defaultValue('pagination')->cannotBeEmpty()->info('The name of the query parameter to enable or disable pagination.')->end()
42✔
130
                                ->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()
42✔
131
                                ->scalarNode('partial_parameter_name')->defaultValue('partial')->cannotBeEmpty()->info('The name of the query parameter to enable or disable partial pagination.')->end()
42✔
132
                            ->end()
42✔
133
                        ->end()
42✔
134
                    ->end()
42✔
135
                ->end()
42✔
136
                ->arrayNode('mapping')
42✔
137
                    ->addDefaultsIfNotSet()
42✔
138
                    ->children()
42✔
139
                        ->arrayNode('imports')
42✔
140
                            ->prototype('scalar')->end()
42✔
141
                        ->end()
42✔
142
                        ->arrayNode('paths')
42✔
143
                            ->prototype('scalar')->end()
42✔
144
                        ->end()
42✔
145
                    ->end()
42✔
146
                ->end()
42✔
147
                ->arrayNode('resource_class_directories')
42✔
148
                    ->prototype('scalar')->end()
42✔
149
                    ->setDeprecated('api-platform/symfony', '4.1', 'The "resource_class_directories" configuration is deprecated, classes using #[ApiResource] attribute are autoconfigured by the dependency injection container.')
42✔
150
                ->end()
42✔
151
                ->arrayNode('serializer')
42✔
152
                    ->addDefaultsIfNotSet()
42✔
153
                    ->children()
42✔
154
                        ->booleanNode('hydra_prefix')->defaultFalse()->info('Use the "hydra:" prefix.')->end()
42✔
155
                    ->end()
42✔
156
                ->end()
42✔
157
            ->end();
42✔
158

159
        $this->addDoctrineOrmSection($rootNode);
42✔
160
        $this->addDoctrineMongoDbOdmSection($rootNode);
42✔
161
        $this->addOAuthSection($rootNode);
42✔
162
        $this->addGraphQlSection($rootNode);
42✔
163
        $this->addSwaggerSection($rootNode);
42✔
164
        $this->addHttpCacheSection($rootNode);
42✔
165
        $this->addMercureSection($rootNode);
42✔
166
        $this->addMessengerSection($rootNode);
42✔
167
        $this->addElasticsearchSection($rootNode);
42✔
168
        $this->addOpenApiSection($rootNode);
42✔
169
        $this->addMakerSection($rootNode);
42✔
170

171
        $this->addExceptionToStatusSection($rootNode);
42✔
172

173
        $this->addFormatSection($rootNode, 'formats', [
42✔
174
            'jsonld' => ['mime_types' => ['application/ld+json']],
42✔
175
        ]);
42✔
176
        $this->addFormatSection($rootNode, 'patch_formats', [
42✔
177
            'json' => ['mime_types' => ['application/merge-patch+json']],
42✔
178
        ]);
42✔
179

180
        $defaultDocFormats = [
42✔
181
            'jsonld' => ['mime_types' => ['application/ld+json']],
42✔
182
            'jsonopenapi' => ['mime_types' => ['application/vnd.openapi+json']],
42✔
183
            'html' => ['mime_types' => ['text/html']],
42✔
184
        ];
42✔
185

186
        if (class_exists(Yaml::class)) {
42✔
187
            $defaultDocFormats['yamlopenapi'] = ['mime_types' => ['application/vnd.openapi+yaml']];
42✔
188
        }
189

190
        $this->addFormatSection($rootNode, 'docs_formats', $defaultDocFormats);
42✔
191

192
        $this->addFormatSection($rootNode, 'error_formats', [
42✔
193
            'jsonld' => ['mime_types' => ['application/ld+json']],
42✔
194
            'jsonproblem' => ['mime_types' => ['application/problem+json']],
42✔
195
            'json' => ['mime_types' => ['application/problem+json', 'application/json']],
42✔
196
        ]);
42✔
197
        $rootNode
42✔
198
            ->children()
42✔
199
                ->arrayNode('jsonschema_formats')
42✔
200
                    ->scalarPrototype()->end()
42✔
201
                    ->defaultValue([])
42✔
202
                    ->info('The JSON formats to compute the JSON Schemas for.')
42✔
203
                ->end()
42✔
204
            ->end();
42✔
205

206
        $this->addDefaultsSection($rootNode);
42✔
207

208
        return $treeBuilder;
42✔
209
    }
210

211
    private function addDoctrineOrmSection(ArrayNodeDefinition $rootNode): void
212
    {
213
        $rootNode
42✔
214
            ->children()
42✔
215
                ->arrayNode('doctrine')
42✔
216
                    ->{class_exists(DoctrineBundle::class) && interface_exists(EntityManagerInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}()
42✔
217
                ->end()
42✔
218
            ->end();
42✔
219
    }
220

221
    private function addDoctrineMongoDbOdmSection(ArrayNodeDefinition $rootNode): void
222
    {
223
        $rootNode
42✔
224
            ->children()
42✔
225
                ->arrayNode('doctrine_mongodb_odm')
42✔
226
                    ->{class_exists(DoctrineMongoDBBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
42✔
227
                ->end()
42✔
228
            ->end();
42✔
229
    }
230

231
    private function addOAuthSection(ArrayNodeDefinition $rootNode): void
232
    {
233
        $rootNode
42✔
234
            ->children()
42✔
235
                ->arrayNode('oauth')
42✔
236
                    ->canBeEnabled()
42✔
237
                    ->addDefaultsIfNotSet()
42✔
238
                    ->children()
42✔
239
                        ->scalarNode('clientId')->defaultValue('')->info('The oauth client id.')->end()
42✔
240
                        ->scalarNode('clientSecret')
42✔
241
                            ->defaultValue('')
42✔
242
                            ->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')
42✔
243
                        ->end()
42✔
244
                        ->booleanNode('pkce')->defaultFalse()->info('Enable the oauth PKCE.')->end()
42✔
245
                        ->scalarNode('type')->defaultValue('oauth2')->info('The oauth type.')->end()
42✔
246
                        ->scalarNode('flow')->defaultValue('application')->info('The oauth flow grant type.')->end()
42✔
247
                        ->scalarNode('tokenUrl')->defaultValue('')->info('The oauth token url.')->end()
42✔
248
                        ->scalarNode('authorizationUrl')->defaultValue('')->info('The oauth authentication url.')->end()
42✔
249
                        ->scalarNode('refreshUrl')->defaultValue('')->info('The oauth refresh url.')->end()
42✔
250
                        ->arrayNode('scopes')
42✔
251
                            ->prototype('scalar')->end()
42✔
252
                        ->end()
42✔
253
                    ->end()
42✔
254
                ->end()
42✔
255
            ->end();
42✔
256
    }
257

258
    private function addGraphQlSection(ArrayNodeDefinition $rootNode): void
259
    {
260
        $rootNode
42✔
261
            ->children()
42✔
262
                ->arrayNode('graphql')
42✔
263
                    ->{class_exists(GraphQL::class) ? 'canBeDisabled' : 'canBeEnabled'}()
42✔
264
                    ->addDefaultsIfNotSet()
42✔
265
                    ->children()
42✔
266
                        ->scalarNode('default_ide')->defaultValue('graphiql')->end()
42✔
267
                        ->arrayNode('graphiql')
42✔
268
                            ->{class_exists(GraphQL::class) && class_exists(TwigBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
42✔
269
                        ->end()
42✔
270
                        ->arrayNode('introspection')
42✔
271
                            ->canBeDisabled()
42✔
272
                        ->end()
42✔
273
                        ->integerNode('max_query_depth')->defaultValue(20)
42✔
274
                        ->end()
42✔
275
                        ->integerNode('max_query_complexity')->defaultValue(500)
42✔
276
                        ->end()
42✔
277
                        ->scalarNode('nesting_separator')->defaultValue('_')->info('The separator to use to filter nested fields.')->end()
42✔
278
                        ->arrayNode('collection')
42✔
279
                            ->addDefaultsIfNotSet()
42✔
280
                            ->children()
42✔
281
                                ->arrayNode('pagination')
42✔
282
                                    ->canBeDisabled()
42✔
283
                                ->end()
42✔
284
                            ->end()
42✔
285
                        ->end()
42✔
286
                    ->end()
42✔
287
                ->end()
42✔
288
            ->end();
42✔
289
    }
290

291
    private function addSwaggerSection(ArrayNodeDefinition $rootNode): void
292
    {
293
        $supportedVersions = [3];
42✔
294

295
        $rootNode
42✔
296
            ->children()
42✔
297
                ->arrayNode('swagger')
42✔
298
                    ->addDefaultsIfNotSet()
42✔
299
                    ->children()
42✔
300
                        ->booleanNode('persist_authorization')->defaultValue(false)->info('Persist the SwaggerUI Authorization in the localStorage.')->end()
42✔
301
                        ->arrayNode('versions')
42✔
302
                            ->info('The active versions of OpenAPI to be exported or used in Swagger UI. The first value is the default.')
42✔
303
                            ->defaultValue($supportedVersions)
42✔
304
                            ->beforeNormalization()
42✔
305
                                ->always(static function ($v): array {
42✔
306
                                    if (!\is_array($v)) {
4✔
307
                                        $v = [$v];
×
308
                                    }
309

310
                                    foreach ($v as &$version) {
4✔
311
                                        $version = (int) $version;
2✔
312
                                    }
313

314
                                    return $v;
4✔
315
                                })
42✔
316
                            ->end()
42✔
317
                            ->validate()
42✔
318
                                ->ifTrue(static fn ($v): bool => $v !== array_intersect($v, $supportedVersions))
42✔
319
                                ->thenInvalid(sprintf('Only the versions %s are supported. Got %s.', implode(' and ', $supportedVersions), '%s'))
42✔
320
                            ->end()
42✔
321
                            ->prototype('scalar')->end()
42✔
322
                        ->end()
42✔
323
                        ->arrayNode('api_keys')
42✔
324
                            ->useAttributeAsKey('key')
42✔
325
                            ->validate()
42✔
326
                                ->ifTrue(static fn ($v): bool => (bool) array_filter(array_keys($v), fn ($item) => !preg_match('/^[a-zA-Z0-9._-]+$/', $item)))
42✔
327
                                ->thenInvalid('The api keys "key" is not valid according to the pattern enforced by OpenAPI 3.1 ^[a-zA-Z0-9._-]+$.')
42✔
328
                            ->end()
42✔
329
                            ->prototype('array')
42✔
330
                                ->children()
42✔
331
                                    ->scalarNode('name')
42✔
332
                                        ->info('The name of the header or query parameter containing the api key.')
42✔
333
                                    ->end()
42✔
334
                                    ->enumNode('type')
42✔
335
                                        ->info('Whether the api key should be a query parameter or a header.')
42✔
336
                                        ->values(['query', 'header'])
42✔
337
                                    ->end()
42✔
338
                                ->end()
42✔
339
                            ->end()
42✔
340
                        ->end()
42✔
341
                        ->arrayNode('http_auth')
42✔
342
                            ->info('Creates http security schemes for OpenAPI.')
42✔
343
                            ->useAttributeAsKey('key')
42✔
344
                            ->validate()
42✔
345
                                ->ifTrue(static fn ($v): bool => (bool) array_filter(array_keys($v), fn ($item) => !preg_match('/^[a-zA-Z0-9._-]+$/', $item)))
42✔
346
                                ->thenInvalid('The api keys "key" is not valid according to the pattern enforced by OpenAPI 3.1 ^[a-zA-Z0-9._-]+$.')
42✔
347
                            ->end()
42✔
348
                            ->prototype('array')
42✔
349
                                ->children()
42✔
350
                                    ->scalarNode('scheme')
42✔
351
                                        ->info('The OpenAPI HTTP auth scheme, for example "bearer"')
42✔
352
                                    ->end()
42✔
353
                                    ->scalarNode('bearerFormat')
42✔
354
                                        ->info('The OpenAPI HTTP bearer format')
42✔
355
                                    ->end()
42✔
356
                                ->end()
42✔
357
                            ->end()
42✔
358
                        ->end()
42✔
359
                        ->variableNode('swagger_ui_extra_configuration')
42✔
360
                            ->defaultValue([])
42✔
361
                            ->validate()
42✔
362
                                ->ifTrue(static fn ($v): bool => false === \is_array($v))
42✔
363
                                ->thenInvalid('The swagger_ui_extra_configuration parameter must be an array.')
42✔
364
                            ->end()
42✔
365
                            ->info('To pass extra configuration to Swagger UI, like docExpansion or filter.')
42✔
366
                        ->end()
42✔
367
                    ->end()
42✔
368
                ->end()
42✔
369
            ->end();
42✔
370
    }
371

372
    private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void
373
    {
374
        $rootNode
42✔
375
            ->children()
42✔
376
                ->arrayNode('http_cache')
42✔
377
                    ->addDefaultsIfNotSet()
42✔
378
                    ->children()
42✔
379
                        ->booleanNode('public')->defaultNull()->info('To make all responses public by default.')->end()
42✔
380
                        ->arrayNode('invalidation')
42✔
381
                            ->info('Enable the tags-based cache invalidation system.')
42✔
382
                            ->canBeEnabled()
42✔
383
                            ->children()
42✔
384
                                ->arrayNode('varnish_urls')
42✔
385
                                    ->setDeprecated('api-platform/core', '3.0', 'The "varnish_urls" configuration is deprecated, use "urls" or "scoped_clients".')
42✔
386
                                    ->defaultValue([])
42✔
387
                                    ->prototype('scalar')->end()
42✔
388
                                    ->info('URLs of the Varnish servers to purge using cache tags when a resource is updated.')
42✔
389
                                ->end()
42✔
390
                                ->arrayNode('urls')
42✔
391
                                    ->defaultValue([])
42✔
392
                                    ->prototype('scalar')->end()
42✔
393
                                    ->info('URLs of the Varnish servers to purge using cache tags when a resource is updated.')
42✔
394
                                ->end()
42✔
395
                                ->arrayNode('scoped_clients')
42✔
396
                                    ->defaultValue([])
42✔
397
                                    ->prototype('scalar')->end()
42✔
398
                                    ->info('Service names of scoped client to use by the cache purger.')
42✔
399
                                ->end()
42✔
400
                                ->integerNode('max_header_length')
42✔
401
                                    ->defaultValue(7500)
42✔
402
                                    ->info('Max header length supported by the cache server.')
42✔
403
                                ->end()
42✔
404
                                ->variableNode('request_options')
42✔
405
                                    ->defaultValue([])
42✔
406
                                    ->validate()
42✔
407
                                        ->ifTrue(static fn ($v): bool => false === \is_array($v))
42✔
408
                                        ->thenInvalid('The request_options parameter must be an array.')
42✔
409
                                    ->end()
42✔
410
                                    ->info('To pass options to the client charged with the request.')
42✔
411
                                ->end()
42✔
412
                                ->scalarNode('purger')
42✔
413
                                    ->defaultValue('api_platform.http_cache.purger.varnish')
42✔
414
                                    ->info('Specify a purger to use (available values: "api_platform.http_cache.purger.varnish.ban", "api_platform.http_cache.purger.varnish.xkey", "api_platform.http_cache.purger.souin").')
42✔
415
                                ->end()
42✔
416
                                ->arrayNode('xkey')
42✔
417
                                    ->setDeprecated('api-platform/core', '3.0', 'The "xkey" configuration is deprecated, use your own purger to customize surrogate keys or the appropriate paramters.')
42✔
418
                                    ->addDefaultsIfNotSet()
42✔
419
                                    ->children()
42✔
420
                                        ->scalarNode('glue')
42✔
421
                                        ->defaultValue(' ')
42✔
422
                                        ->info('xkey glue between keys')
42✔
423
                                        ->end()
42✔
424
                                    ->end()
42✔
425
                                ->end()
42✔
426
                            ->end()
42✔
427
                        ->end()
42✔
428
                    ->end()
42✔
429
                ->end()
42✔
430
            ->end();
42✔
431
    }
432

433
    private function addMercureSection(ArrayNodeDefinition $rootNode): void
434
    {
435
        $rootNode
42✔
436
            ->children()
42✔
437
                ->arrayNode('mercure')
42✔
438
                    ->{class_exists(MercureBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
42✔
439
                    ->children()
42✔
440
                        ->scalarNode('hub_url')
42✔
441
                            ->defaultNull()
42✔
442
                            ->info('The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle\'s default hub.')
42✔
443
                        ->end()
42✔
444
                        ->booleanNode('include_type')
42✔
445
                            ->defaultFalse()
42✔
446
                            ->info('Always include @type in updates (including delete ones).')
42✔
447
                        ->end()
42✔
448
                    ->end()
42✔
449
                ->end()
42✔
450
            ->end();
42✔
451
    }
452

453
    private function addMessengerSection(ArrayNodeDefinition $rootNode): void
454
    {
455
        $rootNode
42✔
456
            ->children()
42✔
457
                ->arrayNode('messenger')
42✔
458
                    ->{!class_exists(FullStack::class) && interface_exists(MessageBusInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}()
42✔
459
                ->end()
42✔
460
            ->end();
42✔
461
    }
462

463
    private function addElasticsearchSection(ArrayNodeDefinition $rootNode): void
464
    {
465
        $rootNode
42✔
466
            ->children()
42✔
467
                ->arrayNode('elasticsearch')
42✔
468
                    ->canBeEnabled()
42✔
469
                    ->addDefaultsIfNotSet()
42✔
470
                    ->children()
42✔
471
                        ->booleanNode('enabled')
42✔
472
                            ->defaultFalse()
42✔
473
                            ->validate()
42✔
474
                                ->ifTrue()
42✔
475
                                ->then(static function (bool $v): bool {
42✔
476
                                    if (
477
                                        // ES v7
478
                                        !class_exists(\Elasticsearch\Client::class)
2✔
479
                                        // ES v8 and up
480
                                        && !class_exists(\Elastic\Elasticsearch\Client::class)
2✔
481
                                    ) {
482
                                        throw new InvalidConfigurationException('The elasticsearch/elasticsearch package is required for Elasticsearch support.');
×
483
                                    }
484

485
                                    return $v;
2✔
486
                                })
42✔
487
                            ->end()
42✔
488
                        ->end()
42✔
489
                        ->arrayNode('hosts')
42✔
490
                            ->beforeNormalization()->castToArray()->end()
42✔
491
                            ->defaultValue([])
42✔
492
                            ->prototype('scalar')->end()
42✔
493
                        ->end()
42✔
494
                    ->end()
42✔
495
                ->end()
42✔
496
            ->end();
42✔
497
    }
498

499
    private function addOpenApiSection(ArrayNodeDefinition $rootNode): void
500
    {
501
        $rootNode
42✔
502
            ->children()
42✔
503
                ->arrayNode('openapi')
42✔
504
                    ->addDefaultsIfNotSet()
42✔
505
                        ->children()
42✔
506
                        ->arrayNode('contact')
42✔
507
                        ->addDefaultsIfNotSet()
42✔
508
                            ->children()
42✔
509
                                ->scalarNode('name')->defaultNull()->info('The identifying name of the contact person/organization.')->end()
42✔
510
                                ->scalarNode('url')->defaultNull()->info('The URL pointing to the contact information. MUST be in the format of a URL.')->end()
42✔
511
                                ->scalarNode('email')->defaultNull()->info('The email address of the contact person/organization. MUST be in the format of an email address.')->end()
42✔
512
                            ->end()
42✔
513
                        ->end()
42✔
514
                        ->scalarNode('termsOfService')->defaultNull()->info('A URL to the Terms of Service for the API. MUST be in the format of a URL.')->end()
42✔
515
                        ->arrayNode('tags')
42✔
516
                            ->info('Global OpenApi tags overriding the default computed tags if specified.')
42✔
517
                            ->prototype('array')
42✔
518
                                ->children()
42✔
519
                                    ->scalarNode('name')->isRequired()->end()
42✔
520
                                    ->scalarNode('description')->defaultNull()->end()
42✔
521
                                ->end()
42✔
522
                            ->end()
42✔
523
                        ->end()
42✔
524
                        ->arrayNode('license')
42✔
525
                        ->addDefaultsIfNotSet()
42✔
526
                            ->children()
42✔
527
                                ->scalarNode('name')->defaultNull()->info('The license name used for the API.')->end()
42✔
528
                                ->scalarNode('url')->defaultNull()->info('URL to the license used for the API. MUST be in the format of a URL.')->end()
42✔
529
                                ->scalarNode('identifier')->defaultNull()->info('An SPDX license expression for the API. The identifier field is mutually exclusive of the url field.')->end()
42✔
530
                            ->end()
42✔
531
                        ->end()
42✔
532
                        ->variableNode('swagger_ui_extra_configuration')
42✔
533
                            ->defaultValue([])
42✔
534
                            ->validate()
42✔
535
                                ->ifTrue(static fn ($v): bool => false === \is_array($v))
42✔
536
                                ->thenInvalid('The swagger_ui_extra_configuration parameter must be an array.')
42✔
537
                            ->end()
42✔
538
                            ->info('To pass extra configuration to Swagger UI, like docExpansion or filter.')
42✔
539
                        ->end()
42✔
540
                        ->booleanNode('overrideResponses')->defaultTrue()->info('Whether API Platform adds automatic responses to the OpenAPI documentation.')->end()
42✔
541
                        ->scalarNode('error_resource_class')->defaultNull()->info('The class used to represent errors in the OpenAPI documentation.')->end()
42✔
542
                        ->scalarNode('validation_error_resource_class')->defaultNull()->info('The class used to represent validation errors in the OpenAPI documentation.')->end()
42✔
543
                    ->end()
42✔
544
                ->end()
42✔
545
            ->end();
42✔
546
    }
547

548
    /**
549
     * @throws InvalidConfigurationException
550
     */
551
    private function addExceptionToStatusSection(ArrayNodeDefinition $rootNode): void
552
    {
553
        $rootNode
42✔
554
            ->children()
42✔
555
                ->arrayNode('exception_to_status')
42✔
556
                    ->defaultValue([
42✔
557
                        SerializerExceptionInterface::class => Response::HTTP_BAD_REQUEST,
42✔
558
                        InvalidArgumentException::class => Response::HTTP_BAD_REQUEST,
42✔
559
                        OptimisticLockException::class => Response::HTTP_CONFLICT,
42✔
560
                    ])
42✔
561
                    ->info('The list of exceptions mapped to their HTTP status code.')
42✔
562
                    ->normalizeKeys(false)
42✔
563
                    ->useAttributeAsKey('exception_class')
42✔
564
                    ->prototype('integer')->end()
42✔
565
                    ->validate()
42✔
566
                        ->ifArray()
42✔
567
                        ->then(static function (array $exceptionToStatus): array {
42✔
568
                            foreach ($exceptionToStatus as $httpStatusCode) {
10✔
569
                                if ($httpStatusCode < 100 || $httpStatusCode >= 600) {
10✔
570
                                    throw new InvalidConfigurationException(sprintf('The HTTP status code "%s" is not valid.', $httpStatusCode));
8✔
571
                                }
572
                            }
573

574
                            return $exceptionToStatus;
2✔
575
                        })
42✔
576
                    ->end()
42✔
577
                ->end()
42✔
578
            ->end();
42✔
579
    }
580

581
    private function addFormatSection(ArrayNodeDefinition $rootNode, string $key, array $defaultValue): void
582
    {
583
        $rootNode
42✔
584
            ->children()
42✔
585
                ->arrayNode($key)
42✔
586
                    ->defaultValue($defaultValue)
42✔
587
                    ->info('The list of enabled formats. The first one will be the default.')
42✔
588
                    ->normalizeKeys(false)
42✔
589
                    ->useAttributeAsKey('format')
42✔
590
                    ->beforeNormalization()
42✔
591
                        ->ifArray()
42✔
592
                        ->then(static function ($v) {
42✔
593
                            foreach ($v as $format => $value) {
2✔
594
                                if (isset($value['mime_types'])) {
2✔
595
                                    continue;
×
596
                                }
597

598
                                $v[$format] = ['mime_types' => $value];
2✔
599
                            }
600

601
                            return $v;
2✔
602
                        })
42✔
603
                    ->end()
42✔
604
                    ->prototype('array')
42✔
605
                        ->children()
42✔
606
                            ->arrayNode('mime_types')->prototype('scalar')->end()->end()
42✔
607
                        ->end()
42✔
608
                    ->end()
42✔
609
                ->end()
42✔
610
            ->end();
42✔
611
    }
612

613
    private function addDefaultsSection(ArrayNodeDefinition $rootNode): void
614
    {
615
        $nameConverter = new CamelCaseToSnakeCaseNameConverter();
42✔
616
        $defaultsNode = $rootNode->children()->arrayNode('defaults');
42✔
617

618
        $defaultsNode
42✔
619
            ->ignoreExtraKeys(false)
42✔
620
            ->beforeNormalization()
42✔
621
            ->always(static function (array $defaults) use ($nameConverter): array {
42✔
622
                $normalizedDefaults = [];
2✔
623
                foreach ($defaults as $option => $value) {
2✔
624
                    $option = $nameConverter->normalize($option);
2✔
625
                    $normalizedDefaults[$option] = $value;
2✔
626
                }
627

628
                return $normalizedDefaults;
2✔
629
            });
42✔
630

631
        $this->defineDefault($defaultsNode, new \ReflectionClass(ApiResource::class), $nameConverter);
42✔
632
        $this->defineDefault($defaultsNode, new \ReflectionClass(Put::class), $nameConverter);
42✔
633
        $this->defineDefault($defaultsNode, new \ReflectionClass(Post::class), $nameConverter);
42✔
634
    }
635

636
    private function addMakerSection(ArrayNodeDefinition $rootNode): void
637
    {
638
        $rootNode
42✔
639
            ->children()
42✔
640
                ->arrayNode('maker')
42✔
641
                    ->{class_exists(MakerBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
42✔
642
                ->end()
42✔
643
            ->end();
42✔
644
    }
645

646
    private function defineDefault(ArrayNodeDefinition $defaultsNode, \ReflectionClass $reflectionClass, CamelCaseToSnakeCaseNameConverter $nameConverter): void
647
    {
648
        foreach ($reflectionClass->getConstructor()->getParameters() as $parameter) {
42✔
649
            $defaultsNode->children()->variableNode($nameConverter->normalize($parameter->getName()));
42✔
650
        }
651
    }
652
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc