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

api-platform / core / 10537652610

24 Aug 2024 10:04AM UTC coverage: 7.707%. Remained the same
10537652610

push

github

dunglas
cleanup

12490 of 162060 relevant lines covered (7.71%)

22.98 hits per line

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

80.32
/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.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\Odm\Extension\AggregationCollectionExtensionInterface;
17
use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
18
use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter as DoctrineMongoDbOdmAbstractFilter;
19
use ApiPlatform\Doctrine\Odm\State\LinksHandlerInterface as OdmLinksHandlerInterface;
20
use ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension;
21
use ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension;
22
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface as DoctrineQueryCollectionExtensionInterface;
23
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
24
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter as DoctrineOrmAbstractFilter;
25
use ApiPlatform\Doctrine\Orm\State\LinksHandlerInterface as OrmLinksHandlerInterface;
26
use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface;
27
use ApiPlatform\GraphQl\Error\ErrorHandlerInterface;
28
use ApiPlatform\GraphQl\Resolver\MutationResolverInterface;
29
use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface;
30
use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
31
use ApiPlatform\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface;
32
use ApiPlatform\Metadata\ApiResource;
33
use ApiPlatform\Metadata\FilterInterface;
34
use ApiPlatform\Metadata\UriVariableTransformerInterface;
35
use ApiPlatform\Metadata\UrlGeneratorInterface;
36
use ApiPlatform\State\ApiResource\Error;
37
use ApiPlatform\State\ParameterProviderInterface;
38
use ApiPlatform\State\ProcessorInterface;
39
use ApiPlatform\State\ProviderInterface;
40
use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface;
41
use ApiPlatform\Symfony\Validator\ValidationGroupsGeneratorInterface;
42
use ApiPlatform\Validator\Exception\ValidationException;
43
use Doctrine\Persistence\ManagerRegistry;
44
use phpDocumentor\Reflection\DocBlockFactoryInterface;
45
use PHPStan\PhpDocParser\Parser\PhpDocParser;
46
use Ramsey\Uuid\Uuid;
47
use Symfony\Component\Config\FileLocator;
48
use Symfony\Component\Config\Resource\DirectoryResource;
49
use Symfony\Component\DependencyInjection\ContainerBuilder;
50
use Symfony\Component\DependencyInjection\Definition;
51
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
52
use Symfony\Component\DependencyInjection\Extension\Extension;
53
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
54
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
55
use Symfony\Component\DependencyInjection\Reference;
56
use Symfony\Component\Finder\Finder;
57
use Symfony\Component\HttpClient\ScopingHttpClient;
58
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
59
use Symfony\Component\Uid\AbstractUid;
60
use Symfony\Component\Validator\Validator\ValidatorInterface;
61
use Symfony\Component\Yaml\Yaml;
62
use Twig\Environment;
63

64
/**
65
 * The extension of this bundle.
66
 *
67
 * @author Kévin Dunglas <dunglas@gmail.com>
68
 */
