Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Named Autowiring & Scoped HTTP Clients

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

In MixRepository, it would be cool if we didn't need to specify the host name when we make the HTTP request. Like, it'd be great if that were preconfigured and we only needed to include the path. Also, pretty soon, we're going to configure an access token that will be used when we make requests to the GitHub API. We could pass that access token manually here in our service, but how cool would it be if the HttpClient service came preconfigured to always include the access token?

So, does Symfony have a way for us to, sort of, "preconfigure" the HttpClient service? It does! It's called "scoped clients": a feature of HttpClient where you can create multiple HttpClient services, each preconfigured differently.

Creating a Scoped Client

Here's how it works. Open up config/packages/framework.yaml. To create a scoped client, under the framework key, add http_client followed by scoped_clients. Now, give your scoped client a name, like githubContentClient... since we're using a part of their API that returns the content of files. Also add base_uri, go copy the host name over here... and paste:

... line 1
framework:
... lines 3 - 19
http_client:
scoped_clients:
githubContentClient:
base_uri: https://raw.githubusercontent.com
... lines 24 - 30

Remember: the purpose of these config files is to change the services in the container. The end result of this new code is that a second HttpClient service will be added to the container. We'll see that in a minute. And, by the way, there's no way that you could just guess that you need http_client and scoped_clients keys to make this work. Configuration is the kind of thing where you really need to rely on the documentation.

Anyways, now that we've preconfigured this client, we should be able to go into MixRepository and make a request directly to the path:

... lines 1 - 9
class MixRepository
{
... lines 12 - 18
public function findAll(): array
{
return $this->cache->get('mixes_data', function(CacheItemInterface $cacheItem) {
... line 22
$response = $this->httpClient->request('GET', '/SymfonyCasts/vinyl-mixes/main/mixes.json');
... lines 24 - 25
});
}
}

But if we head over and refresh... ah...

Invalid URL: scheme is missing [...]. Did you forget to add "http(s)://"?

I didn't think we forgot... since we configured it via the base_uri option... but apparently that didn't work. And you may have guessed why. Find your terminal and run:

php bin/console debug:autowiring client

There are now two HttpClient services in the container: The normal, non-configured one and the one that we just configured. Apparently, in MixRepository, Symfony is still passing us the unconfigured HttpClient service.

How can I be sure? Well, think back to how autowiring works. Symfony looks at the type-hint of our argument, which is Symfony\Contracts\HttpClient\HttpClientInterface, and then looks in the container to find a service whose ID is an exact match. It's that simple

Fetching the Named Version of a Service

So... if there are multiple services with the same "type" in our container, is only the main one autowireable? Fortunately, no! We can use something called "named autowiring"... and it's already showing us how. If we type-hint an argument with HttpClientInterface and name the argument $githubContentClient, Symfony will pass us the second one.

Let's try it: change the argument from $httpClient to $githubContentClient:

... lines 1 - 9
class MixRepository
{
public function __construct(
private HttpClientInterface $githubContentClient,
... lines 14 - 16
) {}
... lines 18 - 27
}

and now... it doesn't work. Whoops...

Undefined property: MixRepository::$httpClient

That's... just me being careless. When I changed the argument name, it changed the property name. So... we need to adjust the code below:

... lines 1 - 9
class MixRepository
{
public function __construct(
private HttpClientInterface $githubContentClient,
... lines 14 - 16
) {}
... line 18
public function findAll(): array
{
return $this->cache->get('mixes_data', function(CacheItemInterface $cacheItem) {
... line 22
$response = $this->githubContentClient->request('GET', '/SymfonyCasts/vinyl-mixes/main/mixes.json');
... lines 24 - 25
});
}
}

And now... it's alive! We just autowired a specific HttpClientInterface service!

Next, let's tackle another tricky problem with autowiring by learning how to fetch one of the many services in our container that is totally not available for autowiring.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "knplabs/knp-time-bundle": "^1.18", // v1.19.0
        "symfony/asset": "6.1.*", // v6.1.0-RC1
        "symfony/console": "6.1.*", // v6.1.0-RC1
        "symfony/dotenv": "6.1.*", // v6.1.0-RC1
        "symfony/flex": "^2", // v2.1.8
        "symfony/framework-bundle": "6.1.*", // v6.1.0-RC1
        "symfony/http-client": "6.1.*", // v6.1.0-RC1
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/runtime": "6.1.*", // v6.1.0-RC1
        "symfony/twig-bundle": "6.1.*", // v6.1.0-RC1
        "symfony/ux-turbo": "^2.0", // v2.1.1
        "symfony/webpack-encore-bundle": "^1.13", // v1.14.1
        "symfony/yaml": "6.1.*", // v6.1.0-RC1
        "twig/extra-bundle": "^2.12|^3.0", // v3.4.0
        "twig/twig": "^2.12|^3.0" // v3.4.0
    },
    "require-dev": {
        "symfony/debug-bundle": "6.1.*", // v6.1.0-RC1
        "symfony/maker-bundle": "^1.41", // v1.42.0
        "symfony/stopwatch": "6.1.*", // v6.1.0-RC1
        "symfony/web-profiler-bundle": "6.1.*" // v6.1.0-RC1
    }
}