vendor/api-platform/core/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php line 179

  1. <?php
  2. /*
  3.  * This file is part of the API Platform project.
  4.  *
  5.  * (c) Kévin Dunglas <dunglas@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace ApiPlatform\Symfony\Bundle\DependencyInjection;
  12. use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface;
  13. use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
  14. use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter as DoctrineMongoDbOdmAbstractFilter;
  15. use ApiPlatform\Doctrine\Odm\State\LinksHandlerInterface as OdmLinksHandlerInterface;
  16. use ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension;
  17. use ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension;
  18. use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface as DoctrineQueryCollectionExtensionInterface;
  19. use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
  20. use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter as DoctrineOrmAbstractFilter;
  21. use ApiPlatform\Doctrine\Orm\State\LinksHandlerInterface as OrmLinksHandlerInterface;
  22. use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface;
  23. use ApiPlatform\GraphQl\Error\ErrorHandlerInterface;
  24. use ApiPlatform\GraphQl\Resolver\MutationResolverInterface;
  25. use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface;
  26. use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
  27. use ApiPlatform\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface;
  28. use ApiPlatform\Metadata\ApiResource;
  29. use ApiPlatform\Metadata\FilterInterface;
  30. use ApiPlatform\Metadata\UrlGeneratorInterface;
  31. use ApiPlatform\Metadata\Util\Inflector;
  32. use ApiPlatform\State\ApiResource\Error;
  33. use ApiPlatform\State\ProcessorInterface;
  34. use ApiPlatform\State\ProviderInterface;
  35. use ApiPlatform\Symfony\GraphQl\Resolver\Factory\DataCollectorResolverFactory;
  36. use ApiPlatform\Symfony\Validator\Exception\ValidationException;
  37. use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface;
  38. use ApiPlatform\Symfony\Validator\ValidationGroupsGeneratorInterface;
  39. use Doctrine\Persistence\ManagerRegistry;
  40. use phpDocumentor\Reflection\DocBlockFactoryInterface;
  41. use PHPStan\PhpDocParser\Parser\PhpDocParser;
  42. use Ramsey\Uuid\Uuid;
  43. use Symfony\Component\Config\FileLocator;
  44. use Symfony\Component\Config\Resource\DirectoryResource;
  45. use Symfony\Component\DependencyInjection\ContainerBuilder;
  46. use Symfony\Component\DependencyInjection\ContainerInterface;
  47. use Symfony\Component\DependencyInjection\Definition;
  48. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  49. use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
  50. use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  51. use Symfony\Component\DependencyInjection\Reference;
  52. use Symfony\Component\Finder\Finder;
  53. use Symfony\Component\HttpClient\ScopingHttpClient;
  54. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  55. use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
  56. use Symfony\Component\Uid\AbstractUid;
  57. use Symfony\Component\Validator\Validator\ValidatorInterface;
  58. use Symfony\Component\Yaml\Yaml;
  59. use Twig\Environment;
  60. /**
  61.  * The extension of this bundle.
  62.  *
  63.  * @author Kévin Dunglas <dunglas@gmail.com>
  64.  */
  65. final class ApiPlatformExtension extends Extension implements PrependExtensionInterface
  66. {
  67.     /**
  68.      * {@inheritdoc}
  69.      */
  70.     public function prepend(ContainerBuilder $container): void
  71.     {
  72.         if (isset($container->getExtensions()['framework'])) {
  73.             $container->prependExtensionConfig('framework', [
  74.                 'serializer' => [
  75.                     'enabled' => true,
  76.                 ],
  77.             ]);
  78.             $container->prependExtensionConfig('framework', [
  79.                 'property_info' => [
  80.                     'enabled' => true,
  81.                 ],
  82.             ]);
  83.         }
  84.         if (isset($container->getExtensions()['lexik_jwt_authentication'])) {
  85.             $container->prependExtensionConfig('lexik_jwt_authentication', [
  86.                 'api_platform' => [
  87.                     'enabled' => true,
  88.                 ],
  89.             ]);
  90.         }
  91.     }
  92.     /**
  93.      * {@inheritdoc}
  94.      */
  95.     public function load(array $configsContainerBuilder $container): void
  96.     {
  97.         $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
  98.         $configuration = new Configuration();
  99.         $config $this->processConfiguration($configuration$configs);
  100.         if (!$config['formats']) {
  101.             trigger_deprecation('api-platform/core''3.2''Setting the "formats" section will be mandatory in API Platform 4.');
  102.             $config['formats'] = [
  103.                 'jsonld' => ['mime_types' => ['application/ld+json']],
  104.                 // Note that in API Platform 4 this will be removed as it was used for documentation only and are is now present in the docsFormats
  105.                 'json' => ['mime_types' => ['application/json']], // Swagger support
  106.             ];
  107.         }
  108.         $formats $this->getFormats($config['formats']);
  109.         $patchFormats $this->getFormats($config['patch_formats']);
  110.         $errorFormats $this->getFormats($config['error_formats']);
  111.         $docsFormats $this->getFormats($config['docs_formats']);
  112.         if (!isset($errorFormats['json'])) {
  113.             $errorFormats['json'] = ['application/problem+json''application/json'];
  114.         }
  115.         if (!isset($errorFormats['jsonproblem'])) {
  116.             $errorFormats['jsonproblem'] = ['application/problem+json'];
  117.         }
  118.         if ($this->isConfigEnabled($container$config['graphql']) && !isset($formats['json'])) {
  119.             trigger_deprecation('api-platform/core''3.2''Add the "json" format to the configuration to use GraphQL.');
  120.             $formats['json'] = ['application/json'];
  121.         }
  122.         // Backward Compatibility layer
  123.         if (isset($formats['jsonapi']) && !isset($patchFormats['jsonapi'])) {
  124.             $patchFormats['jsonapi'] = ['application/vnd.api+json'];
  125.         }
  126.         if (isset($docsFormats['json']) && !isset($docsFormats['jsonopenapi'])) {
  127.             trigger_deprecation('api-platform/core''3.2''The "json" format is too broad, use ["jsonopenapi" => ["application/vnd.openapi+json"]] instead.');
  128.             $docsFormats['jsonopenapi'] = ['application/vnd.openapi+json'];
  129.         }
  130.         $this->registerCommonConfiguration($container$config$loader$formats$patchFormats$errorFormats$docsFormats);
  131.         $this->registerMetadataConfiguration($container$config$loader);
  132.         $this->registerOAuthConfiguration($container$config);
  133.         $this->registerOpenApiConfiguration($container$config$loader);
  134.         $this->registerSwaggerConfiguration($container$config$loader);
  135.         $this->registerJsonApiConfiguration($formats$loader$config);
  136.         $this->registerJsonLdHydraConfiguration($container$formats$loader$config);
  137.         $this->registerJsonHalConfiguration($formats$loader);
  138.         $this->registerJsonProblemConfiguration($errorFormats$loader);
  139.         $this->registerGraphQlConfiguration($container$config$loader);
  140.         $this->registerCacheConfiguration($container);
  141.         $this->registerDoctrineOrmConfiguration($container$config$loader);
  142.         $this->registerDoctrineMongoDbOdmConfiguration($container$config$loader);
  143.         $this->registerHttpCacheConfiguration($container$config$loader);
  144.         $this->registerValidatorConfiguration($container$config$loader);
  145.         $this->registerDataCollectorConfiguration($container$config$loader);
  146.         $this->registerMercureConfiguration($container$config$loader);
  147.         $this->registerMessengerConfiguration($container$config$loader);
  148.         $this->registerElasticsearchConfiguration($container$config$loader);
  149.         $this->registerSecurityConfiguration($container$config$loader);
  150.         $this->registerMakerConfiguration($container$config$loader);
  151.         $this->registerArgumentResolverConfiguration($loader);
  152.         $container->registerForAutoconfiguration(FilterInterface::class)
  153.             ->addTag('api_platform.filter');
  154.         $container->registerForAutoconfiguration(ProviderInterface::class)
  155.             ->addTag('api_platform.state_provider');
  156.         $container->registerForAutoconfiguration(ProcessorInterface::class)
  157.             ->addTag('api_platform.state_processor');
  158.         if (!$container->has('api_platform.state.item_provider')) {
  159.             $container->setAlias('api_platform.state.item_provider''api_platform.state_provider.object');
  160.         }
  161.         $this->registerInflectorConfiguration($config);
  162.     }
  163.     private function registerCommonConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader, array $formats, array $patchFormats, array $errorFormats, array $docsFormats): void
  164.     {
  165.         $loader->load('symfony/events.xml');
  166.         $loader->load('symfony/controller.xml');
  167.         $loader->load('api.xml');
  168.         $loader->load('state.xml');
  169.         $loader->load('filter.xml');
  170.         if (class_exists(Uuid::class)) {
  171.             $loader->load('ramsey_uuid.xml');
  172.         }
  173.         if (class_exists(AbstractUid::class)) {
  174.             $loader->load('symfony/uid.xml');
  175.         }
  176.         // TODO: remove in 4.x
  177.         $container->setParameter('api_platform.event_listeners_backward_compatibility_layer'$config['event_listeners_backward_compatibility_layer']);
  178.         $loader->load('legacy/events.xml');
  179.         $container->setParameter('api_platform.enable_entrypoint'$config['enable_entrypoint']);
  180.         $container->setParameter('api_platform.enable_docs'$config['enable_docs']);
  181.         $container->setParameter('api_platform.keep_legacy_inflector'$config['keep_legacy_inflector']);
  182.         $container->setParameter('api_platform.title'$config['title']);
  183.         $container->setParameter('api_platform.description'$config['description']);
  184.         $container->setParameter('api_platform.version'$config['version']);
  185.         $container->setParameter('api_platform.show_webby'$config['show_webby']);
  186.         $container->setParameter('api_platform.url_generation_strategy'$config['defaults']['url_generation_strategy'] ?? UrlGeneratorInterface::ABS_PATH);
  187.         $container->setParameter('api_platform.exception_to_status'$config['exception_to_status']);
  188.         $container->setParameter('api_platform.formats'$formats);
  189.         $container->setParameter('api_platform.patch_formats'$patchFormats);
  190.         $container->setParameter('api_platform.error_formats'$errorFormats);
  191.         $container->setParameter('api_platform.docs_formats'$docsFormats);
  192.         $container->setParameter('api_platform.eager_loading.enabled'$this->isConfigEnabled($container$config['eager_loading']));
  193.         $container->setParameter('api_platform.eager_loading.max_joins'$config['eager_loading']['max_joins']);
  194.         $container->setParameter('api_platform.eager_loading.fetch_partial'$config['eager_loading']['fetch_partial']);
  195.         $container->setParameter('api_platform.eager_loading.force_eager'$config['eager_loading']['force_eager']);
  196.         $container->setParameter('api_platform.collection.exists_parameter_name'$config['collection']['exists_parameter_name']);
  197.         $container->setParameter('api_platform.collection.order'$config['collection']['order']);
  198.         $container->setParameter('api_platform.collection.order_parameter_name'$config['collection']['order_parameter_name']);
  199.         $container->setParameter('api_platform.collection.order_nulls_comparison'$config['collection']['order_nulls_comparison']);
  200.         $container->setParameter('api_platform.collection.pagination.enabled'$config['defaults']['pagination_enabled'] ?? true);
  201.         $container->setParameter('api_platform.collection.pagination.partial'$config['defaults']['pagination_partial'] ?? false);
  202.         $container->setParameter('api_platform.collection.pagination.client_enabled'$config['defaults']['pagination_client_enabled'] ?? false);
  203.         $container->setParameter('api_platform.collection.pagination.client_items_per_page'$config['defaults']['pagination_client_items_per_page'] ?? false);
  204.         $container->setParameter('api_platform.collection.pagination.client_partial'$config['defaults']['pagination_client_partial'] ?? false);
  205.         $container->setParameter('api_platform.collection.pagination.items_per_page'$config['defaults']['pagination_items_per_page'] ?? 30);
  206.         $container->setParameter('api_platform.collection.pagination.maximum_items_per_page'$config['defaults']['pagination_maximum_items_per_page'] ?? null);
  207.         $container->setParameter('api_platform.collection.pagination.page_parameter_name'$config['defaults']['pagination_page_parameter_name'] ?? $config['collection']['pagination']['page_parameter_name']);
  208.         $container->setParameter('api_platform.collection.pagination.enabled_parameter_name'$config['defaults']['pagination_enabled_parameter_name'] ?? $config['collection']['pagination']['enabled_parameter_name']);
  209.         $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']);
  210.         $container->setParameter('api_platform.collection.pagination.partial_parameter_name'$config['defaults']['pagination_partial_parameter_name'] ?? $config['collection']['pagination']['partial_parameter_name']);
  211.         $container->setParameter('api_platform.collection.pagination'$this->getPaginationDefaults($config['defaults'] ?? [], $config['collection']['pagination']));
  212.         $container->setParameter('api_platform.http_cache.etag'$config['defaults']['cache_headers']['etag'] ?? true);
  213.         $container->setParameter('api_platform.http_cache.max_age'$config['defaults']['cache_headers']['max_age'] ?? null);
  214.         $container->setParameter('api_platform.http_cache.shared_max_age'$config['defaults']['cache_headers']['shared_max_age'] ?? null);
  215.         $container->setParameter('api_platform.http_cache.vary'$config['defaults']['cache_headers']['vary'] ?? ['Accept']);
  216.         $container->setParameter('api_platform.http_cache.public'$config['defaults']['cache_headers']['public'] ?? $config['http_cache']['public']);
  217.         $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']);
  218.         $container->setParameter('api_platform.http_cache.invalidation.xkey.glue'$config['defaults']['cache_headers']['invalidation']['xkey']['glue'] ?? $config['http_cache']['invalidation']['xkey']['glue']);
  219.         $container->setAlias('api_platform.path_segment_name_generator'$config['path_segment_name_generator']);
  220.         if ($config['name_converter']) {
  221.             $container->setAlias('api_platform.name_converter'$config['name_converter']);
  222.         }
  223.         $container->setParameter('api_platform.asset_package'$config['asset_package']);
  224.         $container->setParameter('api_platform.defaults'$this->normalizeDefaults($config['defaults'] ?? []));
  225.         $container->setParameter('api_platform.rfc_7807_compliant_errors'$config['defaults']['extra_properties']['rfc_7807_compliant_errors'] ?? false);
  226.         if ($container->getParameter('kernel.debug')) {
  227.             $container->removeDefinition('api_platform.serializer.mapping.cache_class_metadata_factory');
  228.         }
  229.     }
  230.     /**
  231.      * This method will be removed in 3.0 when "defaults" will be the regular configuration path for the pagination.
  232.      */
  233.     private function getPaginationDefaults(array $defaults, array $collectionPaginationConfiguration): array
  234.     {
  235.         $paginationOptions = [];
  236.         foreach ($defaults as $key => $value) {
  237.             if (!str_starts_with($key'pagination_')) {
  238.                 continue;
  239.             }
  240.             $paginationOptions[str_replace('pagination_'''$key)] = $value;
  241.         }
  242.         return array_merge($collectionPaginationConfiguration$paginationOptions);
  243.     }
  244.     private function normalizeDefaults(array $defaults): array
  245.     {
  246.         $normalizedDefaults = ['extra_properties' => $defaults['extra_properties'] ?? []];
  247.         unset($defaults['extra_properties']);
  248.         $rc = new \ReflectionClass(ApiResource::class);
  249.         $publicProperties = [];
  250.         foreach ($rc->getConstructor()->getParameters() as $param) {
  251.             $publicProperties[$param->getName()] = true;
  252.         }
  253.         $nameConverter = new CamelCaseToSnakeCaseNameConverter();
  254.         foreach ($defaults as $option => $value) {
  255.             if (isset($publicProperties[$nameConverter->denormalize($option)])) {
  256.                 $normalizedDefaults[$option] = $value;
  257.                 continue;
  258.             }
  259.             $normalizedDefaults['extra_properties'][$option] = $value;
  260.         }
  261.         return $normalizedDefaults;
  262.     }
  263.     private function registerMetadataConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  264.     {
  265.         [$xmlResources$yamlResources] = $this->getResourcesToWatch($container$config);
  266.         $container->setParameter('api_platform.class_name_resources'$this->getClassNameResources());
  267.         $loader->load('metadata/resource_name.xml');
  268.         $loader->load('metadata/property_name.xml');
  269.         if (!empty($config['resource_class_directories'])) {
  270.             $container->setParameter('api_platform.resource_class_directories'array_merge(
  271.                 $config['resource_class_directories'],
  272.                 $container->getParameter('api_platform.resource_class_directories')
  273.             ));
  274.         }
  275.         // V3 metadata
  276.         $loader->load('metadata/xml.xml');
  277.         $loader->load('metadata/links.xml');
  278.         $loader->load('metadata/property.xml');
  279.         $loader->load('metadata/resource.xml');
  280.         $loader->load('metadata/operation.xml');
  281.         $container->getDefinition('api_platform.metadata.resource_extractor.xml')->replaceArgument(0$xmlResources);
  282.         $container->getDefinition('api_platform.metadata.property_extractor.xml')->replaceArgument(0$xmlResources);
  283.         if (class_exists(PhpDocParser::class) || interface_exists(DocBlockFactoryInterface::class)) {
  284.             $loader->load('metadata/php_doc.xml');
  285.         }
  286.         if (class_exists(Yaml::class)) {
  287.             $loader->load('metadata/yaml.xml');
  288.             $container->getDefinition('api_platform.metadata.resource_extractor.yaml')->replaceArgument(0$yamlResources);
  289.             $container->getDefinition('api_platform.metadata.property_extractor.yaml')->replaceArgument(0$yamlResources);
  290.         }
  291.     }
  292.     private function getClassNameResources(): array
  293.     {
  294.         return [
  295.             Error::class,
  296.             ValidationException::class,
  297.         ];
  298.     }
  299.     private function getBundlesResourcesPaths(ContainerBuilder $container, array $config): array
  300.     {
  301.         $bundlesResourcesPaths = [];
  302.         foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
  303.             $dirname $bundle['path'];
  304.             $paths = [
  305.                 "$dirname/ApiResource",
  306.                 "$dirname/src/ApiResource",
  307.             ];
  308.             foreach (['.yaml''.yml''.xml'''] as $extension) {
  309.                 $paths[] = "$dirname/Resources/config/api_resources$extension";
  310.                 $paths[] = "$dirname/config/api_resources$extension";
  311.             }
  312.             if ($this->isConfigEnabled($container$config['doctrine'])) {
  313.                 $paths[] = "$dirname/Entity";
  314.                 $paths[] = "$dirname/src/Entity";
  315.             }
  316.             if ($this->isConfigEnabled($container$config['doctrine_mongodb_odm'])) {
  317.                 $paths[] = "$dirname/Document";
  318.                 $paths[] = "$dirname/src/Document";
  319.             }
  320.             foreach ($paths as $path) {
  321.                 if ($container->fileExists($pathfalse)) {
  322.                     $bundlesResourcesPaths[] = $path;
  323.                 }
  324.             }
  325.         }
  326.         return $bundlesResourcesPaths;
  327.     }
  328.     private function getResourcesToWatch(ContainerBuilder $container, array $config): array
  329.     {
  330.         $paths array_unique(array_merge($this->getBundlesResourcesPaths($container$config), $config['mapping']['paths']));
  331.         if (!$config['mapping']['paths']) {
  332.             $projectDir $container->getParameter('kernel.project_dir');
  333.             foreach (["$projectDir/config/api_platform""$projectDir/src/ApiResource"] as $dir) {
  334.                 if (is_dir($dir)) {
  335.                     $paths[] = $dir;
  336.                 }
  337.             }
  338.             if ($this->isConfigEnabled($container$config['doctrine']) && is_dir($doctrinePath "$projectDir/src/Entity")) {
  339.                 $paths[] = $doctrinePath;
  340.             }
  341.             if ($this->isConfigEnabled($container$config['doctrine_mongodb_odm']) && is_dir($documentPath "$projectDir/src/Document")) {
  342.                 $paths[] = $documentPath;
  343.             }
  344.         }
  345.         $resources = ['yml' => [], 'xml' => [], 'dir' => []];
  346.         foreach ($paths as $path) {
  347.             if (is_dir($path)) {
  348.                 foreach (Finder::create()->followLinks()->files()->in($path)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) {
  349.                     $resources['yaml' === ($extension $file->getExtension()) ? 'yml' $extension][] = $file->getRealPath();
  350.                 }
  351.                 $resources['dir'][] = $path;
  352.                 $container->addResource(new DirectoryResource($path'/\.(xml|ya?ml|php)$/'));
  353.                 continue;
  354.             }
  355.             if ($container->fileExists($pathfalse)) {
  356.                 if (!preg_match('/\.(xml|ya?ml)$/', (string) $path$matches)) {
  357.                     throw new RuntimeException(sprintf('Unsupported mapping type in "%s", supported types are XML & YAML.'$path));
  358.                 }
  359.                 $resources['yaml' === $matches[1] ? 'yml' $matches[1]][] = $path;
  360.                 continue;
  361.             }
  362.             throw new RuntimeException(sprintf('Could not open file or directory "%s".'$path));
  363.         }
  364.         $container->setParameter('api_platform.resource_class_directories'$resources['dir']);
  365.         return [$resources['xml'], $resources['yml']];
  366.     }
  367.     private function registerOAuthConfiguration(ContainerBuilder $container, array $config): void
  368.     {
  369.         if (!$config['oauth']) {
  370.             return;
  371.         }
  372.         $container->setParameter('api_platform.oauth.enabled'$this->isConfigEnabled($container$config['oauth']));
  373.         $container->setParameter('api_platform.oauth.clientId'$config['oauth']['clientId']);
  374.         $container->setParameter('api_platform.oauth.clientSecret'$config['oauth']['clientSecret']);
  375.         $container->setParameter('api_platform.oauth.type'$config['oauth']['type']);
  376.         $container->setParameter('api_platform.oauth.flow'$config['oauth']['flow']);
  377.         $container->setParameter('api_platform.oauth.tokenUrl'$config['oauth']['tokenUrl']);
  378.         $container->setParameter('api_platform.oauth.authorizationUrl'$config['oauth']['authorizationUrl']);
  379.         $container->setParameter('api_platform.oauth.refreshUrl'$config['oauth']['refreshUrl']);
  380.         $container->setParameter('api_platform.oauth.scopes'$config['oauth']['scopes']);
  381.         $container->setParameter('api_platform.oauth.pkce'$config['oauth']['pkce']);
  382.         if ($container->hasDefinition('api_platform.swagger_ui.action')) {
  383.             $container->getDefinition('api_platform.swagger_ui.action')->setArgument(10$config['oauth']['pkce']);
  384.         }
  385.     }
  386.     /**
  387.      * Registers the Swagger, ReDoc and Swagger UI configuration.
  388.      */
  389.     private function registerSwaggerConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  390.     {
  391.         foreach (array_keys($config['swagger']['api_keys']) as $keyName) {
  392.             if (!preg_match('/^[a-zA-Z0-9._-]+$/'$keyName)) {
  393.                 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));
  394.             }
  395.         }
  396.         $container->setParameter('api_platform.swagger.versions'$config['swagger']['versions']);
  397.         if (!$config['enable_swagger'] && $config['enable_swagger_ui']) {
  398.             throw new RuntimeException('You can not enable the Swagger UI without enabling Swagger, fix this by enabling swagger via the configuration "enable_swagger: true".');
  399.         }
  400.         if (!$config['enable_swagger']) {
  401.             return;
  402.         }
  403.         $loader->load('openapi.xml');
  404.         $loader->load('swagger_ui.xml');
  405.         $loader->load('legacy/swagger_ui.xml');
  406.         if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) {
  407.             // Remove the listener but keep the controller to allow customizing the path of the UI
  408.             $container->removeDefinition('api_platform.swagger.listener.ui');
  409.         }
  410.         $container->setParameter('api_platform.enable_swagger_ui'$config['enable_swagger_ui']);
  411.         $container->setParameter('api_platform.enable_re_doc'$config['enable_re_doc']);
  412.         $container->setParameter('api_platform.swagger.api_keys'$config['swagger']['api_keys']);
  413.         if ($config['openapi']['swagger_ui_extra_configuration'] && $config['swagger']['swagger_ui_extra_configuration']) {
  414.             throw new RuntimeException('You can not set "swagger_ui_extra_configuration" twice - in "openapi" and "swagger" section.');
  415.         }
  416.         $container->setParameter('api_platform.swagger_ui.extra_configuration'$config['openapi']['swagger_ui_extra_configuration'] ?: $config['swagger']['swagger_ui_extra_configuration']);
  417.     }
  418.     private function registerJsonApiConfiguration(array $formatsXmlFileLoader $loader, array $config): void
  419.     {
  420.         if (!isset($formats['jsonapi'])) {
  421.             return;
  422.         }
  423.         $loader->load('jsonapi.xml');
  424.         $loader->load('legacy/jsonapi.xml');
  425.     }
  426.     private function registerJsonLdHydraConfiguration(ContainerBuilder $container, array $formatsXmlFileLoader $loader, array $config): void
  427.     {
  428.         if (!isset($formats['jsonld'])) {
  429.             return;
  430.         }
  431.         $loader->load('jsonld.xml');
  432.         $loader->load('legacy/hydra.xml');
  433.         $loader->load('hydra.xml');
  434.         if (!$container->has('api_platform.json_schema.schema_factory')) {
  435.             $container->removeDefinition('api_platform.hydra.json_schema.schema_factory');
  436.         }
  437.         if (!$config['enable_docs']) {
  438.             $container->removeDefinition('api_platform.hydra.listener.response.add_link_header');
  439.         }
  440.     }
  441.     private function registerJsonHalConfiguration(array $formatsXmlFileLoader $loader): void
  442.     {
  443.         if (!isset($formats['jsonhal'])) {
  444.             return;
  445.         }
  446.         $loader->load('hal.xml');
  447.     }
  448.     private function registerJsonProblemConfiguration(array $errorFormatsXmlFileLoader $loader): void
  449.     {
  450.         if (!isset($errorFormats['jsonproblem'])) {
  451.             return;
  452.         }
  453.         $loader->load('problem.xml');
  454.     }
  455.     private function registerGraphQlConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  456.     {
  457.         $enabled $this->isConfigEnabled($container$config['graphql']);
  458.         $graphqlIntrospectionEnabled $enabled && $this->isConfigEnabled($container$config['graphql']['introspection']);
  459.         $graphiqlEnabled $enabled && $this->isConfigEnabled($container$config['graphql']['graphiql']);
  460.         $graphqlPlayGroundEnabled $enabled && $this->isConfigEnabled($container$config['graphql']['graphql_playground']);
  461.         if ($graphqlPlayGroundEnabled) {
  462.             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.');
  463.         }
  464.         $container->setParameter('api_platform.graphql.enabled'$enabled);
  465.         $container->setParameter('api_platform.graphql.introspection.enabled'$graphqlIntrospectionEnabled);
  466.         $container->setParameter('api_platform.graphql.graphiql.enabled'$graphiqlEnabled);
  467.         $container->setParameter('api_platform.graphql.graphql_playground.enabled'$graphqlPlayGroundEnabled);
  468.         $container->setParameter('api_platform.graphql.collection.pagination'$config['graphql']['collection']['pagination']);
  469.         if (!$enabled) {
  470.             return;
  471.         }
  472.         $container->setParameter('api_platform.graphql.default_ide'$config['graphql']['default_ide']);
  473.         $container->setParameter('api_platform.graphql.nesting_separator'$config['graphql']['nesting_separator']);
  474.         $loader->load('graphql.xml');
  475.         // @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
  476.         if (!class_exists(Environment::class) || !isset($container->getParameter('kernel.bundles')['TwigBundle'])) {
  477.             if ($graphiqlEnabled || $graphqlPlayGroundEnabled) {
  478.                 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')));
  479.             }
  480.             $container->removeDefinition('api_platform.graphql.action.graphiql');
  481.             $container->removeDefinition('api_platform.graphql.action.graphql_playground');
  482.         }
  483.         $container->registerForAutoconfiguration(QueryItemResolverInterface::class)
  484.             ->addTag('api_platform.graphql.resolver');
  485.         $container->registerForAutoconfiguration(QueryCollectionResolverInterface::class)
  486.             ->addTag('api_platform.graphql.resolver');
  487.         $container->registerForAutoconfiguration(MutationResolverInterface::class)
  488.             ->addTag('api_platform.graphql.resolver');
  489.         $container->registerForAutoconfiguration(GraphQlTypeInterface::class)
  490.             ->addTag('api_platform.graphql.type');
  491.         $container->registerForAutoconfiguration(ErrorHandlerInterface::class)
  492.             ->addTag('api_platform.graphql.error_handler');
  493.         /* TODO: remove these in 4.x only one resolver factory is used and we're using providers/processors */
  494.         if ($config['event_listeners_backward_compatibility_layer'] ?? true) {
  495.             // @TODO: API Platform 3.3 trigger_deprecation('api-platform/core', '3.3', 'In API Platform 4 only one factory "api_platform.graphql.resolver.factory.item" will remain. Stages are deprecated in favor of using a provider/processor.');
  496.             // + deprecate every service from legacy/graphql.xml
  497.             $loader->load('legacy/graphql.xml');
  498.             if (!$container->getParameter('kernel.debug')) {
  499.                 return;
  500.             }
  501.             $requestStack = new Reference('request_stack'ContainerInterface::NULL_ON_INVALID_REFERENCE);
  502.             $collectionDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  503.                 ->setDecoratedService('api_platform.graphql.resolver.factory.collection')
  504.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.collection.inner'), $requestStack]);
  505.             $itemDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  506.                 ->setDecoratedService('api_platform.graphql.resolver.factory.item')
  507.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.item.inner'), $requestStack]);
  508.             $itemMutationDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  509.                 ->setDecoratedService('api_platform.graphql.resolver.factory.item_mutation')
  510.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.item_mutation.inner'), $requestStack]);
  511.             $itemSubscriptionDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  512.                 ->setDecoratedService('api_platform.graphql.resolver.factory.item_subscription')
  513.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.item_subscription.inner'), $requestStack]);
  514.             $container->addDefinitions([
  515.                 'api_platform.graphql.data_collector.resolver.factory.collection' => $collectionDataCollectorResolverFactory,
  516.                 'api_platform.graphql.data_collector.resolver.factory.item' => $itemDataCollectorResolverFactory,
  517.                 'api_platform.graphql.data_collector.resolver.factory.item_mutation' => $itemMutationDataCollectorResolverFactory,
  518.                 'api_platform.graphql.data_collector.resolver.factory.item_subscription' => $itemSubscriptionDataCollectorResolverFactory,
  519.             ]);
  520.         }
  521.     }
  522.     private function registerCacheConfiguration(ContainerBuilder $container): void
  523.     {
  524.         if (!$container->hasParameter('kernel.debug') || !$container->getParameter('kernel.debug')) {
  525.             $container->removeDefinition('api_platform.cache_warmer.cache_pool_clearer');
  526.         }
  527.     }
  528.     private function registerDoctrineOrmConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  529.     {
  530.         if (!$this->isConfigEnabled($container$config['doctrine'])) {
  531.             return;
  532.         }
  533.         // For older versions of doctrine bridge this allows autoconfiguration for filters
  534.         if (!$container->has(ManagerRegistry::class)) {
  535.             $container->setAlias(ManagerRegistry::class, 'doctrine');
  536.         }
  537.         $container->registerForAutoconfiguration(QueryItemExtensionInterface::class)
  538.             ->addTag('api_platform.doctrine.orm.query_extension.item');
  539.         $container->registerForAutoconfiguration(DoctrineQueryCollectionExtensionInterface::class)
  540.             ->addTag('api_platform.doctrine.orm.query_extension.collection');
  541.         $container->registerForAutoconfiguration(DoctrineOrmAbstractFilter::class);
  542.         $container->registerForAutoconfiguration(OrmLinksHandlerInterface::class)
  543.             ->addTag('api_platform.doctrine.orm.links_handler');
  544.         $loader->load('doctrine_orm.xml');
  545.         if ($this->isConfigEnabled($container$config['eager_loading'])) {
  546.             return;
  547.         }
  548.         $container->removeAlias(EagerLoadingExtension::class);
  549.         $container->removeDefinition('api_platform.doctrine.orm.query_extension.eager_loading');
  550.         $container->removeAlias(FilterEagerLoadingExtension::class);
  551.         $container->removeDefinition('api_platform.doctrine.orm.query_extension.filter_eager_loading');
  552.     }
  553.     private function registerDoctrineMongoDbOdmConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  554.     {
  555.         if (!$this->isConfigEnabled($container$config['doctrine_mongodb_odm'])) {
  556.             return;
  557.         }
  558.         $container->registerForAutoconfiguration(AggregationItemExtensionInterface::class)
  559.             ->addTag('api_platform.doctrine_mongodb.odm.aggregation_extension.item');
  560.         $container->registerForAutoconfiguration(AggregationCollectionExtensionInterface::class)
  561.             ->addTag('api_platform.doctrine_mongodb.odm.aggregation_extension.collection');
  562.         $container->registerForAutoconfiguration(DoctrineMongoDbOdmAbstractFilter::class)
  563.             ->setBindings(['$managerRegistry' => new Reference('doctrine_mongodb')]);
  564.         $container->registerForAutoconfiguration(OdmLinksHandlerInterface::class)
  565.             ->addTag('api_platform.doctrine.odm.links_handler');
  566.         $loader->load('doctrine_mongodb_odm.xml');
  567.     }
  568.     private function registerHttpCacheConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  569.     {
  570.         $loader->load('http_cache.xml');
  571.         $loader->load('legacy/http_cache.xml');
  572.         if (!$this->isConfigEnabled($container$config['http_cache']['invalidation'])) {
  573.             return;
  574.         }
  575.         if ($this->isConfigEnabled($container$config['doctrine'])) {
  576.             $loader->load('doctrine_orm_http_cache_purger.xml');
  577.         }
  578.         $loader->load('http_cache_purger.xml');
  579.         $loader->load('legacy/http_cache_purger.xml');
  580.         foreach ($config['http_cache']['invalidation']['scoped_clients'] as $client) {
  581.             $definition $container->getDefinition($client);
  582.             $definition->addTag('api_platform.http_cache.http_client');
  583.         }
  584.         if (!($urls $config['http_cache']['invalidation']['urls'])) {
  585.             $urls $config['http_cache']['invalidation']['varnish_urls'];
  586.         }
  587.         foreach ($urls as $key => $url) {
  588.             $definition = new Definition(ScopingHttpClient::class, [new Reference('http_client'), $url, ['base_uri' => $url] + $config['http_cache']['invalidation']['request_options']]);
  589.             $definition->setFactory([ScopingHttpClient::class, 'forBaseUri']);
  590.             $definition->addTag('api_platform.http_cache.http_client');
  591.             $container->setDefinition('api_platform.invalidation_http_client.'.$key$definition);
  592.         }
  593.         $serviceName $config['http_cache']['invalidation']['purger'];
  594.         if (!$container->hasDefinition('api_platform.http_cache.purger')) {
  595.             $container->setAlias('api_platform.http_cache.purger'$serviceName);
  596.         }
  597.     }
  598.     /**
  599.      * Normalizes the format from config to the one accepted by Symfony HttpFoundation.
  600.      */
  601.     private function getFormats(array $configFormats): array
  602.     {
  603.         $formats = [];
  604.         foreach ($configFormats as $format => $value) {
  605.             foreach ($value['mime_types'] as $mimeType) {
  606.                 $formats[$format][] = $mimeType;
  607.             }
  608.         }
  609.         return $formats;
  610.     }
  611.     private function registerValidatorConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  612.     {
  613.         if (interface_exists(ValidatorInterface::class)) {
  614.             $loader->load('metadata/validator.xml');
  615.             $loader->load('symfony/validator.xml');
  616.             if ($this->isConfigEnabled($container$config['graphql'])) {
  617.                 $loader->load('graphql/validator.xml');
  618.             }
  619.             $container->registerForAutoconfiguration(ValidationGroupsGeneratorInterface::class)
  620.                 ->addTag('api_platform.validation_groups_generator');
  621.             $container->registerForAutoconfiguration(PropertySchemaRestrictionMetadataInterface::class)
  622.                 ->addTag('api_platform.metadata.property_schema_restriction');
  623.             $loader->load('legacy/validator.xml');
  624.         }
  625.         if (!$config['validator']) {
  626.             return;
  627.         }
  628.         $container->setParameter('api_platform.validator.serialize_payload_fields'$config['validator']['serialize_payload_fields']);
  629.         $container->setParameter('api_platform.validator.query_parameter_validation'$config['validator']['query_parameter_validation']);
  630.         if (!$config['validator']['query_parameter_validation']) {
  631.             $container->removeDefinition('api_platform.listener.view.validate_query_parameters');
  632.             $container->removeDefinition('api_platform.validator.query_parameter_validator');
  633.         }
  634.     }
  635.     private function registerDataCollectorConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  636.     {
  637.         if (!$config['enable_profiler']) {
  638.             return;
  639.         }
  640.         $loader->load('data_collector.xml');
  641.         if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) {
  642.             $loader->load('debug.xml');
  643.         }
  644.     }
  645.     private function registerMercureConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  646.     {
  647.         if (!$this->isConfigEnabled($container$config['mercure'])) {
  648.             return;
  649.         }
  650.         $container->setParameter('api_platform.mercure.include_type'$config['mercure']['include_type']);
  651.         $loader->load('legacy/mercure.xml');
  652.         $loader->load('mercure.xml');
  653.         if ($this->isConfigEnabled($container$config['doctrine'])) {
  654.             $loader->load('doctrine_orm_mercure_publisher.xml');
  655.         }
  656.         if ($this->isConfigEnabled($container$config['doctrine_mongodb_odm'])) {
  657.             $loader->load('doctrine_odm_mercure_publisher.xml');
  658.         }
  659.         if ($this->isConfigEnabled($container$config['graphql'])) {
  660.             $loader->load('graphql_mercure.xml');
  661.         }
  662.     }
  663.     private function registerMessengerConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  664.     {
  665.         if (!$this->isConfigEnabled($container$config['messenger'])) {
  666.             return;
  667.         }
  668.         $loader->load('messenger.xml');
  669.     }
  670.     private function registerElasticsearchConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  671.     {
  672.         $enabled $this->isConfigEnabled($container$config['elasticsearch']);
  673.         $container->setParameter('api_platform.elasticsearch.enabled'$enabled);
  674.         if (!$enabled) {
  675.             return;
  676.         }
  677.         $clientClass class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\Client::class;
  678.         $clientDefinition = new Definition($clientClass);
  679.         $container->setDefinition('api_platform.elasticsearch.client'$clientDefinition);
  680.         $container->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class)
  681.             ->addTag('api_platform.elasticsearch.request_body_search_extension.collection');
  682.         $container->setParameter('api_platform.elasticsearch.hosts'$config['elasticsearch']['hosts']);
  683.         $loader->load('elasticsearch.xml');
  684.         // @phpstan-ignore-next-line
  685.         if (\Elasticsearch\Client::class === $clientClass) {
  686.             $loader->load('legacy/elasticsearch.xml');
  687.             $container->setParameter('api_platform.elasticsearch.mapping'$config['elasticsearch']['mapping']);
  688.             $container->setDefinition('api_platform.elasticsearch.client_for_metadata'$clientDefinition);
  689.         }
  690.     }
  691.     private function registerSecurityConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  692.     {
  693.         /** @var string[] $bundles */
  694.         $bundles $container->getParameter('kernel.bundles');
  695.         if (!isset($bundles['SecurityBundle'])) {
  696.             return;
  697.         }
  698.         $loader->load('security.xml');
  699.         $loader->load('legacy/security.xml');
  700.         if (interface_exists(ValidatorInterface::class)) {
  701.             $loader->load('symfony/security_validator.xml');
  702.         }
  703.         if ($this->isConfigEnabled($container$config['graphql'])) {
  704.             $loader->load('graphql/security.xml');
  705.         }
  706.     }
  707.     private function registerOpenApiConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  708.     {
  709.         $container->setParameter('api_platform.openapi.termsOfService'$config['openapi']['termsOfService']);
  710.         $container->setParameter('api_platform.openapi.contact.name'$config['openapi']['contact']['name']);
  711.         $container->setParameter('api_platform.openapi.contact.url'$config['openapi']['contact']['url']);
  712.         $container->setParameter('api_platform.openapi.contact.email'$config['openapi']['contact']['email']);
  713.         $container->setParameter('api_platform.openapi.license.name'$config['openapi']['license']['name']);
  714.         $container->setParameter('api_platform.openapi.license.url'$config['openapi']['license']['url']);
  715.         $loader->load('json_schema.xml');
  716.     }
  717.     private function registerMakerConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  718.     {
  719.         if (!$this->isConfigEnabled($container$config['maker'])) {
  720.             return;
  721.         }
  722.         $loader->load('maker.xml');
  723.     }
  724.     private function registerArgumentResolverConfiguration(XmlFileLoader $loader): void
  725.     {
  726.         $loader->load('argument_resolver.xml');
  727.     }
  728.     private function registerInflectorConfiguration(array $config): void
  729.     {
  730.         if ($config['keep_legacy_inflector']) {
  731.             Inflector::keepLegacyInflector(true);
  732.             trigger_deprecation('api-platform/core''3.2''Using doctrine/inflector is deprecated since API Platform 3.2 and will be removed in API Platform 4. Use symfony/string instead. Run "composer require symfony/string" and set "keep_legacy_inflector" to false in config.');
  733.         } else {
  734.             Inflector::keepLegacyInflector(false);
  735.         }
  736.     }
  737. }