69
final class ApiPlatformExtension extends Extension implements PrependExtensionInterface
70
{
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function prepend(ContainerBuilder $container): void
75
    {
76
        if (isset($container->getExtensions()['framework'])) {
3✔
77
            $container->prependExtensionConfig('framework', [
3✔
78
                'serializer' => [
3✔
79
                    'enabled' => true,
3✔
80
                ],
3✔
81
            ]);
3✔
82
            $container->prependExtensionConfig('framework', [
3✔
83
                'property_info' => [
3✔
84
                    'enabled' => true,
3✔
85
                ],
3✔
86
            ]);
3✔
87
        }
88
        if (isset($container->getExtensions()['lexik_jwt_authentication'])) {
3✔
89
            $container->prependExtensionConfig('lexik_jwt_authentication', [
×
90
                'api_platform' => [
×
91
                    'enabled' => true,
×
92
                ],
×
93
            ]);
×
94
        }
95
    }
96

97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function load(array $configs, ContainerBuilder $container): void
101
    {
102
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
3✔
103

104
        $configuration = new Configuration();
3✔
105
        $config = $this->processConfiguration($configuration, $configs);
3✔
106
        $container->setParameter('api_platform.use_symfony_listeners', $config['use_symfony_listeners']);
3✔
107

108
        $formats = $this->getFormats($config['formats']);
3✔
109
        $patchFormats = $this->getFormats($config['patch_formats']);
3✔
110
        $errorFormats = $this->getFormats($config['error_formats']);
3✔
111
        $docsFormats = $this->getFormats($config['docs_formats']);
3✔
112
        $jsonSchemaFormats = $config['jsonschema_formats'];
3✔
113

114
        if (!$jsonSchemaFormats) {
3✔
115
            foreach (array_keys($formats) as $f) {
3✔
116
                // Distinct JSON-based formats must have names that start with 'json'
117
                if (str_starts_with($f, 'json')) {
3✔
118
                    $jsonSchemaFormats[$f] = true;
3✔
119
                }
120
            }
121
        }
122

123
        if (!isset($errorFormats['json'])) {
3✔
124
            $errorFormats['json'] = ['application/problem+json', 'application/json'];
3✔
125
        }
126

127
        if (!isset($errorFormats['jsonproblem'])) {
3✔
128
            $errorFormats['jsonproblem'] = ['application/problem+json'];
×
129
        }
130

131
        if (isset($formats['jsonapi']) && !isset($patchFormats['jsonapi'])) {
3✔
132
            $patchFormats['jsonapi'] = ['application/vnd.api+json'];
3✔
133
        }
134

135
        $this->registerCommonConfiguration($container, $config, $loader, $formats, $patchFormats, $errorFormats, $docsFormats, $jsonSchemaFormats);
3✔
136
        $this->registerMetadataConfiguration($container, $config, $loader);
3✔
137
        $this->registerOAuthConfiguration($container, $config);
3✔
138
        $this->registerOpenApiConfiguration($container, $config, $loader);
3✔
139
        $this->registerSwaggerConfiguration($container, $config, $loader);
3✔
140
        $this->registerJsonApiConfiguration($formats, $loader, $config);
3✔
141
        $this->registerJsonLdHydraConfiguration($container, $formats, $loader, $config);
3✔
142
        $this->registerJsonHalConfiguration($formats, $loader);
3✔
143
        $this->registerJsonProblemConfiguration($errorFormats, $loader);
3✔
144
        $this->registerGraphQlConfiguration($container, $config, $loader);
3✔
145
        $this->registerCacheConfiguration($container);
3✔
146
        $this->registerDoctrineOrmConfiguration($container, $config, $loader);
3✔
147
        $this->registerDoctrineMongoDbOdmConfiguration($container, $config, $loader);
3✔
148
        $this->registerHttpCacheConfiguration($container, $config, $loader);
3✔
149
        $this->registerValidatorConfiguration($container, $config, $loader);
3✔
150
        $this->registerDataCollectorConfiguration($container, $config, $loader);
3✔
151
        $this->registerMercureConfiguration($container, $config, $loader);
3✔
152
        $this->registerMessengerConfiguration($container, $config, $loader);
3✔
153
        $this->registerElasticsearchConfiguration($container, $config, $loader);
3✔
154
        $this->registerSecurityConfiguration($container, $config, $loader);
3✔
155
        $this->registerMakerConfiguration($container, $config, $loader);
3✔
156
        $this->registerArgumentResolverConfiguration($loader);
3✔
157
        $this->registerLinkSecurityConfiguration($loader, $config);
3✔
158

159
        $container->registerForAutoconfiguration(FilterInterface::class)
3✔
160
            ->addTag('api_platform.filter');
3✔
161
        $container->registerForAutoconfiguration(ProviderInterface::class)
3✔
162
            ->addTag('api_platform.state_provider');
3✔
163
        $container->registerForAutoconfiguration(ProcessorInterface::class)
3✔
164
            ->addTag('api_platform.state_processor');
3✔
165
        $container->registerForAutoconfiguration(UriVariableTransformerInterface::class)
3✔
166
            ->addTag('api_platform.uri_variables.transformer');
3✔
167
        $container->registerForAutoconfiguration(ParameterProviderInterface::class)
3✔
168
            ->addTag('api_platform.parameter_provider');
3✔
169

170
        if (!$container->has('api_platform.state.item_provider')) {
3✔
171
            $container->setAlias('api_platform.state.item_provider', 'api_platform.state_provider.object');
×
172
        }
173
    }
174

175
    private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $patchFormats, array $errorFormats, array $docsFormats, array $jsonSchemaFormats): void
