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

Command icon 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

Murz created an issue. See original summary.

murz’s picture

Status: Active » Needs review
Issue tags: +Needs Review Queue Initiative

I made an MR https://git.drupalcode.org/project/drupal/-/merge_requests/8883 with implementing the Drupal\Core\Http\ClientFactoryInterface - please review.

quietone’s picture

Version: 11.0.x-dev » 11.x-dev

Changes 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?

smustgrave’s picture

Status: Needs review » Needs work
        Ignored error pattern #^Call to deprecated method getConfig\(\) of                    
         class GuzzleHttp\\Client\:                                                            
         Client\:\:getConfig will be removed in guzzlehttp/guzzle\:8\.0\.$# in                 
         path                                                                                  
         /builds/issue/drupal-3463251/core/tests/Drupal/Tests/Core/Http/ClientFactoryTest.php  
         was not matched in reported errors.                                                   
  58     Call to deprecated method getConfig() of interface                                    
         GuzzleHttp\ClientInterface:                                                           
         ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0.                  
         🪪  method.deprecated                                                                 
 ------ -----------------------------

Appears to have phpstan issues

@quietone that's a good question not sure we ever decided on that.

murz’s picture

Status: Needs work » Needs review

> 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

smustgrave’s picture

Status: Needs review » Needs work

Would first try rebasing as current MR is 67 commits behind.

Then can see if it passes but can't mark it with failing checks.

mrinalini9 made their first commit to this issue’s fork.

joachim’s picture

Issue tags: -http_client, -ClientFactory, -http_client_factory, -GuzzleHttp

Rebased, 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.

joachim’s picture

Also, having a __construct in an interface is wrong.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.