Symfony Bundle
This bundle integrates HTTPlug with the Symfony framework. The bundle helps to register services for all your clients and makes sure all the configuration is in one place. The bundle also features a profiling plugin with information about your requests.
This guide explains how to configure HTTPlug in the Symfony framework. See the HTTPlug Tutorial for examples how to use HTTPlug in general.
Installation
HTTPlug works with any HTTP client implementation that provides PSR-18 or a HTTPlug adapter. The flex recipe installs the php-http curl client. See Clients & Adapters for a list of clients known to work with the bundle.
You can find all available configuration at the full configuration page.
Using Symfony Flex
HttplugBundle has a Symfony Flex recipe that will set it up with default configuration:
$ composer require php-http/httplug-bundle
Without Symfony Flex
Install the HTTPlug bundle with composer and enable it in your AppKernel.php.
$ composer require php-http/httplug-bundle [some-adapter?]
If you already added the HTTPlug client requirement to your project, then you
only need to add php-http/httplug-bundle
. Otherwise, you also need to
specify an HTTP client to use - see Clients & Adapters for a list of available
clients.
Activate Bundle in Symfony 4 and newer
// config/bundles.php
return [
...
Http\HttplugBundle\HttplugBundle::class => ['all' => true],
];
Activate Bundle in Symfony 3
// app/AppKernel.php
public function registerBundles()
{
$bundles = [
// ...
new Http\HttplugBundle\HttplugBundle(),
];
}
Usage
httplug:
plugins:
logger: ~
clients:
acme:
factory: 'httplug.factory.guzzle6'
plugins: ['httplug.plugin.logger']
config:
timeout: 2
$request = $this->container->get(\Psr\Http\Message\RequestFactoryInterface::class)->createRequest('GET', 'http://example.com');
$response = $this->container->get('httplug.client.acme')->sendRequest($request);
Autowiring
The first configured client is considered the “default” client. The default
clients are available for autowiring: The PSR-18 Psr\Http\Client\ClientInterface
and the Http\Client\HttpAsyncClient
.
Autowiring can be convenient to build your application.
However, if you configured several different clients and need to be sure that the correct client is used in each service, it can also hide mistakes. Therefore you can disable autowiring with a configuration option:
httplug:
default_client_autowiring: false
When using this bundle with Symfony 5.3 or newer, you can use the Symfony
#[Target] attribute to select a client by name. For a client configured as
httplug.clients.acme
, this would be:
use Psr\Http\Client\ClientInterface;
use Symfony\Component\DependencyInjection\Attribute as DI;
final class MyService
{
public function __construct(
#[DI\Target('acme')] ClientInterface $client
) {}
}
Web Debug Toolbar
When using a client configured with HttplugBundle
, you will get debug
information in the web debug toolbar. It will tell you how many request were
made and how many of those that were successful or not. It will also show you
detailed information about each request.
The web profiler page will show you lots of information about the request and also how different plugins changes the message. See example screen shots below.
The body of the HTTP messages is not captured by default because of performance
reasons. Turn this on by changing the captured_body_length
configuration.
httplug:
profiling:
captured_body_length: 1000 # Capture the first 1000 chars of the HTTP body
You can set captured_body_length
to null
to avoid body limitation size.
httplug:
profiling:
captured_body_length: ~ # Avoid truncation of body content
The profiling is automatically turned off when kernel.debug = false
. You can
also disable the profiling by configuration.
httplug:
profiling: false
You can configure the bundle to show debug information for clients found with
discovery. You may also force a specific client to be found when a third party
library is using discovery. The configuration below makes sure the client with
service id httplug.client.my_guzzle7
is returned when calling
Psr18ClientDiscovery::find()
. It does also make sure to show debug info for
asynchronous clients.
Note
Ideally, you would always use dependency injection and never rely on auto discovery to find a client.
httplug:
clients:
my_guzzle7:
factory: 'httplug.factory.guzzle7'
discovery:
client: 'httplug.client.my_guzzle7'
async_client: 'auto'
For normal clients, the auto discovery debug info is enabled by default. For
async clients, debug is not enabled by default to avoid errors when using the
bundle with a client that can not do async. To get debug information for async
clients, set discovery.async_client
to 'auto'
or an explicit client.
You can turn off all interaction of the bundle with auto discovery by setting
the value of discovery.client
to false
.
Discovery of Factory Classes
You can specify all the factory classes for you client. The following example shows how you configure factory classes using Guzzle:
httplug:
classes:
client: Http\Adapter\Guzzle7\Client
psr17_request_factory: GuzzleHttp\Psr7\HttpFactory
psr17_response_factory: GuzzleHttp\Psr7\HttpFactory
psr17_uri_factory: GuzzleHttp\Psr7\HttpFactory
psr17_stream_factory: GuzzleHttp\Psr7\HttpFactory
Configure Clients
You can configure your clients with default options. These default values will be specific to you client you are using. The clients are later registered as services.
httplug:
clients:
my_guzzle7:
factory: 'httplug.factory.guzzle7'
config:
# These options are given to Guzzle without validation.
defaults:
# timeout if connection is not established after 4 seconds
timeout: 4
acme:
factory: 'httplug.factory.curl'
config:
# timeout if connection is not established after 4 seconds
CURLOPT_CONNECTTIMEOUT: 4
# throttle sending data if more than ~ 1MB / second
CURLOPT_MAX_SEND_SPEED_LARGE: 1000000
$httpClient = $this->container->get('httplug.client.my_guzzle7');
$httpClient = $this->container->get('httplug.client.acme');
// will be the same as ``httplug.client.my_guzzle7``
$httpClient = $this->container->get(\Psr\Http\Client\ClientInterface::class);
The bundle has client factory services that you can use to build your client.
If you need a very custom made client you could create your own factory service
implementing Http\HttplugBundle\ClientFactory\ClientFactory
. The built-in
services are:
httplug.factory.curl
httplug.factory.buzz
httplug.factory.guzzle6
httplug.factory.guzzle7
httplug.factory.react
httplug.factory.socket
httplug.factory.symfony
httplug.factory.mock
(Installphp-http/mock-client
first)
Note
Added in version 1.10: If you already have a client service registered you can skip using the factory
and use the service
key instead.
httplug:
clients:
my_client:
service: 'my_custom_client_service'
Added in version 1.17: All configured clients are tagged with 'httplug.client'
(the value of the constant Http\HttplugBundle\DependencyInjection\HttplugExtension::HTTPLUG_CLIENT_TAG
),
so they can be easily retrieved. This is useful for functional tests, where one might want to replace every
configured client with a mock client, so they can be retrieved and configured later
use Http\HttplugBundle\DependencyInjection\HttplugExtension;
use Http\Mock\Client;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/** @var ContainerBuilder $container */
$serviceIds = array_keys($container->findTaggedServiceIds(HttplugExtension::HTTPLUG_CLIENT_TAG));
foreach ($serviceIds as $serviceId) {
$decoratingServiceId = \sprintf(
'%s.mock',
$serviceId
);
$container->register($decoratingServiceId, Client::class)
->setDecoratedService($serviceId)
->setPublic(true);
}
Plugins
Clients can have plugins that act on the request before it is sent out and/or
on the response before it is returned to the caller. Generic plugins from
php-http/client-common
(e.g. retry or redirect) can be configured globally.
You can tell the client which of those plugins to use, as well as specify the
service names of custom plugins that you want to use.
Additionally you can configure any of the php-http/plugins
specifically on
a client. For some plugins this is the only place where they can be configured.
The order in which you specify the plugins does matter.
See the plugin documentation for more information on the plugins.
See full configuration for the full list of plugins you can configure through this bundle. If a plugin is not available in the configuration, you can configure it as a service and reference the plugin by service id as you would do for a custom plugin.
You can configure many of the plugins directly on the client:
// config.yml
httplug:
clients:
acme:
factory: 'httplug.factory.guzzle6'
plugins:
- error:
only_server_exception: true
- add_host:
host: "http://localhost:8000"
- header_defaults:
headers:
"X-FOO": bar
- authentication:
acme_basic:
type: 'basic'
username: 'my_username'
password: 'p4ssw0rd'
Alternatively, the same configuration also works on a global level. With this,
you can configure plugins once and then use them in several clients. The plugin
service names follow the pattern httplug.plugin.<name>
:
// config.yml
httplug:
plugins:
cache:
cache_pool: 'my_cache_pool'
clients:
acme:
factory: 'httplug.factory.guzzle6'
plugins:
- 'httplug.plugin.cache'
app:
plugins:
- 'httplug.plugin.cache'
Note
To configure HTTP caching, you need to require php-http/cache-plugin
in
your project. It is available as a separate composer package.
Configure a Custom Plugin
To use a custom plugin or when you need specific configuration that is not covered by the bundle configuration, you can configure the plugin as a normal Symfony service and then reference that service name in the plugin list of your client:
// services.yml
acme_plugin:
class: Acme\Plugin\MyCustomPlugin
arguments: ["%some_parameter%"]
// config.yml
httplug:
clients:
acme:
factory: 'httplug.factory.guzzle6'
plugins:
- 'acme_plugin'
If you want to configure your plugin using the bundle configuration, you can
create a class that implements PluginConfigurator
and define configurator
plugins.
final class CustomPluginConfigurator implements PluginConfigurator
{
public static function getConfigTreeBuilder() : TreeBuilder
{
$treeBuilder = new TreeBuilder('custom_plugin');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->end();
return $treeBuilder;
}
public function create(array $config) : CustomPlugin
{
return new CustomPlugin($config['name']);
}
}
// config.yml
httplug:
clients:
acme:
factory: 'httplug.factory.guzzle6'
plugins:
- configurator:
id: 'App\CustomPluginConfigurator'
config:
name: 'foo'
Authentication
You can configure a client with authentication. Valid authentication types are
basic
, bearer
, service
, wsse
, query_param
and header
. See more examples at the
full configuration.
// config.yml
httplug:
plugins:
authentication:
my_wsse:
type: 'wsse'
username: 'my_username'
password: 'p4ssw0rd'
clients:
acme:
factory: 'httplug.factory.guzzle6'
plugins: ['httplug.plugin.authentication.my_wsse']
Warning
Using query parameters for authentication is not safe. The auth params will appear on the URL and we recommend to NOT log your request, especially on production side.
VCR Plugin
The VCR Plugin allows to record and/or replay HTTP requests. You can configure the mode you want, how to find recorded responses and how to match requests with responses. The mandatory options are:
// config.yml
httplug:
clients:
acme:
plugins:
- vcr:
mode: replay # record | replay | replay_or_record
fixtures_directory: '%kernel.project_dir%/fixtures/http' # mandatory for "filesystem" recorder
# recorder: filesystem
See Full configuration for the full list of configuration options.
Warning
You have to explicitly require this plugin with composer (composer require --dev php-http/vcr-plugin
) before
using it, as it isn’t included by default.
Special HTTP Clients
If you want to use the FlexibleHttpClient
or HttpMethodsClient
from the
php-http/client-common
package, you may specify that on the client configuration.
// config.yml
httplug:
clients:
acme:
factory: 'httplug.factory.guzzle6'
flexible_client: true
foobar:
factory: 'httplug.factory.guzzle6'
http_methods_client: true
List of Services
Service id |
Description |
---|---|
|
Service* that provides the PsrHttpMessageRequestFactoryInterface |
|
Service* that provides the PsrHttpMessageResponseFactoryInterface |
|
Service* that provides the PsrHttpMessageUriFactoryInterface |
|
Service* that provides the PsrHttpMessageStreamFactoryInterface |
|
There is one service per named client. |
|
If there is a client named “default”, this service is an alias to
that client, otherwise it is an alias to the first client configured.
|
httplug.plugin.content_length httplug.plugin.decoder httplug.plugin.logger httplug.plugin.redirect httplug.plugin.retry httplug.plugin.stopwatch |
These are plugins that are enabled by default.
These services are private and should only be used to configure
clients or other services.
|
httplug.plugin.cache httplug.plugin.cookie httplug.plugin.history httplug.plugin.error httplug.plugin.throttle |
These are plugins that are disabled by default and only get
activated when configured.
These services are private and should only be used to configure
clients or other services.
|
* These services are always an alias to another service. You can specify your own service or leave the default, which is the same name with `.default` appended.
Usage for Reusable Bundles
Rather than code against specific HTTP clients, you want to use the HTTPlug
Client
interface. To avoid building your own infrastructure to define
services for the client, simply require: php-http/httplug-bundle
in your
bundles composer.json
. You SHOULD provide a configuration option to specify
which HTTP client service to use for each of your services. This option should
default to httplug.client
. This way, the default case needs no additional
configuration for your users, but they have the option of using specific
clients with each of your services.
The only steps they need is require
one of the adapter implementations in
their projects composer.json
and instantiating the HttplugBundle
in
their kernel.
Mock Responses In Functional Tests
First thing to do is add the php-http/mock-client to your require-dev
section.
Then, use the mock client factory in your test environment configuration:
# config_test.yml
httplug:
clients:
my_awesome_backend:
factory: 'httplug.factory.mock' # replace factory
The client is always wrapped into a plugin client. Therefore you need to access
the inner client to get the mock client. It is available in the container with
the suffix .inner
. For the example above, the full name is
httplug.clients.my_awesome_backend.inner
.
If you enable a decorator like http_methods_client: true
, the actual mock
client will be at httplug.client.my_awesome_backend.http_methods.inner
. Use
the container:debug
command to make sure you grab the correct service.
To mock a response in your tests, do:
// SomeWebTestCase.php
$client = static::createClient();
// If your test has the client (BrowserKit) make multiple requests, you need to disable reboot as the kernel is rebooted on each request.
// $client->disableReboot();
$response = $this->createMock(Psr\\Http\Message\ResponseInterface:class);
$response->method('getBody')->willReturn(/* Psr\Http\Message\Interface instance containing expected response content. */);
$client->getContainer()->get('httplug.clients.my_awesome_backend.client')->addResponse($response);
If you do not specify the factory in your configuration, you can also directly overwrite the HTTPlug services:
# config/services_test.yaml
services:
# overwrite the http clients for mocking
httplug.client.my_awesome_backend:
class: Http\Mock\Client
public: true
With this method, the plugin client is not applied. However, if you configure a
decorator, your mock client will still be decorated and the mock available as
service ...<decorator>.inner
.
Read more on how the mock client works in the mock client documentation.