176
    {
177
        $loader->load('state/state.xml');
3✔
178
        $loader->load('symfony/symfony.xml');
3✔
179
        $loader->load('api.xml');
3✔
180
        $loader->load('filter.xml');
3✔
181

182
        if (class_exists(Uuid::class)) {
3✔
183
            $loader->load('ramsey_uuid.xml');
3✔
184
        }
185

186
        if (class_exists(AbstractUid::class)) {
3✔
187
            $loader->load('symfony/uid.xml');
3✔
188
        }
189

190
        if ($config['use_symfony_listeners']) {
3✔
191
            $loader->load('symfony/events.xml');
1✔
192
        } else {
193
            $loader->load('symfony/controller.xml');
2✔
194
            $loader->load('state/provider.xml');
2✔
195
            $loader->load('state/processor.xml');
2✔
196
        }
197

198
        $container->setParameter('api_platform.enable_entrypoint', $config['enable_entrypoint']);
3✔
199
        $container->setParameter('api_platform.enable_docs', $config['enable_docs']);
3✔
200
        $container->setParameter('api_platform.title', $config['title']);
3✔
201
        $container->setParameter('api_platform.description', $config['description']);
3✔
202
        $container->setParameter('api_platform.version', $config['version']);
3✔
203
        $container->setParameter('api_platform.show_webby', $config['show_webby']);
3✔
204
        $container->setParameter('api_platform.url_generation_strategy', $config['defaults']['url_generation_strategy'] ?? UrlGeneratorInterface::ABS_PATH);
3✔
205
        $container->setParameter('api_platform.exception_to_status', $config['exception_to_status']);
3✔
206
        $container->setParameter('api_platform.formats', $formats);
3✔
207
        $container->setParameter('api_platform.patch_formats', $patchFormats);
3✔
208
        $container->setParameter('api_platform.error_formats', $errorFormats);
3✔
209
        $container->setParameter('api_platform.docs_formats', $docsFormats);
3✔
210
        $container->setParameter('api_platform.jsonschema_formats', $jsonSchemaFormats);
3✔
211
        $container->setParameter('api_platform.eager_loading.enabled', $this->isConfigEnabled($container, $config['eager_loading']));
3✔
212
        $container->setParameter('api_platform.eager_loading.max_joins', $config['eager_loading']['max_joins']);
3✔
213
        $container->setParameter('api_platform.eager_loading.fetch_partial', $config['eager_loading']['fetch_partial']);
3✔
214
        $container->setParameter('api_platform.eager_loading.force_eager', $config['eager_loading']['force_eager']);
3✔
215
        $container->setParameter('api_platform.collection.exists_parameter_name', $config['collection']['exists_parameter_name']);
3✔
216
        $container->setParameter('api_platform.collection.order', $config['collection']['order']);
3✔
217
        $container->setParameter('api_platform.collection.order_parameter_name', $config['collection']['order_parameter_name']);
3✔
218
        $container->setParameter('api_platform.collection.order_nulls_comparison', $config['collection']['order_nulls_comparison']);
3✔
219
        $container->setParameter('api_platform.collection.pagination.enabled', $config['defaults']['pagination_enabled'] ?? true);
3✔
220
        $container->setParameter('api_platform.collection.pagination.partial', $config['defaults']['pagination_partial'] ?? false);
3✔
221
        $container->setParameter('api_platform.collection.pagination.client_enabled', $config['defaults']['pagination_client_enabled'] ?? false);
3✔
222
        $container->setParameter('api_platform.collection.pagination.client_items_per_page', $config['defaults']['pagination_client_items_per_page'] ?? false);
3✔
223
        $container->setParameter('api_platform.collection.pagination.client_partial', $config['defaults']['pagination_client_partial'] ?? false);
3✔
224
        $container->setParameter('api_platform.collection.pagination.items_per_page', $config['defaults']['pagination_items_per_page'] ?? 30);
3✔
225
        $container->setParameter('api_platform.collection.pagination.maximum_items_per_page', $config['defaults']['pagination_maximum_items_per_page'] ?? null);
3✔
226
        $container->setParameter('api_platform.collection.pagination.page_parameter_name', $config['defaults']['pagination_page_parameter_name'] ?? $config['collection']['pagination']['page_parameter_name']);
3✔
227
        $container->setParameter('api_platform.collection.pagination.enabled_parameter_name', $config['defaults']['pagination_enabled_parameter_name'] ?? $config['collection']['pagination']['enabled_parameter_name']);
3✔
228
        $container->setParameter('api_platform.collection.pagination.items_per_page_parameter_name', $config['defaults']['pagination_items_per_page_parameter_name'] ?? $config['collection']['pagination']['items_per_page_parameter_name']);
3✔
229
        $container->setParameter('api_platform.collection.pagination.partial_parameter_name', $config['defaults']['pagination_partial_parameter_name'] ?? $config['collection']['pagination']['partial_parameter_name']);
3✔
230
        $container->setParameter('api_platform.collection.pagination', $this->getPaginationDefaults($config['defaults'] ?? [], $config['collection']['pagination']));
3✔
231
        $container->setParameter('api_platform.handle_symfony_errors', $config['handle_symfony_errors'] ?? false);
3✔
232
        $container->setParameter('api_platform.http_cache.etag', $config['defaults']['cache_headers']['etag'] ?? true);
3✔
233
        $container->setParameter('api_platform.http_cache.max_age', $config['defaults']['cache_headers']['max_age'] ?? null);
3✔
234
        $container->setParameter('api_platform.http_cache.shared_max_age', $config['defaults']['cache_headers']['shared_max_age'] ?? null);
3✔
235
        $container->setParameter('api_platform.http_cache.vary', $config['defaults']['cache_headers']['vary'] ?? ['Accept']);
3✔
236
        $container->setParameter('api_platform.http_cache.public', $config['defaults']['cache_headers']['public'] ?? $config['http_cache']['public']);
3✔
237
        $container->setParameter('api_platform.http_cache.invalidation.max_header_length', $config['defaults']['cache_headers']['invalidation']['max_header_length'] ?? $config['http_cache']['invalidation']['max_header_length']);
3✔
238
        $container->setParameter('api_platform.http_cache.invalidation.xkey.glue', $config['defaults']['cache_headers']['invalidation']['xkey']['glue'] ?? $config['http_cache']['invalidation']['xkey']['glue']);
3✔
239

240
        $container->setAlias('api_platform.path_segment_name_generator', $config['path_segment_name_generator']);
3✔
241
        $container->setAlias('api_platform.inflector', $config['inflector']);
3✔
242

243
        if ($config['name_converter']) {
3✔
244
            $container->setAlias('api_platform.name_converter', $config['name_converter']);
3✔
245
        }
246
        $container->setParameter('api_platform.asset_package', $config['asset_package']);
3✔
247
        $container->setParameter('api_platform.defaults', $this->normalizeDefaults($config['defaults'] ?? []));
3✔
248

249
        if ($container->getParameter('kernel.debug')) {
3✔
250
            $container->removeDefinition('api_platform.serializer.mapping.cache_class_metadata_factory');
3✔
251
        }
252
    }
253

254
    /**
255
     * This method will be removed in 3.0 when "defaults" will be the regular configuration path for the pagination.
256
     */
257
    private function getPaginationDefaults(array $defaults, array $collectionPaginationConfiguration): array
258
    {
259
        $paginationOptions = [];
3✔
260

261
        foreach ($defaults as $key => $value) {
3✔
262
            if (!str_starts_with($key, 'pagination_')) {
3✔
263
                continue;
3✔
264
            }
265

266
            $paginationOptions[str_replace('pagination_', '', $key)] = $value;
3✔
267
        }
268

269
        return array_merge($collectionPaginationConfiguration, $paginationOptions);
3✔
270
    }
271

272
    private function normalizeDefaults(array $defaults): array
