Passed
Push — master ( 23d0f8...d047d8 )
by Kévin
04:09
created

Bundle/DependencyInjection/Configuration.php (1 issue)

Labels
Severity
1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
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\Core\Bridge\Symfony\Bundle\DependencyInjection;
15
16
use ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\DocumentMetadata;
17
use ApiPlatform\Core\Exception\FilterValidationException;
18
use ApiPlatform\Core\Exception\InvalidArgumentException;
19
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
20
use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle;
21
use Doctrine\ORM\OptimisticLockException;
22
use Doctrine\ORM\Version as DoctrineOrmVersion;
23
use Elasticsearch\Client as ElasticsearchClient;
24
use FOS\UserBundle\FOSUserBundle;
25
use GraphQL\GraphQL;
26
use Symfony\Bundle\FullStack;
27
use Symfony\Bundle\MercureBundle\MercureBundle;
28
use Symfony\Bundle\TwigBundle\TwigBundle;
29
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
30
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
31
use Symfony\Component\Config\Definition\ConfigurationInterface;
32
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
33
use Symfony\Component\HttpFoundation\Response;
34
use Symfony\Component\Messenger\MessageBusInterface;
35
use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerExceptionInterface;
36
37
/**
38
 * The configuration of the bundle.
39
 *
40
 * @author Kévin Dunglas <[email protected]>
41
 * @author Baptiste Meyer <[email protected]>
42
 */
43
final class Configuration implements ConfigurationInterface
44
{
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function getConfigTreeBuilder()
49
    {
50
        if (method_exists(TreeBuilder::class, 'getRootNode')) {
51
            $treeBuilder = new TreeBuilder('api_platform');
52
            $rootNode = $treeBuilder->getRootNode();
53
        } else {
54
            $treeBuilder = new TreeBuilder();
55
            $rootNode = $treeBuilder->root('api_platform');
56
        }
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
66
                    return $v;
67
                })
68
            ->end()
69
            ->children()
70
                ->scalarNode('title')
71
                    ->info('The title of the API.')
72
                    ->cannotBeEmpty()
73
                    ->defaultValue('')
74
                ->end()
75
                ->scalarNode('description')
76
                    ->info('The description of the API.')
77
                    ->cannotBeEmpty()
78
                    ->defaultValue('')
79
                ->end()
80
                ->scalarNode('version')
81
                    ->info('The version of the API.')
82
                    ->cannotBeEmpty()
83
                    ->defaultValue('0.0.0')
84
                ->end()
85
                ->booleanNode('show_webby')->defaultTrue()->info('If true, show Webby on the documentation page')->end()
86
                ->scalarNode('default_operation_path_resolver')
87
                    ->defaultValue('api_platform.operation_path_resolver.underscore')
