From 11eeafcf029b333f9c47fd7d0343bae52fcfa8a0 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sat, 6 Aug 2016 12:59:07 +0200 Subject: [PATCH] Allow to configure plugins specifically for a client --- CHANGELOG.md | 2 + DependencyInjection/Configuration.php | 352 +++++++++++++----- DependencyInjection/HttplugExtension.php | 111 ++++-- Resources/config/plugins.xml | 20 + Tests/Functional/ServiceInstantiationTest.php | 3 + Tests/Resources/Fixtures/config/full.php | 37 ++ Tests/Resources/Fixtures/config/full.xml | 21 ++ Tests/Resources/Fixtures/config/full.yml | 22 ++ .../Fixtures/config/invalid_plugin.yml | 6 + Tests/Resources/app/config/config.yml | 12 + .../DependencyInjection/ConfigurationTest.php | 60 ++- .../HttplugExtensionTest.php | 76 ++++ 12 files changed, 598 insertions(+), 124 deletions(-) create mode 100644 Tests/Resources/Fixtures/config/invalid_plugin.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c7e1b82..a666ee45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ ### Added +- You can now also configure client specific plugins in the `plugins` option of a client. + Some plugins that you previously had to define in your own services can now be configured on the client. - Support for BatchClient - The stopwatch plugin in included by default when using profiling. diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 8bea9e7c..2eed8a3b 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -2,8 +2,8 @@ namespace Http\HttplugBundle\DependencyInjection; -use Symfony\Component\Config\Definition\ArrayNode; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; @@ -45,7 +45,7 @@ public function getConfigTreeBuilder() $rootNode = $treeBuilder->root('httplug'); $this->configureClients($rootNode); - $this->configurePlugins($rootNode); + $this->configureSharedPlugins($rootNode); $rootNode ->validate() @@ -167,6 +167,7 @@ private function configureClients(ArrayNodeDefinition $root) ->thenInvalid('A http client can\'t be decorated with both FlexibleHttpClient and HttpMethodsClient. Only one of the following options can be true. ("flexible_client", "http_methods_client")')->end() ->useAttributeAsKey('name') ->prototype('array') + ->fixXmlConfig('plugin') ->children() ->scalarNode('factory') ->isRequired() @@ -185,136 +186,289 @@ private function configureClients(ArrayNodeDefinition $root) ->defaultFalse() ->info('Set to true to get the client wrapped in a BatchClient which allows you to send multiple request at the same time.') ->end() - ->arrayNode('plugins') - ->info('A list of service ids of plugins. The order is important.') - ->prototype('scalar')->end() - ->end() ->variableNode('config')->defaultValue([])->end() + ->append($this->createClientPluginNode()) ->end() - ->end(); + ->end() + ->end(); } /** * @param ArrayNodeDefinition $root */ - private function configurePlugins(ArrayNodeDefinition $root) + private function configureSharedPlugins(ArrayNodeDefinition $root) { - $root->children() - ->arrayNode('plugins') + $pluginsNode = $root + ->children() + ->arrayNode('plugins') + ->info('Global plugin configuration. Plugins need to be explicitly added to clients.') ->addDefaultsIfNotSet() - ->children() - ->append($this->addAuthenticationPluiginNode()) + // don't call end to get the plugins node + ; + $this->addSharedPluginNodes($pluginsNode); + } - ->arrayNode('cache') + /** + * Createplugins node of a client. + * + * @return ArrayNodeDefinition The plugin node + */ + private function createClientPluginNode() + { + $builder = new TreeBuilder(); + $node = $builder->root('plugins'); + + /** @var ArrayNodeDefinition $pluginList */ + $pluginList = $node + ->info('A list of plugin service ids and client specific plugin definitions. The order is important.') + ->prototype('array') + ; + $pluginList + // support having just a service id in the list + ->beforeNormalization() + ->always(function ($plugin) { + if (is_string($plugin)) { + return [ + 'reference' => [ + 'enabled' => true, + 'id' => $plugin, + ], + ]; + } + + return $plugin; + }) + ->end() + + ->validate() + ->always(function ($plugins) { + foreach ($plugins as $name => $definition) { + if ('authentication' === $name) { + if (!count($definition)) { + unset($plugins['authentication']); + } + } elseif (!$definition['enabled']) { + unset($plugins[$name]); + } + } + + return $plugins; + }) + ->end() + ; + $this->addSharedPluginNodes($pluginList, true); + + $pluginList + ->children() + ->arrayNode('reference') + ->canBeEnabled() + ->info('Reference to a plugin service') + ->children() + ->scalarNode('id') + ->info('Service id of a plugin') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->end() + ->end() + ->arrayNode('add_host') ->canBeEnabled() ->addDefaultsIfNotSet() - ->children() - ->scalarNode('cache_pool') - ->info('This must be a service id to a service implementing Psr\Cache\CacheItemPoolInterface') - ->isRequired() - ->cannotBeEmpty() - ->end() - ->scalarNode('stream_factory') - ->info('This must be a service id to a service implementing Http\Message\StreamFactory') - ->defaultValue('httplug.stream_factory') - ->cannotBeEmpty() - ->end() - ->arrayNode('config') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('default_ttl')->defaultNull()->end() - ->scalarNode('respect_cache_headers')->defaultTrue()->end() - ->end() - ->end() + ->info('Set scheme, host and port in the request URI.') + ->children() + ->scalarNode('host') + ->info('Host name including protocol and optionally the port number, e.g. https://api.local:8000') + ->isRequired() + ->cannotBeEmpty() ->end() - ->end() // End cache plugin - - ->arrayNode('cookie') + ->scalarNode('replace') + ->info('Whether to replace the host if request already specifies one') + ->defaultValue(false) + ->end() + ->end() + ->end() + ->arrayNode('header_append') ->canBeEnabled() - ->children() - ->scalarNode('cookie_jar') - ->info('This must be a service id to a service implementing Http\Message\CookieJar') - ->isRequired() - ->cannotBeEmpty() - ->end() + ->info('Append headers to the request. If the header already exists the value will be appended to the current value.') + ->fixXmlConfig('header') + ->children() + ->arrayNode('headers') + ->info('Keys are the header names, values the header values') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('scalar')->end() ->end() - ->end() // End cookie plugin - - ->arrayNode('decoder') - ->canBeDisabled() - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('use_content_encoding')->defaultTrue()->end() + ->end() + ->end() + ->arrayNode('header_defaults') + ->canBeEnabled() + ->info('Set header to default value if it does not exist.') + ->fixXmlConfig('header') + ->children() + ->arrayNode('headers') + ->info('Keys are the header names, values the header values') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('scalar')->end() ->end() - ->end() // End decoder plugin - - ->arrayNode('history') + ->end() + ->end() + ->arrayNode('header_set') ->canBeEnabled() - ->children() - ->scalarNode('journal') - ->info('This must be a service id to a service implementing Http\Client\Plugin\Journal') - ->isRequired() - ->cannotBeEmpty() - ->end() + ->info('Set headers to requests. If the header does not exist it wil be set, if the header already exists it will be replaced.') + ->fixXmlConfig('header') + ->children() + ->arrayNode('headers') + ->info('Keys are the header names, values the header values') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('scalar')->end() ->end() - ->end() // End history plugin - - ->arrayNode('logger') - ->canBeDisabled() - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('logger') - ->info('This must be a service id to a service implementing Psr\Log\LoggerInterface') - ->defaultValue('logger') - ->cannotBeEmpty() - ->end() - ->scalarNode('formatter') - ->info('This must be a service id to a service implementing Http\Message\Formatter') - ->defaultNull() - ->end() + ->end() + ->end() + ->arrayNode('header_remove') + ->canBeEnabled() + ->info('Remove headers from requests.') + ->fixXmlConfig('header') + ->children() + ->arrayNode('headers') + ->info('List of header names to remove') + ->prototype('scalar')->end() ->end() - ->end() // End logger plugin + ->end() + ->end() + ->end() + ->end(); - ->arrayNode('redirect') - ->canBeDisabled() - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('preserve_header')->defaultTrue()->end() - ->scalarNode('use_default_for_multiple')->defaultTrue()->end() - ->end() - ->end() // End redirect plugin + return $node; + } - ->arrayNode('retry') - ->canBeDisabled() - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('retry')->defaultValue(1)->end() - ->end() - ->end() // End retry plugin + /** + * Add the definitions for shared plugin configurations. + * + * @param ArrayNodeDefinition $pluginNode The node to add to. + * @param bool $disableAll Some shared plugins are enabled by default. On the client, all are disabled by default. + */ + private function addSharedPluginNodes(ArrayNodeDefinition $pluginNode, $disableAll = false) + { + $children = $pluginNode->children(); - ->arrayNode('stopwatch') - ->canBeDisabled() - ->addDefaultsIfNotSet() + $children->append($this->createAuthenticationPluginNode()); + + $children->arrayNode('cache') + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('cache_pool') + ->info('This must be a service id to a service implementing Psr\Cache\CacheItemPoolInterface') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('stream_factory') + ->info('This must be a service id to a service implementing Http\Message\StreamFactory') + ->defaultValue('httplug.stream_factory') + ->cannotBeEmpty() + ->end() + ->arrayNode('config') + ->addDefaultsIfNotSet() ->children() - ->scalarNode('stopwatch') - ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch') - ->defaultValue('debug.stopwatch') - ->cannotBeEmpty() - ->end() + ->scalarNode('default_ttl')->defaultNull()->end() + ->scalarNode('respect_cache_headers')->defaultTrue()->end() ->end() - ->end() // End stopwatch plugin + ->end() + ->end() + ->end(); + // End cache plugin + + $children->arrayNode('cookie') + ->canBeEnabled() + ->children() + ->scalarNode('cookie_jar') + ->info('This must be a service id to a service implementing Http\Message\CookieJar') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->end() + ->end(); + // End cookie plugin + + $decoder = $children->arrayNode('decoder'); + $disableAll ? $decoder->canBeEnabled() : $decoder->canBeDisabled(); + $decoder->addDefaultsIfNotSet() + ->children() + ->scalarNode('use_content_encoding')->defaultTrue()->end() + ->end() + ->end(); + // End decoder plugin + + $children->arrayNode('history') + ->canBeEnabled() + ->children() + ->scalarNode('journal') + ->info('This must be a service id to a service implementing Http\Client\Plugin\Journal') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->end() + ->end(); + // End history plugin + $logger = $children->arrayNode('logger'); + $disableAll ? $logger->canBeEnabled() : $logger->canBeDisabled(); + $logger->addDefaultsIfNotSet() + ->children() + ->scalarNode('logger') + ->info('This must be a service id to a service implementing Psr\Log\LoggerInterface') + ->defaultValue('logger') + ->cannotBeEmpty() + ->end() + ->scalarNode('formatter') + ->info('This must be a service id to a service implementing Http\Message\Formatter') + ->defaultNull() + ->end() + ->end() + ->end(); + // End logger plugin + + $redirect = $children->arrayNode('redirect'); + $disableAll ? $redirect->canBeEnabled() : $redirect->canBeDisabled(); + $redirect->addDefaultsIfNotSet() + ->children() + ->scalarNode('preserve_header')->defaultTrue()->end() + ->scalarNode('use_default_for_multiple')->defaultTrue()->end() + ->end() + ->end(); + // End redirect plugin + + $retry = $children->arrayNode('retry'); + $disableAll ? $retry->canBeEnabled() : $retry->canBeDisabled(); + $retry->addDefaultsIfNotSet() + ->children() + ->scalarNode('retry')->defaultValue(1)->end() // TODO: should be called retries for consistency with the class + ->end() + ->end(); + // End retry plugin + + $stopwatch = $children->arrayNode('stopwatch'); + $disableAll ? $stopwatch->canBeEnabled() : $stopwatch->canBeDisabled(); + $stopwatch->addDefaultsIfNotSet() + ->children() + ->scalarNode('stopwatch') + ->info('This must be a service id to a service extending Symfony\Component\Stopwatch\Stopwatch') + ->defaultValue('debug.stopwatch') + ->cannotBeEmpty() ->end() ->end() ->end(); + // End stopwatch plugin } /** - * Add configuration for authentication plugin. + * Create configuration for authentication plugin. * - * @return ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition + * @return NodeDefinition Definition for the authentication node in the plugins list. */ - private function addAuthenticationPluiginNode() + private function createAuthenticationPluginNode() { $builder = new TreeBuilder(); $node = $builder->root('authentication'); diff --git a/DependencyInjection/HttplugExtension.php b/DependencyInjection/HttplugExtension.php index 9ec64a3d..e5035c3b 100644 --- a/DependencyInjection/HttplugExtension.php +++ b/DependencyInjection/HttplugExtension.php @@ -14,8 +14,10 @@ use Http\Message\Authentication\BasicAuth; use Http\Message\Authentication\Bearer; use Http\Message\Authentication\Wsse; +use Psr\Http\Message\UriInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; @@ -53,7 +55,7 @@ public function load(array $configs, ContainerBuilder $container) } // Configure toolbar - if ($config['profiling']['enabled']) { + if ($this->isConfigEnabled($container, $config['profiling'])) { $loader->load('data-collector.xml'); if (!empty($config['profiling']['formatter'])) { @@ -70,8 +72,8 @@ public function load(array $configs, ContainerBuilder $container) ; } - $this->configurePlugins($container, $config['plugins']); $this->configureClients($container, $config); + $this->configureSharedPlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed $this->configureAutoDiscoveryClients($container, $config); } @@ -91,7 +93,7 @@ private function configureClients(ContainerBuilder $container, array $config) $first = $name; } - $this->configureClient($container, $name, $arguments, $config['profiling']['enabled']); + $this->configureClient($container, $name, $arguments, $this->isConfigEnabled($container, $config['profiling'])); } // If we have clients configured @@ -108,7 +110,7 @@ private function configureClients(ContainerBuilder $container, array $config) * @param ContainerBuilder $container * @param array $config */ - private function configurePlugins(ContainerBuilder $container, array $config) + private function configureSharedPlugins(ContainerBuilder $container, array $config) { if (!empty($config['authentication'])) { $this->configureAuthentication($container, $config['authentication']); @@ -118,9 +120,9 @@ private function configurePlugins(ContainerBuilder $container, array $config) foreach ($config as $name => $pluginConfig) { $pluginId = 'httplug.plugin.'.$name; - if ($pluginConfig['enabled']) { + if ($this->isConfigEnabled($container, $pluginConfig)) { $def = $container->getDefinition($pluginId); - $this->configurePluginByName($name, $def, $pluginConfig); + $this->configurePluginByName($name, $def, $pluginConfig, $container, $pluginId); } else { $container->removeDefinition($pluginId); } @@ -128,11 +130,13 @@ private function configurePlugins(ContainerBuilder $container, array $config) } /** - * @param string $name - * @param Definition $definition - * @param array $config + * @param string $name + * @param Definition $definition + * @param array $config + * @param ContainerBuilder $container In case we need to add additional services for this plugin + * @param string $serviceId Service id of the plugin, in case we need to add additional services for this plugin. */ - private function configurePluginByName($name, Definition $definition, array $config) + private function configurePluginByName($name, Definition $definition, array $config, ContainerInterface $container, $serviceId) { switch ($name) { case 'cache': @@ -173,6 +177,23 @@ private function configurePluginByName($name, Definition $definition, array $con $definition->replaceArgument(0, new Reference($config['stopwatch'])); break; + /* client specific plugins */ + + case 'add_host': + $uriService = $serviceId.'.host_uri'; + $this->createUri($container, $uriService, $config['host']); + $definition->replaceArgument(0, new Reference($uriService)); + $definition->replaceArgument(1, [ + 'replace' => $config['replace'], + ]); + break; + case 'header_append': + case 'header_defaults': + case 'header_set': + case 'header_remove': + $definition->replaceArgument(0, $config['headers']); + break; + default: throw new \InvalidArgumentException(sprintf('Internal exception: Plugin %s is not handled', $name)); } @@ -181,11 +202,15 @@ private function configurePluginByName($name, Definition $definition, array $con /** * @param ContainerBuilder $container * @param array $config + * + * @return array List of service ids for the authentication plugins. */ - private function configureAuthentication(ContainerBuilder $container, array $config) + private function configureAuthentication(ContainerBuilder $container, array $config, $servicePrefix = 'httplug.plugin.authentication') { + $pluginServices = []; + foreach ($config as $name => $values) { - $authServiceKey = sprintf('httplug.plugin.authentication.%s.auth', $name); + $authServiceKey = sprintf($servicePrefix.'.%s.auth', $name); switch ($values['type']) { case 'bearer': $container->register($authServiceKey, Bearer::class) @@ -208,33 +233,54 @@ private function configureAuthentication(ContainerBuilder $container, array $con throw new \LogicException(sprintf('Unknown authentication type: "%s"', $values['type'])); } - $container->register('httplug.plugin.authentication.'.$name, AuthenticationPlugin::class) - ->addArgument(new Reference($authServiceKey)); + $pluginServiceKey = $servicePrefix.'.'.$name; + $container->register($pluginServiceKey, AuthenticationPlugin::class) + ->addArgument(new Reference($authServiceKey)) + ; + $pluginServices[] = $pluginServiceKey; } + + return $pluginServices; } /** * @param ContainerBuilder $container - * @param string $name + * @param string $clientName * @param array $arguments * @param bool $profiling */ - private function configureClient(ContainerBuilder $container, $name, array $arguments, $profiling) + private function configureClient(ContainerBuilder $container, $clientName, array $arguments, $profiling) { - $serviceId = 'httplug.client.'.$name; + $serviceId = 'httplug.client.'.$clientName; + + $plugins = []; + foreach ($arguments['plugins'] as $plugin) { + list($pluginName, $pluginConfig) = each($plugin); + if ('reference' === $pluginName) { + $plugins[] = $pluginConfig['id']; + } elseif ('authentication' === $pluginName) { + $plugins = array_merge($plugins, $this->configureAuthentication($container, $pluginConfig, $serviceId.'.authentication')); + } else { + $pluginServiceId = $serviceId.'.plugin.'.$pluginName; + $def = clone $container->getDefinition('httplug.plugin'.'.'.$pluginName); + $def->setAbstract(false); + $this->configurePluginByName($pluginName, $def, $pluginConfig, $container, $pluginServiceId); + $container->setDefinition($pluginServiceId, $def); + $plugins[] = $pluginServiceId; + } + } $pluginClientOptions = []; - if ($profiling) { + // Add the stopwatch plugin if (!in_array('httplug.plugin.stopwatch', $arguments['plugins'])) { - // Add the stopwatch plugin - array_unshift($arguments['plugins'], 'httplug.plugin.stopwatch'); + array_unshift($plugins, 'httplug.plugin.stopwatch'); } // Tell the plugin journal what plugins we used $container ->getDefinition('httplug.collector.plugin_journal') - ->addMethodCall('setPlugins', [$name, $arguments['plugins']]) + ->addMethodCall('setPlugins', [$clientName, $plugins]) ; $debugPluginServiceId = $this->registerDebugPlugin($container, $serviceId); @@ -250,7 +296,7 @@ private function configureClient(ContainerBuilder $container, $name, array $argu function ($id) { return new Reference($id); }, - $arguments['plugins'] + $plugins ) ) ->addArgument(new Reference($arguments['factory'])) @@ -290,6 +336,23 @@ function ($id) { } } + /** + * Create a URI object with the default URI factory. + * + * @param ContainerBuilder $container + * @param string $serviceId Name of the private service to create + * @param string $uri String representation of the URI + */ + private function createUri(ContainerBuilder $container, $serviceId, $uri) + { + $container + ->register($serviceId, UriInterface::class) + ->setPublic(false) + ->setFactory([new Reference('httplug.uri_factory'), 'createUri']) + ->addArgument($uri) + ; + } + /** * Make the user can select what client is used for auto discovery. If none is provided, a service will be created * by finding a client using auto discovery. @@ -307,7 +370,7 @@ private function configureAutoDiscoveryClients(ContainerBuilder $container, arra $container, 'auto_discovered_client', [HttpClientDiscovery::class, 'find'], - $config['profiling']['enabled'] + $this->isConfigEnabled($container, $config['profiling']) ); } @@ -322,7 +385,7 @@ private function configureAutoDiscoveryClients(ContainerBuilder $container, arra $container, 'auto_discovered_async', [HttpAsyncClientDiscovery::class, 'find'], - $config['profiling']['enabled'] + $this->isConfigEnabled($container, $config['profiling']) ); } diff --git a/Resources/config/plugins.xml b/Resources/config/plugins.xml index 7980222a..d921f910 100644 --- a/Resources/config/plugins.xml +++ b/Resources/config/plugins.xml @@ -27,5 +27,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Functional/ServiceInstantiationTest.php b/Tests/Functional/ServiceInstantiationTest.php index 7cefc810..8d07db67 100644 --- a/Tests/Functional/ServiceInstantiationTest.php +++ b/Tests/Functional/ServiceInstantiationTest.php @@ -43,7 +43,10 @@ public function testDebugToolbar() $plugins = $journal->getPlugins('acme'); $this->assertEquals([ 'httplug.plugin.stopwatch', + 'httplug.client.acme.plugin.decoder', 'httplug.plugin.redirect', + 'httplug.client.acme.plugin.add_host', + 'httplug.client.acme.authentication.my_basic', ], $plugins); } } diff --git a/Tests/Resources/Fixtures/config/full.php b/Tests/Resources/Fixtures/config/full.php index 0a8ab554..0dd24a6f 100644 --- a/Tests/Resources/Fixtures/config/full.php +++ b/Tests/Resources/Fixtures/config/full.php @@ -13,6 +13,43 @@ 'uri_factory' => 'Http\Message\UriFactory\GuzzleUriFactory', 'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory', ], + 'clients' => [ + 'test' => [ + 'factory' => 'httplug.factory.guzzle6', + 'http_methods_client' => true, + 'plugins' => [ + 'httplug.plugin.redirect', + [ + 'add_host' => [ + 'host' => 'http://localhost', + ], + ], + [ + 'header_set' => [ + 'headers' => [ + 'X-FOO' => 'bar', + ], + ], + ], + [ + 'header_remove' => [ + 'headers' => [ + 'X-FOO', + ], + ], + ], + [ + 'authentication' => [ + 'my_basic' => [ + 'type' => 'basic', + 'username' => 'foo', + 'password' => 'bar', + ], + ], + ] + ], + ], + ], 'profiling' => [ 'enabled' => true, 'formatter' => 'my_toolbar_formatter', diff --git a/Tests/Resources/Fixtures/config/full.xml b/Tests/Resources/Fixtures/config/full.xml index e0ded54b..11a2e2a4 100644 --- a/Tests/Resources/Fixtures/config/full.xml +++ b/Tests/Resources/Fixtures/config/full.xml @@ -14,6 +14,27 @@ Http\Message\UriFactory\GuzzleUriFactory Http\Message\StreamFactory\GuzzleStreamFactory + + httplug.plugin.redirect + + + + + +
bar
+
+
+ + +
X-FOO
+
+
+ + + + + +
diff --git a/Tests/Resources/Fixtures/config/full.yml b/Tests/Resources/Fixtures/config/full.yml index 72341e8e..445c3b68 100644 --- a/Tests/Resources/Fixtures/config/full.yml +++ b/Tests/Resources/Fixtures/config/full.yml @@ -9,6 +9,28 @@ httplug: message_factory: Http\Message\MessageFactory\GuzzleMessageFactory uri_factory: Http\Message\UriFactory\GuzzleUriFactory stream_factory: Http\Message\StreamFactory\GuzzleStreamFactory + clients: + test: + factory: httplug.factory.guzzle6 + http_methods_client: true + plugins: + - 'httplug.plugin.redirect' + - + add_host: + host: http://localhost + - + header_set: + headers: + X-FOO: bar + - + header_remove: + headers: [X-FOO] + - + authentication: + my_basic: + type: basic + username: foo + password: bar profiling: enabled: true formatter: my_toolbar_formatter diff --git a/Tests/Resources/Fixtures/config/invalid_plugin.yml b/Tests/Resources/Fixtures/config/invalid_plugin.yml new file mode 100644 index 00000000..f05c987f --- /dev/null +++ b/Tests/Resources/Fixtures/config/invalid_plugin.yml @@ -0,0 +1,6 @@ +httplug: + clients: + acme: + plugins: + - foobar: + baz: ~ diff --git a/Tests/Resources/app/config/config.yml b/Tests/Resources/app/config/config.yml index c97ad0fe..9856992b 100644 --- a/Tests/Resources/app/config/config.yml +++ b/Tests/Resources/app/config/config.yml @@ -7,4 +7,16 @@ httplug: acme: factory: httplug.factory.curl plugins: + - + decoder: + use_content_encoding: false - httplug.plugin.redirect + - + add_host: + host: "http://localhost:8000" + - + authentication: + my_basic: + type: basic + username: foo + password: bar diff --git a/Tests/Unit/DependencyInjection/ConfigurationTest.php b/Tests/Unit/DependencyInjection/ConfigurationTest.php index df931ea3..ecf09fcc 100644 --- a/Tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/Tests/Unit/DependencyInjection/ConfigurationTest.php @@ -115,7 +115,55 @@ public function testSupportsAllConfigFormats() 'uri_factory' => 'Http\Message\UriFactory\GuzzleUriFactory', 'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory', ], - 'clients' => [], + 'clients' => [ + 'test' => [ + 'factory' => 'httplug.factory.guzzle6', + 'http_methods_client' => true, + 'flexible_client' => false, + 'batch_client' => false, + 'plugins' => [ + [ + 'reference' => [ + 'enabled' => true, + 'id' => 'httplug.plugin.redirect', + ], + ], + [ + 'add_host' => [ + 'enabled' => true, + 'host' => 'http://localhost', + 'replace' => false, + ], + ], + [ + 'header_set' => [ + 'enabled' => true, + 'headers' => [ + 'X-FOO' => 'bar', + ], + ], + ], + [ + 'header_remove' => [ + 'enabled' => true, + 'headers' => [ + 'X-FOO', + ], + ], + ], + [ + 'authentication' => [ + 'my_basic' => [ + 'type' => 'basic', + 'username' => 'foo', + 'password' => 'bar', + ], + ], + ], + ], + 'config' => [], + ], + ], 'profiling' => [ 'enabled' => true, 'formatter' => 'my_toolbar_formatter', @@ -211,6 +259,16 @@ public function testMissingClass() $this->assertProcessedConfigurationEquals([], [$file]); } + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage Unrecognized option "foobar" under "httplug.clients.acme.plugins.0" + */ + public function testInvalidPlugin() + { + $file = __DIR__.'/../../Resources/Fixtures/config/invalid_plugin.yml'; + $this->assertProcessedConfigurationEquals([], [$file]); + } + /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException * @expectedExceptionMessage password, service, username diff --git a/Tests/Unit/DependencyInjection/HttplugExtensionTest.php b/Tests/Unit/DependencyInjection/HttplugExtensionTest.php index 2e0484d5..598a8e07 100644 --- a/Tests/Unit/DependencyInjection/HttplugExtensionTest.php +++ b/Tests/Unit/DependencyInjection/HttplugExtensionTest.php @@ -2,8 +2,10 @@ namespace Http\HttplugBundle\Tests\Unit\DependencyInjection; +use Http\Client\Common\PluginClient; use Http\HttplugBundle\DependencyInjection\HttplugExtension; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; +use Symfony\Component\DependencyInjection\Reference; /** * @author David Buchmann @@ -61,6 +63,80 @@ public function testConfigLoadService() } } + public function testClientPlugins() + { + $this->load([ + 'clients' => [ + 'acme' => [ + 'factory' => 'httplug.factory.curl', + 'plugins' => [ + [ + 'decoder' => [ + 'use_content_encoding' => false, + ], + ], + 'httplug.plugin.redirect', + [ + 'add_host' => [ + 'host' => 'http://localhost:8000', + ], + ], + [ + 'header_append' => [ + 'headers' => ['X-FOO' => 'bar'], + ], + ], + [ + 'header_defaults' => [ + 'headers' => ['X-FOO' => 'bar'], + ], + ], + [ + 'header_set' => [ + 'headers' => ['X-FOO' => 'bar'], + ], + ], + [ + 'header_remove' => [ + 'headers' => ['X-FOO'], + ], + ], + [ + 'authentication' => [ + 'my_basic' => [ + 'type' => 'basic', + 'username' => 'foo', + 'password' => 'bar', + ], + ], + ], + ], + ], + ], + ]); + + $plugins = [ + 'httplug.plugin.stopwatch', + 'httplug.client.acme.plugin.decoder', + 'httplug.plugin.redirect', + 'httplug.client.acme.plugin.add_host', + 'httplug.client.acme.plugin.header_append', + 'httplug.client.acme.plugin.header_defaults', + 'httplug.client.acme.plugin.header_set', + 'httplug.client.acme.plugin.header_remove', + 'httplug.client.acme.authentication.my_basic', + ]; + $pluginReferences = array_map(function ($id) { + return new Reference($id); + }, $plugins); + + $this->assertContainerBuilderHasService('httplug.client.acme'); + foreach ($plugins as $id) { + $this->assertContainerBuilderHasService($id); + } + $this->assertContainerBuilderHasServiceDefinitionWithArgument('httplug.client.acme', 0, $pluginReferences); + } + public function testNoProfilingWhenToolbarIsDisabled() { $this->load(