273
    {
274
        $normalizedDefaults = ['extra_properties' => $defaults['extra_properties'] ?? []];
3✔
275
        unset($defaults['extra_properties']);
3✔
276

277
        $rc = new \ReflectionClass(ApiResource::class);
3✔
278
        $publicProperties = [];
3✔
279
        foreach ($rc->getConstructor()->getParameters() as $param) {
3✔
280
            $publicProperties[$param->getName()] = true;
3✔
281
        }
282

283
        $nameConverter = new CamelCaseToSnakeCaseNameConverter();
3✔
284
        foreach ($defaults as $option => $value) {
3✔
285
            if (isset($publicProperties[$nameConverter->denormalize($option)])) {
3✔
286
                $normalizedDefaults[$option] = $value;
3✔
287

288
                continue;
3✔
289
            }
290

291
            $normalizedDefaults['extra_properties'][$option] = $value;
×
292
        }
293

294
        return $normalizedDefaults;
3✔
295
    }
296

297
    private function registerMetadataConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
298
    {
299
        [$xmlResources, $yamlResources] = $this->getResourcesToWatch($container, $config);
3✔
300

301
        $container->setParameter('api_platform.class_name_resources', $this->getClassNameResources());
3✔
302

303
        $loader->load('metadata/resource_name.xml');
3✔
304
        $loader->load('metadata/property_name.xml');
3✔
305

306
        if (!empty($config['resource_class_directories'])) {
3✔
307
            $container->setParameter('api_platform.resource_class_directories', array_merge(
×
308
                $config['resource_class_directories'],
×
309
                $container->getParameter('api_platform.resource_class_directories')
×
310
            ));
×
311
        }
312

313
        // V3 metadata
314
        $loader->load('metadata/xml.xml');
3✔
315
        $loader->load('metadata/links.xml');
3✔
316
        $loader->load('metadata/property.xml');
3✔
317
        $loader->load('metadata/resource.xml');
3✔
318
        $loader->load('metadata/operation.xml');
3✔
319

320
        $container->getDefinition('api_platform.metadata.resource_extractor.xml')->replaceArgument(0, $xmlResources);
3✔
321
        $container->getDefinition('api_platform.metadata.property_extractor.xml')->replaceArgument(0, $xmlResources);
3✔
322

323
        if (class_exists(PhpDocParser::class) || interface_exists(DocBlockFactoryInterface::class)) {
3✔
324
            $loader->load('metadata/php_doc.xml');
3✔
325
        }
326

327
        if (class_exists(Yaml::class)) {
3✔
328
            $loader->load('metadata/yaml.xml');
3✔
329
            $container->getDefinition('api_platform.metadata.resource_extractor.yaml')->replaceArgument(0, $yamlResources);
3✔
330
            $container->getDefinition('api_platform.metadata.property_extractor.yaml')->replaceArgument(0, $yamlResources);
3✔
331
        }
332
    }
333

334
    private function getClassNameResources(): array
335
    {
336
        return [
3✔
337
            Error::class,
3✔
338
            ValidationException::class,
3✔
339
        ];
3✔
340
    }
341

342
    private function getBundlesResourcesPaths(ContainerBuilder $container, array $config): array
343
    {
344
        $bundlesResourcesPaths = [];
3✔
345

346
        foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
3✔
347
            $dirname = $bundle['path'];
3✔
348
            $paths = [
3✔
349
                "$dirname/ApiResource",
3✔
350
                "$dirname/src/ApiResource",
3✔
351
            ];
3✔
352
            foreach (['.yaml', '.yml', '.xml', ''] as $extension) {
3✔
353
                $paths[] = "$dirname/Resources/config/api_resources$extension";
3✔
354
                $paths[] = "$dirname/config/api_resources$extension";
3✔
355
            }
356
            if ($this->isConfigEnabled($container, $config['doctrine'])) {
3✔
357
                $paths[] = "$dirname/Entity";
3✔
358
                $paths[] = "$dirname/src/Entity";
3✔
359
            }
360
            if ($this->isConfigEnabled($container, $config['doctrine_mongodb_odm'])) {
3✔
361
                $paths[] = "$dirname/Document";
×
362
                $paths[] = "$dirname/src/Document";
×
363
            }
364

365
            foreach ($paths as $path) {
3✔
366
                if ($container->fileExists($path, false)) {
3✔
367
                    $bundlesResourcesPaths[] = $path;
3✔
368
                }
369
            }
370
        }
371

372
        return $bundlesResourcesPaths;
3✔
373
    }
374

375
    private function getResourcesToWatch(ContainerBuilder $container, array $config): array
376
    {
377
        $paths = array_unique(array_merge($this->getBundlesResourcesPaths($container, $config), $config['mapping']['paths']));
3✔
378

379
        if (!$config['mapping']['paths']) {
3✔
380
            $projectDir = $container->getParameter('kernel.project_dir');
×
381
            foreach (["$projectDir/config/api_platform", "$projectDir/src/ApiResource"] as $dir) {
×
382
                if (is_dir($dir)) {
×
383
                    $paths[] = $dir;
×
384
                }
385
            }
386

387
            if ($this->isConfigEnabled($container, $config['doctrine']) && is_dir($doctrinePath = "$projectDir/src/Entity")) {
×
388
                $paths[] = $doctrinePath;
×
389
            }
390

391
            if ($this->isConfigEnabled($container, $config['doctrine_mongodb_odm']) && is_dir($documentPath = "$projectDir/src/Document")) {
×
392
                $paths[] = $documentPath;
×
393
            }
394
        }
395

396
        $resources = ['yml' => [], 'xml' => [], 'dir' => []];
3✔
397

398
        foreach ($paths as $path) {
3✔
399
            if (is_dir($path)) {
3✔
400
                foreach (Finder::create()->followLinks()->files()->in($path)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) {
3✔
401
                    $resources['yaml' === ($extension = $file->getExtension()) ? 'yml' : $extension][] = $file->getRealPath();
3✔
402
                }
403

404
                $resources['dir'][] = $path;
3✔
405
                $container->addResource(new DirectoryResource($path, '/\.(xml|ya?ml|php)$/'));
3✔
406

407
                continue;
3✔
408
            }
409

410
            if ($container->fileExists($path, false)) {
×
411
                if (!preg_match('/\.(xml|ya?ml)$/', (string) $path, $matches)) {
×
412
                    throw new RuntimeException(\sprintf('Unsupported mapping type in "%s", supported types are XML & YAML.', $path));
×
413
                }
414

415
                $resources['yaml' === $matches[1] ? 'yml' : $matches[1]][] = $path;
×
416

417
                continue;
×
418
            }
419

420
            throw new RuntimeException(\sprintf('Could not open file or directory "%s".', $path));
×
421
        }
422

423
        $container->setParameter('api_platform.resource_class_directories', $resources['dir']);
3✔
424

425
        return [$resources['xml'], $resources['yml']];
3✔
426
    }