88
                    ->setDeprecated('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('path_segment_name_generator')->defaultValue('api_platform.path_segment_name_generator.underscore')->info('Specify a path name generator to use.')->end()
93
                ->booleanNode('allow_plain_identifiers')->defaultFalse()->info('Allow plain identifiers, for example "id" instead of "@id" when denormalizing a relation.')->end()
94
                ->arrayNode('validator')
95
                    ->addDefaultsIfNotSet()
96
                    ->children()
97
                        ->variableNode('serialize_payload_fields')->defaultValue([])->info('Enable the serialization of payload fields when a validation error is thrown.')->end()
98
                    ->end()
99
                ->end()
100
                ->arrayNode('eager_loading')
101
                    ->canBeDisabled()
102
                    ->addDefaultsIfNotSet()
103
                    ->children()
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()
105
                        ->integerNode('max_joins')->defaultValue(30)->info('Max number of joined relations before EagerLoading throws a RuntimeException')->end()
106
                        ->booleanNode('force_eager')->defaultTrue()->info('Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode.')->end()
107
                    ->end()
108
                ->end()
109
                ->booleanNode('enable_fos_user')->defaultValue(class_exists(FOSUserBundle::class))->info('Enable the FOSUserBundle integration.')->end()
110
                ->booleanNode('enable_nelmio_api_doc')
111
                    ->defaultFalse()
112
                    ->setDeprecated('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.')
113
                    ->info('Enable the NelmioApiDocBundle integration.')
114
                ->end()
115
                ->booleanNode('enable_swagger')->defaultTrue()->info('Enable the Swagger documentation and export.')->end()
116
                ->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger UI')->end()
117
                ->booleanNode('enable_re_doc')->defaultValue(class_exists(TwigBundle::class))->info('Enable ReDoc')->end()
118
                ->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end()
119
                ->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end()
120
                ->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end()
121
                ->arrayNode('collection')
122
                    ->addDefaultsIfNotSet()
123
                    ->children()
124
                        ->scalarNode('exists_parameter_name')->defaultValue('exists')->cannotBeEmpty()->info('The name of the query parameter to filter on nullable field values.')->end()
125
                        ->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
126
                        ->scalarNode('order_parameter_name')->defaultValue('order')->cannotBeEmpty()->info('The name of the query parameter to order results.')->end()
127
                        ->arrayNode('pagination')
128
                            ->canBeDisabled()
129
                            ->addDefaultsIfNotSet()
130
                            ->children()
131
                                ->booleanNode('enabled')->defaultTrue()->info('To enable or disable pagination for all resource collections by default.')->end()
132
                                ->booleanNode('partial')->defaultFalse()->info('To enable or disable partial pagination for all resource collections by default when pagination is enabled.')->end()
133
                                ->booleanNode('client_enabled')->defaultFalse()->info('To allow the client to enable or disable the pagination.')->end()
134
                                ->booleanNode('client_items_per_page')->defaultFalse()->info('To allow the client to set the number of items per page.')->end()
135
                                ->booleanNode('client_partial')->defaultFalse()->info('To allow the client to enable or disable partial pagination.')->end()
136
                                ->integerNode('items_per_page')->defaultValue(30)->info('The default number of items per page.')->end()
137
                                ->integerNode('maximum_items_per_page')->defaultNull()->info('The maximum number of items per page.')->end()
138
                                ->scalarNode('page_parameter_name')->defaultValue('page')->cannotBeEmpty()->info('The default name of the parameter handling the page number.')->end()
139
                                ->scalarNode('enabled_parameter_name')->defaultValue('pagination')->cannotBeEmpty()->info('The name of the query parameter to enable or disable pagination.')->end()
140
                                ->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()
141
                                ->scalarNode('partial_parameter_name')->defaultValue('partial')->cannotBeEmpty()->info('The name of the query parameter to enable or disable partial pagination.')->end()
142
                            ->end()
143
                        ->end()
144
                    ->end()
145
                ->end()
146
                ->arrayNode('mapping')
147
                    ->addDefaultsIfNotSet()
148
                    ->children()
149
                        ->arrayNode('paths')
150
                            ->prototype('scalar')->end()
151
                        ->end()
152
                    ->end()
153
                ->end()
154
                ->arrayNode('resource_class_directories')
155
                    ->prototype('scalar')->end()
156
                ->end()
157
            ->end();
158
159
        $this->addDoctrineOrmSection($rootNode);
160
        $this->addDoctrineMongoDbOdmSection($rootNode);
161
        $this->addOAuthSection($rootNode);
162
        $this->addGraphQlSection($rootNode);
163
        $this->addSwaggerSection($rootNode);
164
        $this->addHttpCacheSection($rootNode);
165
        $this->addMercureSection($rootNode);
166
        $this->addMessengerSection($rootNode);
167
        $this->addElasticsearchSection($rootNode);
168
169
        $this->addExceptionToStatusSection($rootNode);
170
171
        $this->addFormatSection($rootNode, 'formats', [
172
            'jsonld' => ['mime_types' => ['application/ld+json']],
173
            'json' => ['mime_types' => ['application/json']], // Swagger support
174
            'html' => ['mime_types' => ['text/html']], // Swagger UI support
175
        ]);
176
        $this->addFormatSection($rootNode, 'patch_formats', []);
177
        $this->addFormatSection($rootNode, 'error_formats', [
178
            'jsonproblem' => ['mime_types' => ['application/problem+json']],
179
            'jsonld' => ['mime_types' => ['application/ld+json']],
180
        ]);
181
182
        return $treeBuilder;
183
    }
