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

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