427

428
    private function registerOAuthConfiguration(ContainerBuilder $container, array $config): void
429
    {
430
        if (!$config['oauth']) {
3✔
431
            return;
×
432
        }
433

434
        $container->setParameter('api_platform.oauth.enabled', $this->isConfigEnabled($container, $config['oauth']));
3✔
435
        $container->setParameter('api_platform.oauth.clientId', $config['oauth']['clientId']);
3✔
436
        $container->setParameter('api_platform.oauth.clientSecret', $config['oauth']['clientSecret']);
3✔
437
        $container->setParameter('api_platform.oauth.type', $config['oauth']['type']);
3✔
438
        $container->setParameter('api_platform.oauth.flow', $config['oauth']['flow']);
3✔
439
        $container->setParameter('api_platform.oauth.tokenUrl', $config['oauth']['tokenUrl']);
3✔
440
        $container->setParameter('api_platform.oauth.authorizationUrl', $config['oauth']['authorizationUrl']);
3✔
441
        $container->setParameter('api_platform.oauth.refreshUrl', $config['oauth']['refreshUrl']);
3✔
442
        $container->setParameter('api_platform.oauth.scopes', $config['oauth']['scopes']);
3✔
443
        $container->setParameter('api_platform.oauth.pkce', $config['oauth']['pkce']);
3✔
444

445
        if ($container->hasDefinition('api_platform.swagger_ui.action')) {
3✔
446
            $container->getDefinition('api_platform.swagger_ui.action')->setArgument(10, $config['oauth']['pkce']);
×
447
        }
448
    }
449

450
    /**
451
     * Registers the Swagger, ReDoc and Swagger UI configuration.
452
     */
453
    private function registerSwaggerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
454
    {
455
        foreach (array_keys($config['swagger']['api_keys']) as $keyName) {
3✔
456
            if (!preg_match('/^[a-zA-Z0-9._-]+$/', $keyName)) {
3✔
457
                trigger_deprecation('api-platform/core', '3.1', \sprintf('The swagger api_keys key "%s" is not valid with OpenAPI 3.1 it should match "^[a-zA-Z0-9._-]+$"', $keyName));
×
458
            }
459
        }
460

461
        $container->setParameter('api_platform.swagger.versions', $config['swagger']['versions']);
3✔
462

463
        if (!$config['enable_swagger'] && $config['enable_swagger_ui']) {
3✔
464
            throw new RuntimeException('You can not enable the Swagger UI without enabling Swagger, fix this by enabling swagger via the configuration "enable_swagger: true".');
×
465
        }
466

467
        if (!$config['enable_swagger']) {
3✔
468
            return;
×
469
        }
470

471
        $loader->load('openapi.xml');
3✔
472

473
        if (class_exists(Yaml::class)) {
3✔
474
            $loader->load('openapi/yaml.xml');
3✔
475
        }
476

477
        $loader->load('swagger_ui.xml');
3✔
478

479
        if ($config['use_symfony_listeners']) {
3✔
480
            $loader->load('symfony/swagger_ui.xml');
1✔
481
        }
482

483
        if ($config['enable_swagger_ui']) {
3✔
484
            $loader->load('state/swagger_ui.xml');
3✔
485
        }
486

487
        if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) {
3✔
488
            // Remove the listener but keep the controller to allow customizing the path of the UI
489
            $container->removeDefinition('api_platform.swagger.listener.ui');
×
490
        }
491

492
        $container->setParameter('api_platform.enable_swagger_ui', $config['enable_swagger_ui']);
3✔
493
        $container->setParameter('api_platform.enable_re_doc', $config['enable_re_doc']);
3✔
494
        $container->setParameter('api_platform.swagger.api_keys', $config['swagger']['api_keys']);
3✔
495
        if ($config['openapi']['swagger_ui_extra_configuration'] && $config['swagger']['swagger_ui_extra_configuration']) {
3✔
496
            throw new RuntimeException('You can not set "swagger_ui_extra_configuration" twice - in "openapi" and "swagger" section.');
×
497
        }
498
        $container->setParameter('api_platform.swagger_ui.extra_configuration', $config['openapi']['swagger_ui_extra_configuration'] ?: $config['swagger']['swagger_ui_extra_configuration']);
3✔
499
    }
500

501
    private function registerJsonApiConfiguration(array $formats, XmlFileLoader $loader, array $config): void
502
    {
503
        if (!isset($formats['jsonapi'])) {
3✔
504
            return;
×
505
        }
506

507
        $loader->load('jsonapi.xml');
3✔
508
        $loader->load('state/jsonapi.xml');
3✔
509
    }
510

