Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Autoconexión con nombre y clientes HTTP con alcance

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

En MixRepository, sería genial que no tuviéramos que especificar el nombre del host cuando hacemos la petición HTTP. Sería genial que eso estuviera preconfigurado y sólo tuviéramos que incluir la ruta. Además, muy pronto, vamos a configurar un token de acceso que se utilizará cuando hagamos peticiones a la API de GitHub. Podríamos pasar ese token de acceso manualmente aquí en nuestro servicio, pero ¿a qué sería genial que el servicio HttpClient viniera preconfigurado para incluir siempre el token de acceso?

Entonces, ¿tiene Symfony una forma de "preconfigurar" el servicio HttpClient? ¡La tiene! Se llama "scoped clients": una característica de HttpClient que permite crear varios servicios HttpClient, cada uno preconfigurado de forma diferente.

Crear un Scoped Client

Así es como funciona. Abre config/packages/framework.yaml. Para crear un scoped client, bajo la clave framework, añade http_client seguido de scoped_clients. Ahora, dale a tu scoped client un nombre, como githubContentClient... ya que estamos utilizando una parte de su API que devuelve el contenido de los archivos. Añade también base_uri, ve copiando el nombre del host por aquí... y pégalo:

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

Recuerda: el objetivo de estos archivos de configuración es cambiar los servicios del contenedor. El resultado final de este nuevo código es que se añadirá un segundo servicio HttpClient al contenedor. Lo veremos dentro de un minuto. Y, por cierto, no hay forma de que adivines que necesitas las claves http_client y scoped_clientspara que esto funcione. La configuración es el tipo de cosa en la que realmente tienes que confiar en la documentación.

De todos modos, ahora que hemos preconfigurado este cliente, deberíamos poder entrar enMixRepository y hacer una petición directamente a la ruta:

... 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
});
}
}

Pero si nos dirigimos y refrescamos... ah...

URL no válida: falta el esquema [...]. ¿Te has olvidado de añadir "http(s)://"?

No creí que nos hubiéramos olvidado... ya que lo configuramos mediante la opción base_uri... pero parece que no funcionó. Y puede que hayas adivinado por qué. Busca tu terminal y ejecuta:

php bin/console debug:autowiring client

Ahora hay dos servicios HttpClient en el contenedor: El normal, no configurado, y el que acabamos de configurar. Aparentemente, enMixRepository, Symfony nos sigue pasando el servicio HttpClient no configurado.

¿Cómo puedo estar seguro? Bueno, piensa en cómo funciona el autocableado. Symfony mira el tipo de pista de nuestro argumento, que esSymfony\Contracts\HttpClient\HttpClientInterface, y luego busca en el contenedor un servicio cuyo ID coincida exactamente. Es así de sencillo

Obtener la versión con nombre de un servicio

Entonces... si hay varios servicios con el mismo "tipo" en nuestro contenedor, ¿sólo el principal es autoconvocable? Afortunadamente, ¡no! Podemos utilizar algo llamado "autoconexión con nombre"... y ya nos muestra cómo hacerlo. Si tecleamos un argumento conHttpClientInterface y nombramos el argumento $githubContentClient, Symfony nos pasará el segundo.

Probemos: cambiemos el argumento de $httpClient a $githubContentClient:

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

y ahora... no funciona. Ups...

Propiedad no definida MixRepository::$httpClient

Eso es... que me he descuidado. Cuando cambié el nombre del argumento, cambió el nombre de la propiedad. Así que... hay que ajustar el código de abajo:

... 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
});
}
}

Y ahora... ¡está vivo! ¡Acabamos de autocablear un servicio específico de HttpClientInterface!

A continuación, vamos a abordar otro problema complicado con la autoconexión, aprendiendo a obtener uno de los muchos servicios de nuestro contenedor que no está disponible para la autoconexión.

Leave a comment!

2
Login or Register to join the conversation
wh Avatar

Why does the httpClient has to be under the framework key? When I want to do that with other Symfony services, like... idk pre-set some cache value or whatever. How do I know under which key to create it on?

Reply

Hey @wh!

SUCH a good question. As you know, the purpose of any of this config is to help "configure and add services to the container". The short answer to your question is that the bundle that provides the http client service is FrameworkBundle. By using framework, we are passing this config to FrameworkBundle. The same is true for cache config: it also lives under framework because FrameworkBundle provides the cache services.

In other situations, other bundles provide other services. For example, when we want to configure something about Twig (e.g. add a global variable or add a form theme), we do that under the twig key because TwigBundle provides the Twig services.

But, how could a user possibly know which services come from which bundles? Fair question! FrameworkBundle is huge, and it provides MANY of core services you use, which is why so many pieces of config live under framework. And, there's no huge, scientific reason why, for example, "twig services" were put into a separate TwigBundle bug "http client" services were put into the core FrameworkBundle. The real answer to your question about config is: read the docs -e.g. search for "how do I use Symfony's HTTP Client" and you'll find docs with the configuration. Because, even if you knew that the HTTP client services came from FrameworkBundle, the correct config still can't be guessed: it's definitely one spot where you should lean on the docs.

But, hopefully this answer gives you some insight into why framework in this case but other root keys for other services.

Cheers!

1 Reply
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
    }
}