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

  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\ApiResource\Error;
  13. use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface;
  14. use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
  15. use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter as DoctrineMongoDbOdmAbstractFilter;
  16. use ApiPlatform\Doctrine\Odm\State\LinksHandlerInterface as OdmLinksHandlerInterface;
  17. use ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension;
  18. use ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension;
  19. use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface as DoctrineQueryCollectionExtensionInterface;
  20. use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
  21. use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter as DoctrineOrmAbstractFilter;
  22. use ApiPlatform\Doctrine\Orm\State\LinksHandlerInterface as OrmLinksHandlerInterface;
  23. use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface;
  24. use ApiPlatform\GraphQl\Error\ErrorHandlerInterface;
  25. use ApiPlatform\GraphQl\Resolver\MutationResolverInterface;
  26. use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface;
  27. use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
  28. use ApiPlatform\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface;
  29. use ApiPlatform\Metadata\ApiResource;
  30. use ApiPlatform\Metadata\FilterInterface;
  31. use ApiPlatform\Metadata\UrlGeneratorInterface;
  32. use ApiPlatform\Metadata\Util\Inflector;
  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['html']) && $config['enable_swagger'] && $config['enable_swagger_ui']) {
  113.             $errorFormats['html'] = ['text/html'];
  114.         }
  115.         if (!isset($errorFormats['json'])) {
  116.             $errorFormats['json'] = ['application/problem+json''application/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.         if ($container->getParameter('kernel.debug')) {
  226.             $container->removeDefinition('api_platform.serializer.mapping.cache_class_metadata_factory');
  227.         }
  228.     }
  229.     /**
  230.      * This method will be removed in 3.0 when "defaults" will be the regular configuration path for the pagination.
  231.      */
  232.     private function getPaginationDefaults(array $defaults, array $collectionPaginationConfiguration): array
  233.     {
  234.         $paginationOptions = [];
  235.         foreach ($defaults as $key => $value) {
  236.             if (!str_starts_with($key'pagination_')) {
  237.                 continue;
  238.             }
  239.             $paginationOptions[str_replace('pagination_'''$key)] = $value;
  240.         }
  241.         return array_merge($collectionPaginationConfiguration$paginationOptions);
  242.     }
  243.     private function normalizeDefaults(array $defaults): array
  244.     {
  245.         $normalizedDefaults = ['extra_properties' => $defaults['extra_properties'] ?? []];
  246.         unset($defaults['extra_properties']);
  247.         $rc = new \ReflectionClass(ApiResource::class);
  248.         $publicProperties = [];
  249.         foreach ($rc->getConstructor()->getParameters() as $param) {
  250.             $publicProperties[$param->getName()] = true;
  251.         }
  252.         $nameConverter = new CamelCaseToSnakeCaseNameConverter();
  253.         foreach ($defaults as $option => $value) {
  254.             if (isset($publicProperties[$nameConverter->denormalize($option)])) {
  255.                 $normalizedDefaults[$option] = $value;
  256.                 continue;
  257.             }
  258.             $normalizedDefaults['extra_properties'][$option] = $value;
  259.         }
  260.         return $normalizedDefaults;
  261.     }
  262.     private function registerMetadataConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  263.     {
  264.         [$xmlResources$yamlResources] = $this->getResourcesToWatch($container$config);
  265.         $container->setParameter('api_platform.class_name_resources'$this->getClassNameResources());
  266.         $loader->load('metadata/resource_name.xml');
  267.         $loader->load('metadata/property_name.xml');
  268.         if (!empty($config['resource_class_directories'])) {
  269.             $container->setParameter('api_platform.resource_class_directories'array_merge(
  270.                 $config['resource_class_directories'],
  271.                 $container->getParameter('api_platform.resource_class_directories')
  272.             ));
  273.         }
  274.         // V3 metadata
  275.         $loader->load('metadata/xml.xml');
  276.         $loader->load('metadata/links.xml');
  277.         $loader->load('metadata/property.xml');
  278.         $loader->load('metadata/resource.xml');
  279.         $loader->load('metadata/operation.xml');
  280.         $container->getDefinition('api_platform.metadata.resource_extractor.xml')->replaceArgument(0$xmlResources);
  281.         $container->getDefinition('api_platform.metadata.property_extractor.xml')->replaceArgument(0$xmlResources);
  282.         if (class_exists(PhpDocParser::class) || interface_exists(DocBlockFactoryInterface::class)) {
  283.             $loader->load('metadata/php_doc.xml');
  284.         }
  285.         if (class_exists(Yaml::class)) {
  286.             $loader->load('metadata/yaml.xml');
  287.             $container->getDefinition('api_platform.metadata.resource_extractor.yaml')->replaceArgument(0$yamlResources);
  288.             $container->getDefinition('api_platform.metadata.property_extractor.yaml')->replaceArgument(0$yamlResources);
  289.         }
  290.     }
  291.     private function getClassNameResources(): array
  292.     {
  293.         return [
  294.             Error::class,
  295.             ValidationException::class,
  296.         ];
  297.     }
  298.     private function getBundlesResourcesPaths(ContainerBuilder $container, array $config): array
  299.     {
  300.         $bundlesResourcesPaths = [];
  301.         foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
  302.             $dirname $bundle['path'];
  303.             $paths = [
  304.                 "$dirname/ApiResource",
  305.                 "$dirname/src/ApiResource",
  306.             ];
  307.             foreach (['.yaml''.yml''.xml'''] as $extension) {
  308.                 $paths[] = "$dirname/Resources/config/api_resources$extension";
  309.                 $paths[] = "$dirname/config/api_resources$extension";
  310.             }
  311.             if ($this->isConfigEnabled($container$config['doctrine'])) {
  312.                 $paths[] = "$dirname/Entity";
  313.                 $paths[] = "$dirname/src/Entity";
  314.             }
  315.             if ($this->isConfigEnabled($container$config['doctrine_mongodb_odm'])) {
  316.                 $paths[] = "$dirname/Document";
  317.                 $paths[] = "$dirname/src/Document";
  318.             }
  319.             foreach ($paths as $path) {
  320.                 if ($container->fileExists($pathfalse)) {
  321.                     $bundlesResourcesPaths[] = $path;
  322.                 }
  323.             }
  324.         }
  325.         return $bundlesResourcesPaths;
  326.     }
  327.     private function getResourcesToWatch(ContainerBuilder $container, array $config): array
  328.     {
  329.         $paths array_unique(array_merge($this->getBundlesResourcesPaths($container$config), $config['mapping']['paths']));
  330.         if (!$config['mapping']['paths']) {
  331.             $projectDir $container->getParameter('kernel.project_dir');
  332.             foreach (["$projectDir/config/api_platform""$projectDir/src/ApiResource"] as $dir) {
  333.                 if (is_dir($dir)) {
  334.                     $paths[] = $dir;
  335.                 }
  336.             }
  337.             if ($this->isConfigEnabled($container$config['doctrine']) && is_dir($doctrinePath "$projectDir/src/Entity")) {
  338.                 $paths[] = $doctrinePath;
  339.             }
  340.             if ($this->isConfigEnabled($container$config['doctrine_mongodb_odm']) && is_dir($documentPath "$projectDir/src/Document")) {
  341.                 $paths[] = $documentPath;
  342.             }
  343.         }
  344.         $resources = ['yml' => [], 'xml' => [], 'dir' => []];
  345.         foreach ($paths as $path) {
  346.             if (is_dir($path)) {
  347.                 foreach (Finder::create()->followLinks()->files()->in($path)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) {
  348.                     $resources['yaml' === ($extension $file->getExtension()) ? 'yml' $extension][] = $file->getRealPath();
  349.                 }
  350.                 $resources['dir'][] = $path;
  351.                 $container->addResource(new DirectoryResource($path'/\.(xml|ya?ml|php)$/'));
  352.                 continue;
  353.             }
  354.             if ($container->fileExists($pathfalse)) {
  355.                 if (!preg_match('/\.(xml|ya?ml)$/', (string) $path$matches)) {
  356.                     throw new RuntimeException(sprintf('Unsupported mapping type in "%s", supported types are XML & YAML.'$path));
  357.                 }
  358.                 $resources['yaml' === $matches[1] ? 'yml' $matches[1]][] = $path;
  359.                 continue;
  360.             }
  361.             throw new RuntimeException(sprintf('Could not open file or directory "%s".'$path));
  362.         }
  363.         $container->setParameter('api_platform.resource_class_directories'$resources['dir']);
  364.         return [$resources['xml'], $resources['yml']];
  365.     }
  366.     private function registerOAuthConfiguration(ContainerBuilder $container, array $config): void
  367.     {
  368.         if (!$config['oauth']) {
  369.             return;
  370.         }
  371.         $container->setParameter('api_platform.oauth.enabled'$this->isConfigEnabled($container$config['oauth']));
  372.         $container->setParameter('api_platform.oauth.clientId'$config['oauth']['clientId']);
  373.         $container->setParameter('api_platform.oauth.clientSecret'$config['oauth']['clientSecret']);
  374.         $container->setParameter('api_platform.oauth.type'$config['oauth']['type']);
  375.         $container->setParameter('api_platform.oauth.flow'$config['oauth']['flow']);
  376.         $container->setParameter('api_platform.oauth.tokenUrl'$config['oauth']['tokenUrl']);
  377.         $container->setParameter('api_platform.oauth.authorizationUrl'$config['oauth']['authorizationUrl']);
  378.         $container->setParameter('api_platform.oauth.refreshUrl'$config['oauth']['refreshUrl']);
  379.         $container->setParameter('api_platform.oauth.scopes'$config['oauth']['scopes']);
  380.         $container->setParameter('api_platform.oauth.pkce'$config['oauth']['pkce']);
  381.         if ($container->hasDefinition('api_platform.swagger_ui.action')) {
  382.             $container->getDefinition('api_platform.swagger_ui.action')->setArgument(10$config['oauth']['pkce']);
  383.         }
  384.     }
  385.     /**
  386.      * Registers the Swagger, ReDoc and Swagger UI configuration.
  387.      */
  388.     private function registerSwaggerConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  389.     {
  390.         foreach (array_keys($config['swagger']['api_keys']) as $keyName) {
  391.             if (!preg_match('/^[a-zA-Z0-9._-]+$/'$keyName)) {
  392.                 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));
  393.             }
  394.         }
  395.         $container->setParameter('api_platform.swagger.versions'$config['swagger']['versions']);
  396.         if (!$config['enable_swagger'] && $config['enable_swagger_ui']) {
  397.             throw new RuntimeException('You can not enable the Swagger UI without enabling Swagger, fix this by enabling swagger via the configuration "enable_swagger: true".');
  398.         }
  399.         if (!$config['enable_swagger']) {
  400.             return;
  401.         }
  402.         $loader->load('openapi.xml');
  403.         $loader->load('swagger_ui.xml');
  404.         $loader->load('legacy/swagger_ui.xml');
  405.         if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) {
  406.             // Remove the listener but keep the controller to allow customizing the path of the UI
  407.             $container->removeDefinition('api_platform.swagger.listener.ui');
  408.         }
  409.         $container->setParameter('api_platform.enable_swagger_ui'$config['enable_swagger_ui']);
  410.         $container->setParameter('api_platform.enable_re_doc'$config['enable_re_doc']);
  411.         $container->setParameter('api_platform.swagger.api_keys'$config['swagger']['api_keys']);
  412.         if ($config['openapi']['swagger_ui_extra_configuration'] && $config['swagger']['swagger_ui_extra_configuration']) {
  413.             throw new RuntimeException('You can not set "swagger_ui_extra_configuration" twice - in "openapi" and "swagger" section.');
  414.         }
  415.         $container->setParameter('api_platform.swagger_ui.extra_configuration'$config['openapi']['swagger_ui_extra_configuration'] ?: $config['swagger']['swagger_ui_extra_configuration']);
  416.     }
  417.     private function registerJsonApiConfiguration(array $formatsXmlFileLoader $loader, array $config): void
  418.     {
  419.         if (!isset($formats['jsonapi'])) {
  420.             return;
  421.         }
  422.         $loader->load('jsonapi.xml');
  423.         $loader->load('legacy/jsonapi.xml');
  424.     }
  425.     private function registerJsonLdHydraConfiguration(ContainerBuilder $container, array $formatsXmlFileLoader $loader, array $config): void
  426.     {
  427.         if (!isset($formats['jsonld'])) {
  428.             return;
  429.         }
  430.         $loader->load('jsonld.xml');
  431.         $loader->load('legacy/hydra.xml');
  432.         $loader->load('hydra.xml');
  433.         if (!$container->has('api_platform.json_schema.schema_factory')) {
  434.             $container->removeDefinition('api_platform.hydra.json_schema.schema_factory');
  435.         }
  436.         if (!$config['enable_docs']) {
  437.             $container->removeDefinition('api_platform.hydra.listener.response.add_link_header');
  438.         }
  439.     }
  440.     private function registerJsonHalConfiguration(array $formatsXmlFileLoader $loader): void
  441.     {
  442.         if (!isset($formats['jsonhal'])) {
  443.             return;
  444.         }
  445.         $loader->load('hal.xml');
  446.     }
  447.     private function registerJsonProblemConfiguration(array $errorFormatsXmlFileLoader $loader): void
  448.     {
  449.         if (!isset($errorFormats['jsonproblem'])) {
  450.             return;
  451.         }
  452.         $loader->load('problem.xml');
  453.     }
  454.     private function registerGraphQlConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  455.     {
  456.         $enabled $this->isConfigEnabled($container$config['graphql']);
  457.         $graphqlIntrospectionEnabled $enabled && $this->isConfigEnabled($container$config['graphql']['introspection']);
  458.         $graphiqlEnabled $enabled && $this->isConfigEnabled($container$config['graphql']['graphiql']);
  459.         $graphqlPlayGroundEnabled $enabled && $this->isConfigEnabled($container$config['graphql']['graphql_playground']);
  460.         if ($graphqlPlayGroundEnabled) {
  461.             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.');
  462.         }
  463.         $container->setParameter('api_platform.graphql.enabled'$enabled);
  464.         $container->setParameter('api_platform.graphql.introspection.enabled'$graphqlIntrospectionEnabled);
  465.         $container->setParameter('api_platform.graphql.graphiql.enabled'$graphiqlEnabled);
  466.         $container->setParameter('api_platform.graphql.graphql_playground.enabled'$graphqlPlayGroundEnabled);
  467.         $container->setParameter('api_platform.graphql.collection.pagination'$config['graphql']['collection']['pagination']);
  468.         if (!$enabled) {
  469.             return;
  470.         }
  471.         $container->setParameter('api_platform.graphql.default_ide'$config['graphql']['default_ide']);
  472.         $container->setParameter('api_platform.graphql.nesting_separator'$config['graphql']['nesting_separator']);
  473.         $loader->load('graphql.xml');
  474.         // @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
  475.         if (!class_exists(Environment::class) || !isset($container->getParameter('kernel.bundles')['TwigBundle'])) {
  476.             if ($graphiqlEnabled || $graphqlPlayGroundEnabled) {
  477.                 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')));
  478.             }
  479.             $container->removeDefinition('api_platform.graphql.action.graphiql');
  480.             $container->removeDefinition('api_platform.graphql.action.graphql_playground');
  481.         }
  482.         $container->registerForAutoconfiguration(QueryItemResolverInterface::class)
  483.             ->addTag('api_platform.graphql.resolver');
  484.         $container->registerForAutoconfiguration(QueryCollectionResolverInterface::class)
  485.             ->addTag('api_platform.graphql.resolver');
  486.         $container->registerForAutoconfiguration(MutationResolverInterface::class)
  487.             ->addTag('api_platform.graphql.resolver');
  488.         $container->registerForAutoconfiguration(GraphQlTypeInterface::class)
  489.             ->addTag('api_platform.graphql.type');
  490.         $container->registerForAutoconfiguration(ErrorHandlerInterface::class)
  491.             ->addTag('api_platform.graphql.error_handler');
  492.         if (!$container->getParameter('kernel.debug')) {
  493.             return;
  494.         }
  495.         /* TODO: remove these in 4.x only one resolver factory is used and we're using providers/processors */
  496.         if ($config['event_listeners_backward_compatibility_layer'] ?? true) {
  497.             $loader->load('legacy/graphql.xml');
  498.             $requestStack = new Reference('request_stack'ContainerInterface::NULL_ON_INVALID_REFERENCE);
  499.             $collectionDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  500.                 ->setDecoratedService('api_platform.graphql.resolver.factory.collection')
  501.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.collection.inner'), $requestStack]);
  502.             $itemDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  503.                 ->setDecoratedService('api_platform.graphql.resolver.factory.item')
  504.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.item.inner'), $requestStack]);
  505.             $itemMutationDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  506.                 ->setDecoratedService('api_platform.graphql.resolver.factory.item_mutation')
  507.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.item_mutation.inner'), $requestStack]);
  508.             $itemSubscriptionDataCollectorResolverFactory = (new Definition(DataCollectorResolverFactory::class))
  509.                 ->setDecoratedService('api_platform.graphql.resolver.factory.item_subscription')
  510.                 ->setArguments([new Reference('api_platform.graphql.data_collector.resolver.factory.item_subscription.inner'), $requestStack]);
  511.             $container->addDefinitions([
  512.                 'api_platform.graphql.data_collector.resolver.factory.collection' => $collectionDataCollectorResolverFactory,
  513.                 'api_platform.graphql.data_collector.resolver.factory.item' => $itemDataCollectorResolverFactory,
  514.                 'api_platform.graphql.data_collector.resolver.factory.item_mutation' => $itemMutationDataCollectorResolverFactory,
  515.                 'api_platform.graphql.data_collector.resolver.factory.item_subscription' => $itemSubscriptionDataCollectorResolverFactory,
  516.             ]);
  517.         }
  518.     }
  519.     private function registerCacheConfiguration(ContainerBuilder $container): void
  520.     {
  521.         if (!$container->hasParameter('kernel.debug') || !$container->getParameter('kernel.debug')) {
  522.             $container->removeDefinition('api_platform.cache_warmer.cache_pool_clearer');
  523.         }
  524.     }
  525.     private function registerDoctrineOrmConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  526.     {
  527.         if (!$this->isConfigEnabled($container$config['doctrine'])) {
  528.             return;
  529.         }
  530.         // For older versions of doctrine bridge this allows autoconfiguration for filters
  531.         if (!$container->has(ManagerRegistry::class)) {
  532.             $container->setAlias(ManagerRegistry::class, 'doctrine');
  533.         }
  534.         $container->registerForAutoconfiguration(QueryItemExtensionInterface::class)
  535.             ->addTag('api_platform.doctrine.orm.query_extension.item');
  536.         $container->registerForAutoconfiguration(DoctrineQueryCollectionExtensionInterface::class)
  537.             ->addTag('api_platform.doctrine.orm.query_extension.collection');
  538.         $container->registerForAutoconfiguration(DoctrineOrmAbstractFilter::class);
  539.         $container->registerForAutoconfiguration(OrmLinksHandlerInterface::class)
  540.             ->addTag('api_platform.doctrine.orm.links_handler');
  541.         $loader->load('doctrine_orm.xml');
  542.         if ($this->isConfigEnabled($container$config['eager_loading'])) {
  543.             return;
  544.         }
  545.         $container->removeAlias(EagerLoadingExtension::class);
  546.         $container->removeDefinition('api_platform.doctrine.orm.query_extension.eager_loading');
  547.         $container->removeAlias(FilterEagerLoadingExtension::class);
  548.         $container->removeDefinition('api_platform.doctrine.orm.query_extension.filter_eager_loading');
  549.     }
  550.     private function registerDoctrineMongoDbOdmConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  551.     {
  552.         if (!$this->isConfigEnabled($container$config['doctrine_mongodb_odm'])) {
  553.             return;
  554.         }
  555.         $container->registerForAutoconfiguration(AggregationItemExtensionInterface::class)
  556.             ->addTag('api_platform.doctrine_mongodb.odm.aggregation_extension.item');
  557.         $container->registerForAutoconfiguration(AggregationCollectionExtensionInterface::class)
  558.             ->addTag('api_platform.doctrine_mongodb.odm.aggregation_extension.collection');
  559.         $container->registerForAutoconfiguration(DoctrineMongoDbOdmAbstractFilter::class)
  560.             ->setBindings(['$managerRegistry' => new Reference('doctrine_mongodb')]);
  561.         $container->registerForAutoconfiguration(OdmLinksHandlerInterface::class)
  562.             ->addTag('api_platform.doctrine.odm.links_handler');
  563.         $loader->load('doctrine_mongodb_odm.xml');
  564.     }
  565.     private function registerHttpCacheConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  566.     {
  567.         $loader->load('http_cache.xml');
  568.         $loader->load('legacy/http_cache.xml');
  569.         if (!$this->isConfigEnabled($container$config['http_cache']['invalidation'])) {
  570.             return;
  571.         }
  572.         if ($this->isConfigEnabled($container$config['doctrine'])) {
  573.             $loader->load('doctrine_orm_http_cache_purger.xml');
  574.         }
  575.         $loader->load('http_cache_purger.xml');
  576.         $loader->load('legacy/http_cache_purger.xml');
  577.         foreach ($config['http_cache']['invalidation']['scoped_clients'] as $client) {
  578.             $definition $container->getDefinition($client);
  579.             $definition->addTag('api_platform.http_cache.http_client');
  580.         }
  581.         if (!($urls $config['http_cache']['invalidation']['urls'])) {
  582.             $urls $config['http_cache']['invalidation']['varnish_urls'];
  583.         }
  584.         foreach ($urls as $key => $url) {
  585.             $definition = new Definition(ScopingHttpClient::class, [new Reference('http_client'), $url, ['base_uri' => $url] + $config['http_cache']['invalidation']['request_options']]);
  586.             $definition->setFactory([ScopingHttpClient::class, 'forBaseUri']);
  587.             $definition->addTag('api_platform.http_cache.http_client');
  588.             $container->setDefinition('api_platform.invalidation_http_client.'.$key$definition);
  589.         }
  590.         $serviceName $config['http_cache']['invalidation']['purger'];
  591.         if (!$container->hasDefinition('api_platform.http_cache.purger')) {
  592.             $container->setAlias('api_platform.http_cache.purger'$serviceName);
  593.         }
  594.     }
  595.     /**
  596.      * Normalizes the format from config to the one accepted by Symfony HttpFoundation.
  597.      */
  598.     private function getFormats(array $configFormats): array
  599.     {
  600.         $formats = [];
  601.         foreach ($configFormats as $format => $value) {
  602.             foreach ($value['mime_types'] as $mimeType) {
  603.                 $formats[$format][] = $mimeType;
  604.             }
  605.         }
  606.         return $formats;
  607.     }
  608.     private function registerValidatorConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  609.     {
  610.         if (interface_exists(ValidatorInterface::class)) {
  611.             $loader->load('metadata/validator.xml');
  612.             $loader->load('symfony/validator.xml');
  613.             if ($this->isConfigEnabled($container$config['graphql'])) {
  614.                 $loader->load('graphql/validator.xml');
  615.             }
  616.             $container->registerForAutoconfiguration(ValidationGroupsGeneratorInterface::class)
  617.                 ->addTag('api_platform.validation_groups_generator');
  618.             $container->registerForAutoconfiguration(PropertySchemaRestrictionMetadataInterface::class)
  619.                 ->addTag('api_platform.metadata.property_schema_restriction');
  620.             $loader->load('legacy/validator.xml');
  621.         }
  622.         if (!$config['validator']) {
  623.             return;
  624.         }
  625.         $container->setParameter('api_platform.validator.serialize_payload_fields'$config['validator']['serialize_payload_fields']);
  626.         $container->setParameter('api_platform.validator.query_parameter_validation'$config['validator']['query_parameter_validation']);
  627.         if (!$config['validator']['query_parameter_validation']) {
  628.             $container->removeDefinition('api_platform.listener.view.validate_query_parameters');
  629.             $container->removeDefinition('api_platform.validator.query_parameter_validator');
  630.         }
  631.     }
  632.     private function registerDataCollectorConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  633.     {
  634.         if (!$config['enable_profiler']) {
  635.             return;
  636.         }
  637.         $loader->load('data_collector.xml');
  638.         if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) {
  639.             $loader->load('debug.xml');
  640.         }
  641.     }
  642.     private function registerMercureConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  643.     {
  644.         if (!$this->isConfigEnabled($container$config['mercure'])) {
  645.             return;
  646.         }
  647.         $container->setParameter('api_platform.mercure.include_type'$config['mercure']['include_type']);
  648.         $loader->load('legacy/mercure.xml');
  649.         $loader->load('mercure.xml');
  650.         if ($this->isConfigEnabled($container$config['doctrine'])) {
  651.             $loader->load('doctrine_orm_mercure_publisher.xml');
  652.         }
  653.         if ($this->isConfigEnabled($container$config['doctrine_mongodb_odm'])) {
  654.             $loader->load('doctrine_odm_mercure_publisher.xml');
  655.         }
  656.         if ($this->isConfigEnabled($container$config['graphql'])) {
  657.             $loader->load('graphql_mercure.xml');
  658.         }
  659.     }
  660.     private function registerMessengerConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  661.     {
  662.         if (!$this->isConfigEnabled($container$config['messenger'])) {
  663.             return;
  664.         }
  665.         $loader->load('messenger.xml');
  666.     }
  667.     private function registerElasticsearchConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  668.     {
  669.         $enabled $this->isConfigEnabled($container$config['elasticsearch']);
  670.         $container->setParameter('api_platform.elasticsearch.enabled'$enabled);
  671.         if (!$enabled) {
  672.             return;
  673.         }
  674.         $clientClass class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\Client::class;
  675.         $clientDefinition = new Definition($clientClass);
  676.         $container->setDefinition('api_platform.elasticsearch.client'$clientDefinition);
  677.         $container->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class)
  678.             ->addTag('api_platform.elasticsearch.request_body_search_extension.collection');
  679.         $container->setParameter('api_platform.elasticsearch.hosts'$config['elasticsearch']['hosts']);
  680.         $loader->load('elasticsearch.xml');
  681.         // @phpstan-ignore-next-line
  682.         if (\Elasticsearch\Client::class === $clientClass) {
  683.             $loader->load('legacy/elasticsearch.xml');
  684.             $container->setParameter('api_platform.elasticsearch.mapping'$config['elasticsearch']['mapping']);
  685.             $container->setDefinition('api_platform.elasticsearch.client_for_metadata'$clientDefinition);
  686.         }
  687.     }
  688.     private function registerSecurityConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  689.     {
  690.         /** @var string[] $bundles */
  691.         $bundles $container->getParameter('kernel.bundles');
  692.         if (!isset($bundles['SecurityBundle'])) {
  693.             return;
  694.         }
  695.         $loader->load('security.xml');
  696.         $loader->load('legacy/security.xml');
  697.         if (interface_exists(ValidatorInterface::class)) {
  698.             $loader->load('symfony/security_validator.xml');
  699.         }
  700.         if ($this->isConfigEnabled($container$config['graphql'])) {
  701.             $loader->load('graphql/security.xml');
  702.         }
  703.     }
  704.     private function registerOpenApiConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  705.     {
  706.         $container->setParameter('api_platform.openapi.termsOfService'$config['openapi']['termsOfService']);
  707.         $container->setParameter('api_platform.openapi.contact.name'$config['openapi']['contact']['name']);
  708.         $container->setParameter('api_platform.openapi.contact.url'$config['openapi']['contact']['url']);
  709.         $container->setParameter('api_platform.openapi.contact.email'$config['openapi']['contact']['email']);
  710.         $container->setParameter('api_platform.openapi.license.name'$config['openapi']['license']['name']);
  711.         $container->setParameter('api_platform.openapi.license.url'$config['openapi']['license']['url']);
  712.         $loader->load('json_schema.xml');
  713.     }
  714.     private function registerMakerConfiguration(ContainerBuilder $container, array $configXmlFileLoader $loader): void
  715.     {
  716.         if (!$this->isConfigEnabled($container$config['maker'])) {
  717.             return;
  718.         }
  719.         $loader->load('maker.xml');
  720.     }
  721.     private function registerArgumentResolverConfiguration(XmlFileLoader $loader): void
  722.     {
  723.         $loader->load('argument_resolver.xml');
  724.     }
  725.     private function registerInflectorConfiguration(array $config): void
  726.     {
  727.         if ($config['keep_legacy_inflector']) {
  728.             Inflector::keepLegacyInflector(true);
  729.             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.');
  730.         } else {
  731.             Inflector::keepLegacyInflector(false);
  732.         }
  733.     }
  734. }