Problem/Motivation
Sometimes we need a custom HTTP client for some custom services, usually for testing purposes. For example, to provide mocked responses for outgoing HTTP requests in functional tests.
For those cases, Drupal provides http_client_factory service, which can provide http_client with custom options.
But in tests, we usually need to provide a custom HTTP Client, not the original Guzzle class. And we can't do dependency injection here and replace the default class Drupal\Core\Http\ClientFactory with a custom one because it doesn't implement any interface.
Steps to reproduce
1. Create a service that calls some external HTTP API, using an HTTP client from the factory, with custom options.
2. Try to create a test module for functional tests, which overrides the client factory and provides a custom HTTP Client with mocked responses.
You will get an error like this:
TypeError: Argument #1 ($httpClientFactory) must be of type Drupal\Core\Http\ClientFactory, Drupal\my_module_test\TestClientFactory given.
Proposed resolution
The optimal way to resolve this issue is to make Drupal\Core\Http\ClientFactoryInterface and use it as the param type in the service constructors, instead of the Drupal Core class.
With this, we can easily create a custom factory, that implements this interface, and replace the core factory with a custom one.
Also, there is a workaround possible like this:
/**
* MyModule constructor.
*
* @param \Drupal\Core\Http\ClientFactory|\Drupal\my_module_test\TestClientFactory $httpClientFactory
* The HTTP client factory service.
*/
public function __construct(
protected \Drupal\Core\Http\ClientFactory|\Drupal\my_module_test\TestClientFactory $httpClientFactory,
) {
}
But this looks pretty ugly because we should not mention test assets in the main code.
So, with the suggested change we can type just an interface like this:
/**
* MyModule constructor.
*
* @param \Drupal\Core\Http\ClientFactoryInterface $httpClientFactory
* The HTTP client factory service.
*/
public function __construct(
protected \Drupal\Core\Http\ClientFactoryInterface $httpClientFactory,
) {
}
What do you think about this idea?
Remaining tasks
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
Issue fork drupal-3463251
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
murzI made an MR https://git.drupalcode.org/project/drupal/-/merge_requests/8883 with implementing the
Drupal\Core\Http\ClientFactoryInterface- please review.Comment #4
quietone commentedChanges are made on on 11.x (our main development branch) first, and are then back ported as needed according to our policies.
It is my understanding the the 'Needs Review Queue Initiative' tag is added after the issue has been reviewed by that initiative, so they can monitor the activity on reviewed issue. The definition of this special tag supports my thinking. Am I wrong?
Comment #5
smustgrave commentedAppears to have phpstan issues
@quietone that's a good question not sure we ever decided on that.
Comment #6
murz> Appears to have phpstan issues
Yes, but they are not related to the changes at all. And I have no idea about how to fix this error, only remove the test. Here is an issue about this: https://github.com/guzzle/guzzle/issues/3114
Comment #7
smustgrave commentedWould first try rebasing as current MR is 67 commits behind.
Then can see if it passes but can't mark it with failing checks.
Comment #9
joachim commentedRebased, but there is a change in core/tests/Drupal/Tests/Core/Http/ClientFactoryTest.php that doesn't look necessary to me. We shouldn't need to change any tests!
Removing pointless tags.
Comment #10
joachim commentedAlso, having a __construct in an interface is wrong.