vendor/friendsofsymfony/http-cache-bundle/src/DependencyInjection/FOSHttpCacheExtension.php line 40

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the FOSHttpCacheBundle package.
  4.  *
  5.  * (c) FriendsOfSymfony <http://friendsofsymfony.github.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. namespace FOS\HttpCacheBundle\DependencyInjection;
  11. use FOS\HttpCache\ProxyClient\HttpDispatcher;
  12. use FOS\HttpCache\ProxyClient\ProxyClient;
  13. use FOS\HttpCache\SymfonyCache\KernelDispatcher;
  14. use FOS\HttpCache\TagHeaderFormatter\MaxHeaderValueLengthFormatter;
  15. use FOS\HttpCacheBundle\DependencyInjection\Compiler\HashGeneratorPass;
  16. use FOS\HttpCacheBundle\Http\ResponseMatcher\ExpressionResponseMatcher;
  17. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  18. use Symfony\Component\Config\FileLocator;
  19. use Symfony\Component\Console\Application;
  20. use Symfony\Component\DependencyInjection\ChildDefinition;
  21. use Symfony\Component\DependencyInjection\ContainerBuilder;
  22. use Symfony\Component\DependencyInjection\Definition;
  23. use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  24. use Symfony\Component\DependencyInjection\Reference;
  25. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  26. use Symfony\Component\HttpKernel\Kernel;
  27. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  28. /**
  29.  * {@inheritdoc}
  30.  */
  31. class FOSHttpCacheExtension extends Extension
  32. {
  33.     /**
  34.      * {@inheritdoc}
  35.      */
  36.     public function getConfiguration(array $configContainerBuilder $container)
  37.     {
  38.         return new Configuration($container->getParameter('kernel.debug'));
  39.     }
  40.     /**
  41.      * {@inheritdoc}
  42.      */
  43.     public function load(array $configsContainerBuilder $container)
  44.     {
  45.         $configuration $this->getConfiguration($configs$container);
  46.         $config $this->processConfiguration($configuration$configs);
  47.         $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
  48.         $loader->load('matcher.xml');
  49.         if ($config['debug']['enabled'] || (!empty($config['cache_control']))) {
  50.             $debugHeader $config['debug']['enabled'] ? $config['debug']['header'] : false;
  51.             $container->setParameter('fos_http_cache.debug_header'$debugHeader);
  52.             $loader->load('cache_control_listener.xml');
  53.         }
  54.         $this->loadCacheable($container$config['cacheable']);
  55.         if (!empty($config['cache_control'])) {
  56.             $this->loadCacheControl($container$config['cache_control']);
  57.         }
  58.         if (isset($config['proxy_client'])) {
  59.             $this->loadProxyClient($container$loader$config['proxy_client']);
  60.         }
  61.         if (isset($config['test'])) {
  62.             $this->loadTest($container$loader$config['test']);
  63.         }
  64.         if ($config['cache_manager']['enabled']) {
  65.             if (array_key_exists('custom_proxy_client'$config['cache_manager'])) {
  66.                 // overwrite the previously set alias, if a proxy client was also configured
  67.                 $container->setAlias(
  68.                     'fos_http_cache.default_proxy_client',
  69.                     $config['cache_manager']['custom_proxy_client']
  70.                 );
  71.             }
  72.             if ('auto' === $config['cache_manager']['generate_url_type']) {
  73.                 if (array_key_exists('custom_proxy_client'$config['cache_manager'])) {
  74.                     $generateUrlType UrlGeneratorInterface::ABSOLUTE_URL;
  75.                 } else {
  76.                     $defaultClient $this->getDefaultProxyClient($config['proxy_client']);
  77.                     if ('noop' !== $defaultClient
  78.                         && array_key_exists('base_url'$config['proxy_client'][$defaultClient])) {
  79.                         $generateUrlType UrlGeneratorInterface::ABSOLUTE_PATH;
  80.                     } else {
  81.                         $generateUrlType UrlGeneratorInterface::ABSOLUTE_URL;
  82.                     }
  83.                 }
  84.             } else {
  85.                 $generateUrlType $config['cache_manager']['generate_url_type'];
  86.             }
  87.             $container->setParameter('fos_http_cache.cache_manager.generate_url_type'$generateUrlType);
  88.             $loader->load('cache_manager.xml');
  89.             if (class_exists(Application::class)) {
  90.                 $loader->load('cache_manager_commands.xml');
  91.             }
  92.         }
  93.         if ($config['tags']['enabled']) {
  94.             $this->loadCacheTagging(
  95.                 $container,
  96.                 $loader,
  97.                 $config['tags'],
  98.                 array_key_exists('proxy_client'$config)
  99.                     ? $this->getDefaultProxyClient($config['proxy_client'])
  100.                     : 'custom'
  101.             );
  102.         } else {
  103.             $container->setParameter('fos_http_cache.compiler_pass.tag_annotations'false);
  104.         }
  105.         if ($config['invalidation']['enabled']) {
  106.             $loader->load('invalidation_listener.xml');
  107.             if (!empty($config['invalidation']['expression_language'])) {
  108.                 $container->setAlias(
  109.                     'fos_http_cache.invalidation.expression_language',
  110.                     $config['invalidation']['expression_language']
  111.                 );
  112.             }
  113.             if (!empty($config['invalidation']['rules'])) {
  114.                 $this->loadInvalidatorRules($container$config['invalidation']['rules']);
  115.             }
  116.         }
  117.         if ($config['user_context']['enabled']) {
  118.             $this->loadUserContext($container$loader$config['user_context']);
  119.         }
  120.         if (!empty($config['flash_message']) && $config['flash_message']['enabled']) {
  121.             unset($config['flash_message']['enabled']);
  122.             $container->setParameter('fos_http_cache.event_listener.flash_message.options'$config['flash_message']);
  123.             $loader->load('flash_message.xml');
  124.         }
  125.         if (\PHP_VERSION_ID >= 80000) {
  126.             $loader->load('php8_attributes.xml');
  127.         }
  128.     }
  129.     private function loadCacheable(ContainerBuilder $container, array $config)
  130.     {
  131.         $definition $container->getDefinition('fos_http_cache.response_matcher.cacheable');
  132.         // Change CacheableResponseMatcher to ExpressionResponseMatcher
  133.         if ($config['response']['expression']) {
  134.             $definition->setClass(ExpressionResponseMatcher::class)
  135.                 ->setArguments([$config['response']['expression']]);
  136.         } else {
  137.             $container->setParameter(
  138.                 'fos_http_cache.cacheable.response.additional_status',
  139.                 $config['response']['additional_status']
  140.             );
  141.         }
  142.     }
  143.     /**
  144.      * @throws InvalidConfigurationException
  145.      */
  146.     private function loadCacheControl(ContainerBuilder $container, array $config)
  147.     {
  148.         $controlDefinition $container->getDefinition('fos_http_cache.event_listener.cache_control');
  149.         foreach ($config['rules'] as $rule) {
  150.             $ruleMatcher $this->parseRuleMatcher($container$rule['match']);
  151.             if ('default' === $rule['headers']['overwrite']) {
  152.                 $rule['headers']['overwrite'] = $config['defaults']['overwrite'];
  153.             }
  154.             $controlDefinition->addMethodCall('addRule', [$ruleMatcher$rule['headers']]);
  155.         }
  156.     }
  157.     /**
  158.      * Parse one cache control rule match configuration.
  159.      *
  160.      * @param array $match Request and response match criteria
  161.      *
  162.      * @return Reference pointing to a rule matcher service
  163.      */
  164.     private function parseRuleMatcher(ContainerBuilder $container, array $match)
  165.     {
  166.         $requestMatcher $this->parseRequestMatcher($container$match);
  167.         $responseMatcher $this->parseResponseMatcher($container$match);
  168.         $signature serialize([(string) $requestMatcher, (string) $responseMatcher]);
  169.         $id 'fos_http_cache.cache_control.rule_matcher.'.md5($signature);
  170.         if ($container->hasDefinition($id)) {
  171.             throw new InvalidConfigurationException('Duplicate match criteria. Would be hidden by a previous rule. match: '.json_encode($match));
  172.         }
  173.         $container
  174.             ->setDefinition($id, new ChildDefinition('fos_http_cache.rule_matcher'))
  175.             ->replaceArgument(0$requestMatcher)
  176.             ->replaceArgument(1$responseMatcher)
  177.         ;
  178.         return new Reference($id);
  179.     }
  180.     /**
  181.      * Used for cache control, tag and invalidation rules.
  182.      *
  183.      * @return Reference to the request matcher
  184.      */
  185.     private function parseRequestMatcher(ContainerBuilder $container, array $match)
  186.     {
  187.         $match['ips'] = (empty($match['ips'])) ? null $match['ips'];
  188.         $arguments = [
  189.             $match['path'],
  190.             $match['host'],
  191.             $match['methods'],
  192.             $match['ips'],
  193.             $match['attributes'],
  194.         ];
  195.         $serialized serialize($arguments);
  196.         $id 'fos_http_cache.request_matcher.'.md5($serialized).sha1($serialized);
  197.         if (!$container->hasDefinition($id)) {
  198.             $container
  199.                 ->setDefinition($id, new ChildDefinition('fos_http_cache.request_matcher'))
  200.                 ->setArguments($arguments)
  201.             ;
  202.             if (!empty($match['query_string'])) {
  203.                 $container->getDefinition($id)->addMethodCall('setQueryString', [$match['query_string']]);
  204.             }
  205.         }
  206.         return new Reference($id);
  207.     }
  208.     /**
  209.      * Used only for cache control rules.
  210.      *
  211.      * @return Reference to the correct response matcher service
  212.      */
  213.     private function parseResponseMatcher(ContainerBuilder $container, array $config)
  214.     {
  215.         if (!empty($config['additional_response_status'])) {
  216.             $id 'fos_http_cache.cache_control.expression.'.md5(serialize($config['additional_response_status']));
  217.             if (!$container->hasDefinition($id)) {
  218.                 $container
  219.                     ->setDefinition($id, new ChildDefinition('fos_http_cache.response_matcher.cache_control.cacheable_response'))
  220.                     ->setArguments([$config['additional_response_status']])
  221.                 ;
  222.             }
  223.         } elseif (!empty($config['match_response'])) {
  224.             $id 'fos_http_cache.cache_control.match_response.'.md5($config['match_response']);
  225.             if (!$container->hasDefinition($id)) {
  226.                 $container
  227.                     ->setDefinition($id, new ChildDefinition('fos_http_cache.response_matcher.cache_control.expression'))
  228.                     ->replaceArgument(0$config['match_response'])
  229.                 ;
  230.             }
  231.         } else {
  232.             $id 'fos_http_cache.response_matcher.cacheable';
  233.         }
  234.         return new Reference($id);
  235.     }
  236.     private function loadUserContext(ContainerBuilder $containerXmlFileLoader $loader, array $config)
  237.     {
  238.         $configuredUserIdentifierHeaders array_map('strtolower'$config['user_identifier_headers']);
  239.         $completeUserIdentifierHeaders $configuredUserIdentifierHeaders;
  240.         if (false !== $config['session_name_prefix'] && !in_array('cookie'$completeUserIdentifierHeaders)) {
  241.             $completeUserIdentifierHeaders[] = 'cookie';
  242.         }
  243.         $loader->load('user_context.xml');
  244.         // TODO: Remove this service file when going to version 3 of the bundle
  245.         if (Kernel::MAJOR_VERSION >= 6) {
  246.             $loader->load('user_context_legacy_sf6.xml');
  247.         } else {
  248.             $loader->load('user_context_legacy.xml');
  249.         }
  250.         $container->getDefinition('fos_http_cache.user_context.request_matcher')
  251.             ->replaceArgument(0$config['match']['accept'])
  252.             ->replaceArgument(1$config['match']['method']);
  253.         $container->setParameter('fos_http_cache.event_listener.user_context.options', [
  254.             'user_identifier_headers' => $completeUserIdentifierHeaders,
  255.             'user_hash_header' => $config['user_hash_header'],
  256.             'ttl' => $config['hash_cache_ttl'],
  257.             'add_vary_on_hash' => $config['always_vary_on_context_hash'],
  258.         ]);
  259.         $container->getDefinition('fos_http_cache.event_listener.user_context')
  260.             ->replaceArgument(0, new Reference($config['match']['matcher_service']))
  261.         ;
  262.         $options = [
  263.             'user_identifier_headers' => $configuredUserIdentifierHeaders,
  264.             'session_name_prefix' => $config['session_name_prefix'],
  265.         ];
  266.         $container->getDefinition('fos_http_cache.user_context.anonymous_request_matcher')
  267.             ->replaceArgument(0$options);
  268.         if ($config['logout_handler']['enabled']) {
  269.             $container->setAlias('security.logout.handler.session''fos_http_cache.user_context.session_logout_handler');
  270.         } else {
  271.             $container->removeDefinition('fos_http_cache.user_context.logout_handler');
  272.             $container->removeDefinition('fos_http_cache.user_context.session_logout_handler');
  273.             $container->removeDefinition('fos_http_cache.user_context_invalidator');
  274.         }
  275.         if ($config['role_provider']) {
  276.             $container->getDefinition('fos_http_cache.user_context.role_provider')
  277.                 ->addTag(HashGeneratorPass::TAG_NAME)
  278.                 ->setAbstract(false);
  279.         }
  280.     }
  281.     private function loadProxyClient(ContainerBuilder $containerXmlFileLoader $loader, array $config)
  282.     {
  283.         if (isset($config['varnish'])) {
  284.             $this->loadVarnish($container$loader$config['varnish']);
  285.         }
  286.         if (isset($config['nginx'])) {
  287.             $this->loadNginx($container$loader$config['nginx']);
  288.         }
  289.         if (isset($config['symfony'])) {
  290.             $this->loadSymfony($container$loader$config['symfony']);
  291.         }
  292.         if (isset($config['noop'])) {
  293.             $loader->load('noop.xml');
  294.         }
  295.         $container->setAlias(
  296.             'fos_http_cache.default_proxy_client',
  297.             'fos_http_cache.proxy_client.'.$this->getDefaultProxyClient($config)
  298.         );
  299.         $container->setAlias(
  300.             ProxyClient::class,
  301.             'fos_http_cache.default_proxy_client'
  302.         );
  303.     }
  304.     /**
  305.      * Define the http dispatcher service for the proxy client $name.
  306.      *
  307.      * @param string $serviceName
  308.      */
  309.     private function createHttpDispatcherDefinition(ContainerBuilder $container, array $config$serviceName)
  310.     {
  311.         if (array_key_exists('servers'$config)) {
  312.             foreach ($config['servers'] as $url) {
  313.                 $usedEnvs = [];
  314.                 $container->resolveEnvPlaceholders($urlnull$usedEnvs);
  315.                 if (=== \count($usedEnvs)) {
  316.                     $this->validateUrl($url'Not a valid Varnish server address: "%s"');
  317.                 }
  318.             }
  319.         }
  320.         if (array_key_exists('servers_from_jsonenv'$config) && is_string($config['servers_from_jsonenv'])) {
  321.             // check that the config contains an env var
  322.             $usedEnvs = [];
  323.             $container->resolveEnvPlaceholders($config['servers_from_jsonenv'], null$usedEnvs);
  324.             if (=== \count($usedEnvs)) {
  325.                 throw new InvalidConfigurationException('Not a valid Varnish servers_from_jsonenv configuration: '.$config['servers_from_jsonenv']);
  326.             }
  327.             $config['servers'] = $config['servers_from_jsonenv'];
  328.         }
  329.         if (!empty($config['base_url'])) {
  330.             $baseUrl $config['base_url'];
  331.             $usedEnvs = [];
  332.             $container->resolveEnvPlaceholders($baseUrlnull$usedEnvs);
  333.             if (=== \count($usedEnvs)) {
  334.                 $baseUrl $this->prefixSchema($baseUrl);
  335.                 $this->validateUrl($baseUrl'Not a valid base path: "%s"');
  336.             }
  337.         } else {
  338.             $baseUrl null;
  339.         }
  340.         $httpClient null;
  341.         if ($config['http_client']) {
  342.             $httpClient = new Reference($config['http_client']);
  343.         }
  344.         $definition = new Definition(HttpDispatcher::class, [
  345.             $config['servers'],
  346.             $baseUrl,
  347.             $httpClient,
  348.         ]);
  349.         $container->setDefinition($serviceName$definition);
  350.     }
  351.     private function loadVarnish(ContainerBuilder $containerXmlFileLoader $loader, array $config)
  352.     {
  353.         $this->createHttpDispatcherDefinition($container$config['http'], 'fos_http_cache.proxy_client.varnish.http_dispatcher');
  354.         $options = [
  355.             'tag_mode' => $config['tag_mode'],
  356.             'tags_header' => $config['tags_header'],
  357.         ];
  358.         if (!empty($config['header_length'])) {
  359.             $options['header_length'] = $config['header_length'];
  360.         }
  361.         if (!empty($config['default_ban_headers'])) {
  362.             $options['default_ban_headers'] = $config['default_ban_headers'];
  363.         }
  364.         $container->setParameter('fos_http_cache.proxy_client.varnish.options'$options);
  365.         $loader->load('varnish.xml');
  366.     }
  367.     private function loadNginx(ContainerBuilder $containerXmlFileLoader $loader, array $config)
  368.     {
  369.         $this->createHttpDispatcherDefinition($container$config['http'], 'fos_http_cache.proxy_client.nginx.http_dispatcher');
  370.         $container->setParameter('fos_http_cache.proxy_client.nginx.options', [
  371.             'purge_location' => $config['purge_location'],
  372.         ]);
  373.         $loader->load('nginx.xml');
  374.     }
  375.     private function loadSymfony(ContainerBuilder $containerXmlFileLoader $loader, array $config)
  376.     {
  377.         $serviceName 'fos_http_cache.proxy_client.symfony.http_dispatcher';
  378.         if ($config['use_kernel_dispatcher']) {
  379.             $definition = new Definition(KernelDispatcher::class, [
  380.                 new Reference('kernel'),
  381.             ]);
  382.             $container->setDefinition($serviceName$definition);
  383.         } else {
  384.             $this->createHttpDispatcherDefinition($container$config['http'], $serviceName);
  385.         }
  386.         $options = [
  387.             'tags_header' => $config['tags_header'],
  388.             'tags_method' => $config['tags_method'],
  389.             'purge_method' => $config['purge_method'],
  390.         ];
  391.         if (!empty($config['header_length'])) {
  392.             $options['header_length'] = $config['header_length'];
  393.         }
  394.         $container->setParameter('fos_http_cache.proxy_client.symfony.options'$options);
  395.         $loader->load('symfony.xml');
  396.     }
  397.     /**
  398.      * @param array  $config Configuration section for the tags node
  399.      * @param string $client Name of the client used with the cache manager,
  400.      *                       "custom" when a custom client is used
  401.      */
  402.     private function loadCacheTagging(ContainerBuilder $containerXmlFileLoader $loader, array $config$client)
  403.     {
  404.         if ('auto' === $config['enabled'] && !in_array($client, ['varnish''symfony'])) {
  405.             $container->setParameter('fos_http_cache.compiler_pass.tag_annotations'false);
  406.             return;
  407.         }
  408.         if (!in_array($client, ['varnish''symfony''custom''noop'])) {
  409.             throw new InvalidConfigurationException(sprintf('You can not enable cache tagging with the %s client'$client));
  410.         }
  411.         $container->setParameter('fos_http_cache.compiler_pass.tag_annotations'$config['annotations']['enabled']);
  412.         $container->setParameter('fos_http_cache.tag_handler.response_header'$config['response_header']);
  413.         $container->setParameter('fos_http_cache.tag_handler.separator'$config['separator']);
  414.         $container->setParameter('fos_http_cache.tag_handler.strict'$config['strict']);
  415.         $loader->load('cache_tagging.xml');
  416.         if (class_exists(Application::class)) {
  417.             $loader->load('cache_tagging_commands.xml');
  418.         }
  419.         if (!empty($config['expression_language'])) {
  420.             $container->setAlias(
  421.                 'fos_http_cache.tag_handler.expression_language',
  422.                 $config['expression_language']
  423.             );
  424.         }
  425.         if (!empty($config['rules'])) {
  426.             $this->loadTagRules($container$config['rules']);
  427.         }
  428.         if (null !== $config['max_header_value_length']) {
  429.             $container->register('fos_http_cache.tag_handler.max_header_value_length_header_formatter'MaxHeaderValueLengthFormatter::class)
  430.                 ->setDecoratedService('fos_http_cache.tag_handler.header_formatter')
  431.                 ->addArgument(new Reference('fos_http_cache.tag_handler.max_header_value_length_header_formatter.inner'))
  432.                 ->addArgument((int) $config['max_header_value_length']);
  433.         }
  434.     }
  435.     private function loadTest(ContainerBuilder $containerXmlFileLoader $loader, array $config)
  436.     {
  437.         $container->setParameter('fos_http_cache.test.cache_header'$config['cache_header']);
  438.         if ($config['proxy_server']) {
  439.             $this->loadProxyServer($container$loader$config['proxy_server']);
  440.         }
  441.     }
  442.     private function loadProxyServer(ContainerBuilder $containerXmlFileLoader $loader, array $config)
  443.     {
  444.         if (isset($config['varnish'])) {
  445.             $this->loadVarnishProxyServer($container$loader$config['varnish']);
  446.         }
  447.         if (isset($config['nginx'])) {
  448.             $this->loadNginxProxyServer($container$loader$config['nginx']);
  449.         }
  450.         $container->setAlias(
  451.             'fos_http_cache.test.default_proxy_server',
  452.             'fos_http_cache.test.proxy_server.'.$this->getDefaultProxyClient($config)
  453.         );
  454.     }
  455.     private function loadVarnishProxyServer(ContainerBuilder $containerXmlFileLoader $loader$config)
  456.     {
  457.         $loader->load('varnish_proxy.xml');
  458.         foreach ($config as $key => $value) {
  459.             $container->setParameter(
  460.                 'fos_http_cache.test.proxy_server.varnish.'.$key,
  461.                 $value
  462.             );
  463.         }
  464.     }
  465.     private function loadNginxProxyServer(ContainerBuilder $containerXmlFileLoader $loader$config)
  466.     {
  467.         $loader->load('nginx_proxy.xml');
  468.         foreach ($config as $key => $value) {
  469.             $container->setParameter(
  470.                 'fos_http_cache.test.proxy_server.nginx.'.$key,
  471.                 $value
  472.             );
  473.         }
  474.     }
  475.     private function loadTagRules(ContainerBuilder $container, array $config)
  476.     {
  477.         $tagDefinition $container->getDefinition('fos_http_cache.event_listener.tag');
  478.         foreach ($config as $rule) {
  479.             $ruleMatcher $this->parseRequestMatcher($container$rule['match']);
  480.             $tags = [
  481.                 'tags' => $rule['tags'],
  482.                 'expressions' => $rule['tag_expressions'],
  483.             ];
  484.             $tagDefinition->addMethodCall('addRule', [$ruleMatcher$tags]);
  485.         }
  486.     }
  487.     private function loadInvalidatorRules(ContainerBuilder $container, array $config)
  488.     {
  489.         $tagDefinition $container->getDefinition('fos_http_cache.event_listener.invalidation');
  490.         foreach ($config as $rule) {
  491.             $ruleMatcher $this->parseRequestMatcher($container$rule['match']);
  492.             $tagDefinition->addMethodCall('addRule', [$ruleMatcher$rule['routes']]);
  493.         }
  494.     }
  495.     private function validateUrl($url$msg)
  496.     {
  497.         $prefixed $this->prefixSchema($url);
  498.         if (!$parts parse_url($prefixed)) {
  499.             throw new InvalidConfigurationException(sprintf($msg$url));
  500.         }
  501.     }
  502.     private function prefixSchema($url)
  503.     {
  504.         if (false === strpos($url'://')) {
  505.             $url sprintf('%s://%s''http'$url);
  506.         }
  507.         return $url;
  508.     }
  509.     private function getDefaultProxyClient(array $config)
  510.     {
  511.         if (isset($config['default'])) {
  512.             return $config['default'];
  513.         }
  514.         if (isset($config['varnish'])) {
  515.             return 'varnish';
  516.         }
  517.         if (isset($config['nginx'])) {
  518.             return 'nginx';
  519.         }
  520.         if (isset($config['symfony'])) {
  521.             return 'symfony';
  522.         }
  523.         if (isset($config['noop'])) {
  524.             return 'noop';
  525.         }
  526.         throw new InvalidConfigurationException('No proxy client configured');
  527.     }
  528. }