gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Todavía no tenemos una base de datos... y eso lo dejaremos para un futuro tutorial. Pero para hacer las cosas un poco más divertidas, he creado un repositorio de GitHub - https://github.com/SymfonyCasts/vinyl-mixes - con un archivo mixes.json
que contiene una base de datos falsa de mezclas de vinilos. Hagamos una petición HTTP desde nuestra aplicación Symfony a este archivo y usémoslo como nuestra base de datos temporal.
Entonces... ¿cómo podemos hacer peticiones HTTP en Symfony? Bueno, hacer una petición HTTP es un trabajo, y -dilo conmigo ahora- "El trabajo lo hace un servicio". Así que la siguiente pregunta es ¿Existe ya un servicio en nuestra aplicación que pueda hacer peticiones HTTP?
¡Averigüémoslo! Dirígete a tu terminal y ejecuta
php bin/console debug:autowiring http
para buscar "http" en los servicios. Obtenemos un montón de resultados, pero... nada que parezca un cliente HTTP. Y, eso es correcto. Actualmente no hay ningún servicio en nuestra aplicación que pueda hacer peticiones HTTP.
Pero podemos instalar otro paquete que nos proporcione uno. En tu terminal, escribe:
composer require symfony/http-client
Pero, antes de ejecutarlo, quiero mostrarte de dónde viene este comando. Busca "symfony http client". Uno de los primeros resultados es la documentación de Symfony.com que enseña sobre un componente Cliente HTTP. Recuerda: Symfony es una colección de muchas bibliotecas diferentes, llamadas componentes. ¡Y éste nos ayuda a realizar peticiones HTTP!
Cerca de la parte superior, verás una sección llamada "Instalación", ¡y ahí está la línea de nuestro terminal!
De todos modos, si ejecutamos eso... ¡genial! Una vez que termine, prueba de nuevo el comando debug:autowiring
:
php bin/console debug:autowiring http
Y... ¡aquí está! Justo en la parte inferior: HttpClientInterface
que
Proporciona métodos flexibles para solicitar recursos HTTP de forma sincrónica o de forma asíncrona.
¡Guau! ¡Acabamos de conseguir un nuevo servicio! Eso significa que debemos haber instalado un nuevo bundle, ¿verdad? Porque... ¿los bundles nos dan servicios? Bueno... ve a verconfig/bundles.php
:
return [ | |
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], | |
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], | |
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], | |
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], | |
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], | |
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], | |
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true], | |
Symfony\UX\Turbo\TurboBundle::class => ['all' => true], | |
Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true], | |
]; |
¡Woh! ¡Aquí no hay ningún bundle nuevo! Prueba a ejecutar
git status
Sí... eso sólo instaló un paquete PHP normal. Dentro de composer.json
, aquí está el nuevo paquete... Pero es sólo una "biblioteca": no un bundle.
{ | |
... lines 2 - 5 | |
"require": { | |
... lines 7 - 15 | |
"symfony/http-client": "6.1.*", | |
... lines 17 - 24 | |
}, | |
... lines 26 - 84 | |
} |
Así que, normalmente, si instalas "sólo" una biblioteca PHP, te da clases PHP, pero no se engancha a Symfony para darte nuevos servicios. Lo que acabamos de ver es un truco especial que utilizan muchos de los componentes de Symfony. El bundle principal de nuestra aplicación es framework-bundle
. De hecho, cuando empezamos nuestro proyecto, éste era el único bundle que teníamos. framework-bundle
es inteligente. Cuando instalas un nuevo componente de Symfony -como el componente Cliente HTTP- ese bundle se da cuenta de la nueva biblioteca y añade automáticamente los servicios para ella.
Así que el nuevo servicio proviene de framework-bundle
... que lo añade en cuanto detecta que el paquete http-client
está instalado.
De todos modos, es hora de utilizar el nuevo servicio. El tipo que necesitamos es HttpClientInterface
. Dirígete a VinylController.php
y, aquí arriba, en la acción browse()
, autocablea HttpClientInterface
y llamémoslo $httpClient
:
... lines 1 - 7 | |
use Symfony\Contracts\HttpClient\HttpClientInterface; | |
... lines 9 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(HttpClientInterface $httpClient, string $slug = null): Response | |
{ | |
... lines 34 - 41 | |
} | |
... lines 43 - 67 | |
} |
Entonces, en lugar de llamar a $this->getMixes()
, di $response = $httpClient->
. Esto lista todos sus métodos... probablemente queramos request()
. Pasa este GET
... y luego pegaré la URL: puedes copiarla del bloque de código de esta página. Es un enlace directo al contenido del archivo mixes.json
:
... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(HttpClientInterface $httpClient, string $slug = null): Response | |
{ | |
... line 34 | |
$response = $httpClient->request('GET', 'https://raw.githubusercontent.com/SymfonyCasts/vinyl-mixes/main/mixes.json'); | |
... lines 36 - 41 | |
} | |
... lines 43 - 67 | |
} |
¡Genial! Así que hacemos la petición y nos devuelve una respuesta que contiene los datos de mixes.json
que vemos aquí. Afortunadamente, estos datos tienen todas las mismas claves que los antiguos datos que estábamos utilizando aquí abajo... así que deberíamos poder cambiarlos con mucha facilidad. Para obtener los datos mixtos de la respuesta, podemos decir $mixes = $response->toArray()
:
... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(HttpClientInterface $httpClient, string $slug = null): Response | |
{ | |
... line 34 | |
$response = $httpClient->request('GET', 'https://raw.githubusercontent.com/SymfonyCasts/vinyl-mixes/main/mixes.json'); | |
$mixes = $response->toArray(); | |
... lines 37 - 41 | |
} | |
... lines 43 - 67 | |
} |
¡un práctico método que decodifica los datos en JSON por nosotros!
¡Momento de la verdad! Muévete, actualiza y... ¡funciona! Ahora tenemos seis mezclas en la página. Y... ¡superguay! Apareció un nuevo icono en la barra de herramientas de depuración de la web: "Total de peticiones: 1". El servicio Cliente HTTP se engancha a la barra de herramientas de depuración web para añadir esto, lo cual es bastante impresionante. Si hacemos clic en él, podemos ver información sobre la petición y la respuesta. Eso me encanta.
Para celebrar que esto funciona, vuelve a girar y elimina el método getMixes()
codificado:
... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
... line 13 | |
public function homepage(): Response | |
{ | |
... lines 16 - 28 | |
} | |
... lines 30 - 31 | |
public function browse(HttpClientInterface $httpClient, string $slug = null): Response | |
{ | |
... lines 34 - 41 | |
} | |
} |
El único problema que se me ocurre ahora es que, cada vez que alguien visita nuestra página, estamos haciendo una petición HTTP a la API de GitHub... ¡y las peticiones HTTP son lentas! Para empeorar las cosas, una vez que nuestro sitio se haga superpopular -lo que no tardará mucho- la API de GitHub probablemente empezará a limitarnos la velocidad.
Para solucionar esto, vamos a aprovechar otro servicio de Symfony: el servicio de caché.
Hey Marc!
Hmm, that's weird - I can't think of a reason! As long as you have the http_client
enabled (which should happen automatically when you install that package) and the profiler (which you DO have if you are seeing the web debug toolbar), then it should be there. The logic that loads it is here - https://github.com/symfony/symfony/blob/0383b067e947efb3570b2fda8259e582ef3d8f2d/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L846-L848 - but that's super deep and complex, so not sure that's helpful. But it basically says what I just said: as long as profiler and http_client are enabled, that web debug toolbar should be there.
Oh, actually, there is one other possibility: there were no requests issued during the request by the HTTP client. If that happens, the web debug toolbar icon won't be shown - https://github.com/symfony/symfony/blob/0383b067e947efb3570b2fda8259e582ef3d8f2d/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/http_client.html.twig#L40 - but if you clicked any of the other icons to get into the profiler, I think you would still see an "HTTP Client" section.
Let me know if this helps!
Cheers!
Hi Ryan,
Thanks for your answer.
The fact is I tweaked http_client.html.twig to display {{ collector.requestCount }} value. It stays at 0 though dumping the result of
$response = $httpClient->request(
'GET',
'https://raw.githubusercontent.com/SymfonyCasts/vinyl-mixes/main/mixes.json?'
);
via dump($response) outputs :
Symfony\Component\HttpClient\Response\TraceableResponse {#2284 ▼
-client: Symfony\Component\HttpClient\CurlHttpClient {#6639 ▶}
-response: Symfony\Component\HttpClient\Response\CurlResponse {#6264 ▶}
-content: & null
-event: Symfony\Component\Stopwatch\StopwatchEvent {#6169 ▶}
}
By the way, dumping $response->toArray() outputs the desired array.
Would you have an idea how we can get the valid response content with no request done ?
Would there be an internal cache ?
In composer.json, http-client is version 6.2.6
Kind regards,
Marc.
Hey Marc!
That's some good debugging! Hmm. A few things to check out... though I don't think these are the cause (but I need to check anyways):
A) You are autowiring the HttpClient service the same way we do in the tutorial?
B) Are you actually using the result of the response? I'm asking because the HttpClient is "async". So if you simply made a request, but never used its response, then it's possible that the response hasn't actually finished by the time the profiler collects the data. However, if you are even calling $response->toArray()
, then that makes the code "wait" for the request to finish... and so that would be enough to avoid this.
So then, what could the cause be? I'm not sure - it definitely looks weird :/
I'm interested also if:
1) Is this method being called https://github.com/symfony/symfony/blob/0383b067e947efb3570b2fda8259e582ef3d8f2d/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php#L44 ?
2) And if so, what does dd($this->clients)
show? It should show at least one HttpClient
3) If your controller, if you dump($httpClient)
, what does it look like? It should be a TraceableHttpClient
. I'm pretty sure it IS because you're getting back a TraceableResponse
.
Let me know what you find out :).
Cheers!
Thanks Ryan,
for questions A and B, I can say answer is Yes. Here's the code :
#[Route('/browse/{slug}', name: 'app_browse')]
public function browse(HttpClientInterface $httpClient, string $slug = null): Response
{
$genre = $slug ? u(str_replace('-', ' ', $slug))->title(true) : null;
$response = $httpClient->request(
'GET',
'https://raw.githubusercontent.com/SymfonyCasts/vinyl-mixes/main/mixes.json',
);
dump($response->toArray());
return $this->render('vinyl/browse.html.twig',
[
'genre' => $genre,
'mixes' => $response->toArray()
]);
}
For points
I'm really sorry to bother you with this.
Thanks!
Huh... so everything, so far, seems to be working 100% correctly!
So. you have confirmed that lateCollect()
is called (2x in fact... that may be normal - I'm not sure, I'm not familiar enough with that system) - https://github.com/symfony/symfony/blob/0383b067e947efb3570b2fda8259e582ef3d8f2d/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php#L44
And we also know that $this->client
contains 1 item. So next I would be interested in what $traces
looks like - https://github.com/symfony/symfony/blob/0383b067e947efb3570b2fda8259e582ef3d8f2d/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php#L51
If there is at least 1 item in $traces
, then $this->data['request_count']
would be more than 0. And THEN we should see the icon - thanks to https://github.com/symfony/symfony/blob/0383b067e947efb3570b2fda8259e582ef3d8f2d/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/http_client.html.twig#L40
Actually, in https://github.com/symfony/symfony/blob/0383b067e947efb3570b2fda8259e582ef3d8f2d/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/http_client.html.twig#L40 - you might also try a dump(collector)
(outside of any if statements) to make sure this is being called AND what collector
looks like.
So, there is a still a mystery here... but we're circling closer and closer to it :).
Cheers!
Hi Ryan,
Thanks for your answer and sorry for the late reply.
Dumping $trace doesn't provide any info, as it gives an empty array.
The most interesting part comes from dumping collector in http_client.html.twig :
Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector {#425 ▼
#data: array:3 [▼
"request_count" => 0
"error_count" => 0
"clients" => array:2 [▼
"http_client" => array:2 [▼
"traces" => []
"error_count" => 0
]
"githubContentClient" => array:2 [▼
"traces" => array:1 [▼
0 => array:7 [▼
"method" => "GET"
"url" => "/SymfonyCasts/vinyl-mixes/main/mixes.json"
"options" => Symfony\Component\VarDumper\Cloner\Data {#433 ▶}
"content" => array:6 [▶]
"http_code" => 200
"info" => Symfony\Component\VarDumper\Cloner\Data {#437 ▶}
"curlCommand" => """
curl \
--compressed \
--request GET \
--url 'https://raw.githubusercontent.com/SymfonyCasts/vinyl-mixes/main/mixes.json' \
--header 'authorization: Token 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' \
--header 'accept: */*' \
--header 'user-agent: Symfony HttpClient/Curl' \
--header 'accept-encoding: gzip'
"""
]
]
"error_count" => 0
]
]
]
-clients: []
}
No clients in clients[], but clients in the #data property.
Would you have an idea ?
If it looks too complicated, we can forget about my question.
Thanks anyway Ryan !
Dang! I am at a loss! How could that clients
property be an empty array... and yet $data
got populated! Haha, indeed - without being able to play with the code first-hand, I think this will remain a mystery (though, you were an excellent debugging partner). It's possible there is some problem introduced in newer versions of Symfony... or just something bizarre with your setup for some reason. If you DO every happen to find out (maybe it goes away randomly), let me know.
Cheers!
Thanks again Ryan !
I'll let you know in case I solve this stuff...
By the way, congrats to your team : the tutorials are excellent as well as your answers !
Regards,
Marc.
Ah, I can repeat it too! This was a bug introduced unintentionally during a CVE security release. The fix has already been merged - https://github.com/symfony/symfony/pull/49301 - it looks like that should land in 6.2.7 when it's released.
Thanks Yard for speaking up - as soon as 2 users had the problem, I knew something must have been wrong!
Hi Ryan,
Thanks for these wonderful tutorials.
If Laravel is built on top of many Symfony's bundles and even both have their own cache systems I wonder how is possible that some benchmark on the internet gave a better performance to Laravel? Exactly the conclusions of many of them are: "The average loading time for websites on Laravel is about 60 milliseconds, while Symfony's load time is about 250 milliseconds"
Thank you, kind regards.
// 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
}
}
Hi Ryan !
Thanks for these great tutorials.
Would there be a reason for Http Client Icon not showing in the debug toolbar ?
My current Sf version is 6.2.6
Thanks,
Marc.