511
    private function registerJsonLdHydraConfiguration(ContainerBuilder $container, array $formats, XmlFileLoader $loader, array $config): void
512
    {
513
        if (!isset($formats['jsonld'])) {
3✔
514
            return;
×
515
        }
516

517
        if ($config['use_symfony_listeners']) {
3✔
518
            $loader->load('symfony/jsonld.xml');
1✔
519
        } else {
520
            $loader->load('state/jsonld.xml');
2✔
521
        }
522

523
        $loader->load('state/hydra.xml');
3✔
524
        $loader->load('jsonld.xml');
3✔
525
        $loader->load('hydra.xml');
3✔
526

527
        if (!$container->has('api_platform.json_schema.schema_factory')) {
3✔
528
            $container->removeDefinition('api_platform.hydra.json_schema.schema_factory');
×
529
        }
530

531
        if (!$config['enable_docs']) {
3✔
532
            $container->removeDefinition('api_platform.hydra.listener.response.add_link_header');
×
533
            $container->removeDefinition('api_platform.hydra.processor.link');
×
534
        }
535
    }
536

537
    private function registerJsonHalConfiguration(array $formats, XmlFileLoader $loader): void
538
    {
539
        if (!isset($formats['jsonhal'])) {
3✔
540
            return;
×
541
        }
542

543
        $loader->load('hal.xml');
3✔
544
    }
545

546
    private function registerJsonProblemConfiguration(array $errorFormats, XmlFileLoader $loader): void
547
    {
548
        if (!isset($errorFormats['jsonproblem'])) {
3✔
549
            return;
×
550
        }
551

552
        $loader->load('problem.xml');
3✔
553
    }
554

555
    private function registerGraphQlConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
556
    {
557
        $enabled = $this->isConfigEnabled($container, $config['graphql']);
3✔
558

559
        $graphqlIntrospectionEnabled = $enabled && $this->isConfigEnabled($container, $config['graphql']['introspection']);
3✔
560

561
        $graphiqlEnabled = $enabled && $this->isConfigEnabled($container, $config['graphql']['graphiql']);
3✔
562
        $graphqlPlayGroundEnabled = $enabled && $this->isConfigEnabled($container, $config['graphql']['graphql_playground']);
3✔
563
        if ($graphqlPlayGroundEnabled) {
3✔
564
            trigger_deprecation('api-platform/core', '3.1', 'GraphQL Playground is deprecated and will be removed in API Platform 4.0. Only GraphiQL will be available in the future. Set api_platform.graphql.graphql_playground to false in the configuration to remove this deprecation.');
×
565
        }
566

567
        $container->setParameter('api_platform.graphql.enabled', $enabled);
3✔
568
        $container->setParameter('api_platform.graphql.introspection.enabled', $graphqlIntrospectionEnabled);
3✔
569
        $container->setParameter('api_platform.graphql.graphiql.enabled', $graphiqlEnabled);
3✔
570
        $container->setParameter('api_platform.graphql.graphql_playground.enabled', $graphqlPlayGroundEnabled);
3✔
571
        $container->setParameter('api_platform.graphql.collection.pagination', $config['graphql']['collection']['pagination']);
3✔
572

573
        if (!$enabled) {
3✔
574
            return;
×
575
        }
576

577
        $container->setParameter('api_platform.graphql.default_ide', $config['graphql']['default_ide']);
3✔
578
        $container->setParameter('api_platform.graphql.nesting_separator', $config['graphql']['nesting_separator']);
3✔
579

580
        $loader->load('graphql.xml');
3✔
581

582
        // @phpstan-ignore-next-line because PHPStan uses the container of the test env cache and in test the parameter kernel.bundles always contains the key TwigBundle
583
        if (!class_exists(Environment::class) || !isset($container->getParameter('kernel.bundles')['TwigBundle'])) {
3✔
584
            if ($graphiqlEnabled || $graphqlPlayGroundEnabled) {
×
585
                throw new RuntimeException(\sprintf('GraphiQL and GraphQL Playground interfaces depend on Twig. Please activate TwigBundle for the %s environnement or disable GraphiQL and GraphQL Playground.', $container->getParameter('kernel.environment')));
×
586
            }
587
            $container->removeDefinition('api_platform.graphql.action.graphiql');
×
588
            $container->removeDefinition('api_platform.graphql.action.graphql_playground');
×
589
        }
590

591
        $container->registerForAutoconfiguration(QueryItemResolverInterface::class)
3✔
592
            ->addTag('api_platform.graphql.resolver');
3✔
593
        $container->registerForAutoconfiguration(QueryCollectionResolverInterface::class)
3✔
594
            ->addTag('api_platform.graphql.resolver');
3✔
595
        $container->registerForAutoconfiguration(MutationResolverInterface::class)
3✔
596
            ->addTag('api_platform.graphql.resolver');
3✔
597
        $container->registerForAutoconfiguration(GraphQlTypeInterface::class)
3✔
598
            ->addTag('api_platform.graphql.type');
3✔
599
        $container->registerForAutoconfiguration(ErrorHandlerInterface::class)
3✔
600
            ->addTag('api_platform.graphql.error_handler');
3✔
601
    }
602

603
    private function registerCacheConfiguration(ContainerBuilder $container): void
604
    {
605
        if (!$container->hasParameter('kernel.debug') || !$container->getParameter('kernel.debug')) {
3✔
606
            $container->removeDefinition('api_platform.cache_warmer.cache_pool_clearer');
×
607
        }
608
    }
609

610
    private function registerDoctrineOrmConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