184
185
    private function addDoctrineOrmSection(ArrayNodeDefinition $rootNode): void
186
    {
187
        $rootNode
188
            ->children()
189
                ->arrayNode('doctrine')
190
                    ->{class_exists(DoctrineBundle::class) && class_exists(DoctrineOrmVersion::class) ? 'canBeDisabled' : 'canBeEnabled'}()
191
                ->end()
192
            ->end();
193
    }
194
195
    private function addDoctrineMongoDbOdmSection(ArrayNodeDefinition $rootNode): void
196
    {
197
        $rootNode
198
            ->children()
199
                ->arrayNode('doctrine_mongodb_odm')
200
                    ->{class_exists(DoctrineMongoDBBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
201
                ->end()
202
            ->end();
203
    }
204
205
    private function addOAuthSection(ArrayNodeDefinition $rootNode): void
206
    {
207
        $rootNode
208
            ->children()
209
                ->arrayNode('oauth')
210
                    ->canBeEnabled()
211
                    ->addDefaultsIfNotSet()
212
                    ->children()
213
                        ->scalarNode('clientId')->defaultValue('')->info('The oauth client id.')->end()
214
                        ->scalarNode('clientSecret')->defaultValue('')->info('The oauth client secret.')->end()
215
                        ->scalarNode('type')->defaultValue('oauth2')->info('The oauth client secret.')->end()
216
                        ->scalarNode('flow')->defaultValue('application')->info('The oauth flow grant type.')->end()
217
                        ->scalarNode('tokenUrl')->defaultValue('/oauth/v2/token')->info('The oauth token url.')->end()
218
                        ->scalarNode('authorizationUrl')->defaultValue('/oauth/v2/auth')->info('The oauth authentication url.')->end()
219
                        ->arrayNode('scopes')
220
                            ->prototype('scalar')->end()
221
                        ->end()
222
                    ->end()
223
                ->end()
224
            ->end();
225
    }
226
227
    private function addGraphQlSection(ArrayNodeDefinition $rootNode): void
228
    {
229
        $rootNode
230
            ->children()
231
                ->arrayNode('graphql')
232
                    ->{class_exists(GraphQL::class) ? 'canBeDisabled' : 'canBeEnabled'}()
233
                    ->addDefaultsIfNotSet()
234
                    ->children()
235
                        ->scalarNode('default_ide')->defaultValue('graphiql')->end()
236
                        ->arrayNode('graphiql')
237
                            ->{class_exists(GraphQL::class) && class_exists(TwigBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
238
                        ->end()
239
                        ->arrayNode('graphql_playground')
240
                            ->{class_exists(GraphQL::class) && class_exists(TwigBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
241
                        ->end()
242
                    ->end()
243
                ->end()
244
            ->end();
245
    }
246
247
    private function addSwaggerSection(ArrayNodeDefinition $rootNode): void
248
    {
249
        $defaultVersions = [2, 3];
250
251
        $rootNode
252
            ->children()
253
                ->arrayNode('swagger')
254
                    ->addDefaultsIfNotSet()
255
                    ->children()
256
                        ->arrayNode('versions')
257
                            ->info('The active versions of OpenAPI to be exported or used in the swagger_ui. The first value is the default.')
258
                            ->defaultValue($defaultVersions)
259
                            ->beforeNormalization()
260
                                ->always(static function ($v) {
261
                                    if (!\is_array($v)) {
262
                                        $v = [$v];
263
                                    }
264
265
                                    foreach ($v as &$version) {
266
                                        $version = (int) $version;
267
                                    }
268
269
                                    return $v;
270
                                })
271
                            ->end()
272
                            ->validate()
273
                                ->ifTrue(function ($v) use ($defaultVersions) {
274
                                    return $v !== array_intersect($v, $defaultVersions);
275
                                })
276
                                ->thenInvalid(sprintf('Only the versions %s are supported. Got %s.', implode(' and ', $defaultVersions), '%s'))
277
                            ->end()
278
                            ->prototype('scalar')->end()
0 ignored issues
show
The method prototype() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. It seems like you code against a sub-type of Symfony\Component\Config...\Builder\NodeDefinition such as Symfony\Component\Config...der\ArrayNodeDefinition. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

278
                            ->/** @scrutinizer ignore-call */ prototype('scalar')->end()
Loading history...
279
                        ->end()
280
                        ->arrayNode('api_keys')
281
                            ->prototype('array')
282
                                ->children()
283
                                    ->scalarNode('name')
284
                                        ->info('The name of the header or query parameter containing the api key.')
285
                                    ->end()
286
                                    ->enumNode('type')
287
                                        ->info('Whether the api key should be a query parameter or a header.')
288
                                        ->values(['query', 'header'])
289
                                    ->end()
290
                                ->end()
291
                            ->end()
292
                        ->end()
293
                    ->end()
294
                ->end()
295
            ->end();
296
    }
297
298
    private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void
299
    {
300
        $rootNode
301
            ->children()
302
                ->arrayNode('http_cache')
303
                    ->addDefaultsIfNotSet()
304
                    ->children()
305
                        ->booleanNode('etag')->defaultTrue()->info('Automatically generate etags for API responses.')->end()
306
                        ->integerNode('max_age')->defaultNull()->info('Default value for the response max age.')->end()
307
                        ->integerNode('shared_max_age')->defaultNull()->info('Default value for the response shared (proxy) max age.')->end()
308
                        ->arrayNode('vary')
309
                            ->defaultValue(['Accept'])
310
                            ->prototype('scalar')->end()
311
                            ->info('Default values of the "Vary" HTTP header.')
312
                        ->end()
313
                        ->booleanNode('public')->defaultNull()->info('To make all responses public by default.')->end()
314
                        ->arrayNode('invalidation')
315
                            ->info('Enable the tags-based cache invalidation system.')
316
                            ->canBeEnabled()
317
                            ->children()
318
                                ->arrayNode('varnish_urls')
319
                                    ->defaultValue([])
320
                                    ->prototype('scalar')->end()
321
                                    ->info('URLs of the Varnish servers to purge using cache tags when a resource is updated.')
322
                                ->end()
323
                                ->variableNode('request_options')
324
                                    ->defaultValue([])
325
                                    ->validate()
326
                                        ->ifTrue(function ($v) { return false === \is_array($v); })
327
                                        ->thenInvalid('The request_options parameter must be an array.')
328
                                    ->end()
329
                                    ->info('To pass options to the client charged with the request.')
330
                                ->end()
331
                            ->end()
332
                        ->end()
333
                    ->end()
334
                ->end()
335
            ->end();
336
    }
337
338
    private function addMercureSection(ArrayNodeDefinition $rootNode): void
339
    {
340
        $rootNode
341
            ->children()
342
                ->arrayNode('mercure')
343
                    ->{class_exists(MercureBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
344
                    ->children()
345
                        ->scalarNode('hub_url')
346
                            ->defaultNull()
347
                            ->info('The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle\'s default hub.')
348
                        ->end()
349
                    ->end()
350
                ->end()
351
            ->end();
352
    }
353
354
    private function addMessengerSection(ArrayNodeDefinition $rootNode): void
355
    {
356
        $rootNode
357
            ->children()
358
                ->arrayNode('messenger')
359
                    ->{!class_exists(FullStack::class) && interface_exists(MessageBusInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}()
360
                ->end()
361
            ->end();
362
    }
363
364
    private function addElasticsearchSection(ArrayNodeDefinition $rootNode): void
365
    {
366
        $rootNode
367
            ->children()
368
                ->arrayNode('elasticsearch')
369
                    ->canBeEnabled()
370
                    ->addDefaultsIfNotSet()
371
                    ->children()
372
                        ->booleanNode('enabled')
373
                            ->defaultFalse()
374
                            ->validate()
375
                                ->ifTrue()
376
                                ->then(static function (bool $v): bool {
377
                                    if (!class_exists(ElasticsearchClient::class)) {
378
                                        throw new InvalidConfigurationException('The elasticsearch/elasticsearch package is required for Elasticsearch support.');
379
                                    }
380
381
                                    return $v;
382
                                })
383
                            ->end()
384
                        ->end()
385
                        ->arrayNode('hosts')
386
                            ->beforeNormalization()->castToArray()->end()
387
                            ->defaultValue([])
388
                            ->prototype('scalar')->end()
389
                        ->end()
390
                        ->arrayNode('mapping')
391
                            ->normalizeKeys(false)
392
                            ->useAttributeAsKey('resource_class')
393
                            ->prototype('array')
394
                                ->children()
395
                                    ->scalarNode('index')->defaultNull()->end()
396
                                    ->scalarNode('type')->defaultValue(DocumentMetadata::DEFAULT_TYPE)->end()
397
                                ->end()
398
                            ->end()
399
                        ->end()
400
                    ->end()
401
                ->end()
402
            ->end();
403
    }
404
405
    /**
406
     * @throws InvalidConfigurationException
407
     */
408
    private function addExceptionToStatusSection(ArrayNodeDefinition $rootNode): void
409
    {
410
        $rootNode
411
            ->children()
412
                ->arrayNode('exception_to_status')
413
                    ->defaultValue([
414
                        SerializerExceptionInterface::class => Response::HTTP_BAD_REQUEST,
415
                        InvalidArgumentException::class => Response::HTTP_BAD_REQUEST,
416
                        FilterValidationException::class => Response::HTTP_BAD_REQUEST,
417
                        OptimisticLockException::class => Response::HTTP_CONFLICT,
418
                    ])
419
                    ->info('The list of exceptions mapped to their HTTP status code.')
420
                    ->normalizeKeys(false)
421
                    ->useAttributeAsKey('exception_class')
422
                    ->beforeNormalization()
423
                        ->ifArray()
424
                        ->then(function (array $exceptionToStatus) {
425
                            foreach ($exceptionToStatus as &$httpStatusCode) {
426
                                if (\is_int($httpStatusCode)) {
427
                                    continue;
428
                                }
429
430
                                if (\defined($httpStatusCodeConstant = sprintf('%s::%s', Response::class, $httpStatusCode))) {
431
                                    @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);
432
433
                                    $httpStatusCode = \constant($httpStatusCodeConstant);
434
                                }
435
                            }
436
437
                            return $exceptionToStatus;
438
                        })
439
                    ->end()
440
                    ->prototype('integer')->end()
441
                    ->validate()
442
                        ->ifArray()
443
                        ->then(function (array $exceptionToStatus) {
444
                            foreach ($exceptionToStatus as $httpStatusCode) {
445
                                if ($httpStatusCode < 100 || $httpStatusCode >= 600) {
446
                                    throw new InvalidConfigurationException(sprintf('The HTTP status code "%s" is not valid.', $httpStatusCode));
447
                                }
448
                            }
449
450
                            return $exceptionToStatus;
451
                        })
452
                    ->end()
453
                ->end()
454
            ->end();
455
    }
456
457
    private function addFormatSection(ArrayNodeDefinition $rootNode, string $key, array $defaultValue): void
458
    {
459
        $rootNode
460
            ->children()
461
                ->arrayNode($key)
462
                    ->defaultValue($defaultValue)
463
                    ->info('The list of enabled formats. The first one will be the default.')
464
                    ->normalizeKeys(false)
465
                    ->useAttributeAsKey('format')
466
                    ->beforeNormalization()
467
                        ->ifArray()
468
                        ->then(function ($v) {
469
                            foreach ($v as $format => $value) {
470
                                if (isset($value['mime_types'])) {
471
                                    continue;
472
                                }
473
474
                                $v[$format] = ['mime_types' => $value];
475
                            }
476
477
                            return $v;
478
                        })
479
                    ->end()
480
                    ->prototype('array')
481
                        ->children()
482
                            ->arrayNode('mime_types')->prototype('scalar')->end()->end()
483
                        ->end()
484
                    ->end()
485
                ->end()
486
            ->end();
487
    }
488
}
489