611
    {
612
        if (!$this->isConfigEnabled($container, $config['doctrine'])) {
3✔
613
            return;
×
614
        }
615

616
        // For older versions of doctrine bridge this allows autoconfiguration for filters
617
        if (!$container->has(ManagerRegistry::class)) {
3✔
618
            $container->setAlias(ManagerRegistry::class, 'doctrine');
3✔
619
        }
620

621
        $container->registerForAutoconfiguration(QueryItemExtensionInterface::class)
3✔
622
            ->addTag('api_platform.doctrine.orm.query_extension.item');
3✔
623
        $container->registerForAutoconfiguration(DoctrineQueryCollectionExtensionInterface::class)
3✔
624
            ->addTag('api_platform.doctrine.orm.query_extension.collection');
3✔
625
        $container->registerForAutoconfiguration(DoctrineOrmAbstractFilter::class);
3✔
626

627
        $container->registerForAutoconfiguration(OrmLinksHandlerInterface::class)
3✔
628
            ->addTag('api_platform.doctrine.orm.links_handler');
3✔
629

630
        $loader->load('doctrine_orm.xml');
3✔
631

632
        if ($this->isConfigEnabled($container, $config['eager_loading'])) {
3✔
633
            return;
3✔
634
        }
635

636
        $container->removeAlias(EagerLoadingExtension::class);
×
637
        $container->removeDefinition('api_platform.doctrine.orm.query_extension.eager_loading');
×
638
        $container->removeAlias(FilterEagerLoadingExtension::class);
×
639
        $container->removeDefinition('api_platform.doctrine.orm.query_extension.filter_eager_loading');
×
640
    }
641

642
    private function registerDoctrineMongoDbOdmConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
643
    {
644
        if (!$this->isConfigEnabled($container, $config['doctrine_mongodb_odm'])) {
3✔
645
            return;
3✔
646
        }
647

648
        $container->registerForAutoconfiguration(AggregationItemExtensionInterface::class)
×
649
            ->addTag('api_platform.doctrine_mongodb.odm.aggregation_extension.item');
×
650
        $container->registerForAutoconfiguration(AggregationCollectionExtensionInterface::class)
×
651
            ->addTag('api_platform.doctrine_mongodb.odm.aggregation_extension.collection');
×
652
        $container->registerForAutoconfiguration(DoctrineMongoDbOdmAbstractFilter::class)
×
653
            ->setBindings(['$managerRegistry' => new Reference('doctrine_mongodb')]);
×
654
        $container->registerForAutoconfiguration(OdmLinksHandlerInterface::class)
×
655
            ->addTag('api_platform.doctrine.odm.links_handler');
×
656

657
        $loader->load('doctrine_mongodb_odm.xml');
×
658
    }
659

660
    private function registerHttpCacheConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
661
    {
662
        $loader->load('http_cache.xml');
3✔
663

664
        if (!$this->isConfigEnabled($container, $config['http_cache']['invalidation'])) {
3✔
665
            return;
×
666
        }
667

668
        if ($this->isConfigEnabled($container, $config['doctrine'])) {
3✔
669
            $loader->load('doctrine_orm_http_cache_purger.xml');
3✔
670
        }
671

672
        $loader->load('state/http_cache_purger.xml');
3✔
673
        $loader->load('http_cache_purger.xml');
3✔
674

675
        foreach ($config['http_cache']['invalidation']['scoped_clients'] as $client) {
3✔
676
            $definition = $container->getDefinition($client);
×
677
            $definition->addTag('api_platform.http_cache.http_client');
×
678
        }
679

680
        if (!($urls = $config['http_cache']['invalidation']['urls'])) {
3✔
681
            $urls = $config['http_cache']['invalidation']['varnish_urls'];
3✔
682
        }
683

684
        foreach ($urls as $key => $url) {
3✔
685
            $definition = new Definition(ScopingHttpClient::class, [new Reference('http_client'), $url, ['base_uri' => $url] + $config['http_cache']['invalidation']['request_options']]);
×
686
            $definition->setFactory([ScopingHttpClient::class, 'forBaseUri']);
×
687
            $definition->addTag('api_platform.http_cache.http_client');
×
688
            $container->setDefinition('api_platform.invalidation_http_client.'.$key, $definition);
×
689
        }
690

691
        $serviceName = $config['http_cache']['invalidation']['purger'];
3✔
692

693
        if (!$container->hasDefinition('api_platform.http_cache.purger')) {
3✔
694
            $container->setAlias('api_platform.http_cache.purger', $serviceName);
3✔
695
        }
696
    }
697

698
    /**
699
     * Normalizes the format from config to the one accepted by Symfony HttpFoundation.
700
     */
701
    private function getFormats(array $configFormats): array
702
    {
703
        $formats = [];
3✔
704
        foreach ($configFormats as $format => $value) {
3✔
705
            foreach ($value['mime_types'] as $mimeType) {
3✔
706
                $formats[$format][] = $mimeType;
3✔
707
            }
708
        }
709

710
        return $formats;
3✔
711
    }
712

713
    private function registerValidatorConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
714
    {
715
        if (interface_exists(ValidatorInterface::class)) {
3✔
716
            $loader->load('metadata/validator.xml');
3✔
717
            $loader->load('validator/validator.xml');
3✔
718

719
            if ($this->isConfigEnabled($container, $config['graphql'])) {
3✔
720
                $loader->load('graphql/validator.xml');
3✔
721
            }
722

723
            $loader->load($config['use_symfony_listeners'] ? 'validator/events.xml' : 'validator/state.xml');
3✔
724

725
            $container->registerForAutoconfiguration(ValidationGroupsGeneratorInterface::class)
3✔
726
                ->addTag('api_platform.validation_groups_generator');
3✔
727
            $container->registerForAutoconfiguration(PropertySchemaRestrictionMetadataInterface::class)
3✔
728
                ->addTag('api_platform.metadata.property_schema_restriction');
3✔
729
        }
730

731
        if (!$config['validator']) {
3✔
732
            return;
×
733
        }
734

735
        $container->setParameter('api_platform.validator.serialize_payload_fields', $config['validator']['serialize_payload_fields']);
3✔
736
        $container->setParameter('api_platform.validator.query_parameter_validation', $config['validator']['query_parameter_validation']);
3✔
737

738
        if (!$config['validator']['query_parameter_validation']) {
3✔
739
            $container->removeDefinition('api_platform.listener.view.validate_query_parameters');
×
740
            $container->removeDefinition('api_platform.validator.query_parameter_validator');
×
741
            $container->removeDefinition('api_platform.symfony.parameter_validator');
×
742
        }
743
    }
744

745
    private function registerDataCollectorConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
746
    {
747
        if (!$config['enable_profiler']) {
3✔
748
            return;
×
749
        }
750

751
        $loader->load('data_collector.xml');
3✔
752

753
        if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) {
3✔
754
            $loader->load('debug.xml');
3✔
755
        }
756
    }
757

758
    private function registerMercureConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
759
    {
760
        if (!$this->isConfigEnabled($container, $config['mercure'])) {
3✔
761
            return;
×
762
        }
763

764
        $container->setParameter('api_platform.mercure.include_type', $config['mercure']['include_type']);
3✔
765
        $loader->load('state/mercure.xml');
3✔
766

767
        if ($this->isConfigEnabled($container, $config['doctrine'])) {
3✔
768
            $loader->load('doctrine_orm_mercure_publisher.xml');
3✔
769
        }
770
        if ($this->isConfigEnabled($container, $config['doctrine_mongodb_odm'])) {
3✔
771
            $loader->load('doctrine_odm_mercure_publisher.xml');
×
772
        }
773

774
        if ($this->isConfigEnabled($container, $config['graphql'])) {
3✔
775
            $loader->load('graphql_mercure.xml');
3✔
776
        }
777
    }
778

779
    private function registerMessengerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
780
    {
781
        if (!$this->isConfigEnabled($container, $config['messenger'])) {
3✔
782
            return;
×
783
        }
784

785
        $loader->load('messenger.xml');
3✔
786
    }
787

788
    private function registerElasticsearchConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
789
    {
790
        $enabled = $this->isConfigEnabled($container, $config['elasticsearch']);
3✔
791

792
        $container->setParameter('api_platform.elasticsearch.enabled', $enabled);
3✔
793

794
        if (!$enabled) {
3✔
795
            return;
3✔
796
        }
797

798
        $clientClass = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\Client::class;
×
799

800
        $clientDefinition = new Definition($clientClass);
×
801
        $container->setDefinition('api_platform.elasticsearch.client', $clientDefinition);
×
802
        $container->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class)
×
803
            ->addTag('api_platform.elasticsearch.request_body_search_extension.collection');
×
804
        $container->setParameter('api_platform.elasticsearch.hosts', $config['elasticsearch']['hosts']);
×
805
        $loader->load('elasticsearch.xml');
×
806
    }
807

808
    private function registerSecurityConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
809
    {
810
        /** @var string[] $bundles */
811
        $bundles = $container->getParameter('kernel.bundles');
3✔
812

813
        if (!isset($bundles['SecurityBundle'])) {
3✔
814
            return;
×
815
        }
816

817
        $loader->load('security.xml');
3✔
818

819
        $loader->load('state/security.xml');
3✔
820

821
        if (interface_exists(ValidatorInterface::class)) {
3✔
822
            $loader->load('state/security_validator.xml');
3✔
823
        }
824

825
        if ($this->isConfigEnabled($container, $config['graphql'])) {
3✔
826
            $loader->load('graphql/security.xml');
3✔
827
        }
828
    }
829

830
    private function registerOpenApiConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
831
    {
832
        $container->setParameter('api_platform.openapi.termsOfService', $config['openapi']['termsOfService']);
3✔
833
        $container->setParameter('api_platform.openapi.contact.name', $config['openapi']['contact']['name']);
3✔
834
        $container->setParameter('api_platform.openapi.contact.url', $config['openapi']['contact']['url']);
3✔
835
        $container->setParameter('api_platform.openapi.contact.email', $config['openapi']['contact']['email']);
3✔
836
        $container->setParameter('api_platform.openapi.license.name', $config['openapi']['license']['name']);
3✔
837
        $container->setParameter('api_platform.openapi.license.url', $config['openapi']['license']['url']);
3✔
838
        $container->setParameter('api_platform.openapi.overrideResponses', $config['openapi']['overrideResponses']);
3✔
839

840
        $loader->load('json_schema.xml');
3✔
841
    }
842

843
    private function registerMakerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
844
    {
845
        if (!$this->isConfigEnabled($container, $config['maker'])) {
3✔
846
            return;
×
847
        }
848

849
        $loader->load('maker.xml');
3✔
850
    }
851

852
    private function registerArgumentResolverConfiguration(XmlFileLoader $loader): void
853
    {
854
        $loader->load('argument_resolver.xml');
3✔
855
    }
856

857
    private function registerLinkSecurityConfiguration(XmlFileLoader $loader, array $config): void
858
    {
859
        if ($config['enable_link_security']) {
3✔
860
            $loader->load('link_security.xml');
3✔
861
        }
862
    }
863
}
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

© 2026 Coveralls, Inc