Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

El servicio de caché

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Ahora, cuando actualizamos la página de navegación, ¡las mezclas provienen de un repositorio en GitHub! Hacemos una petición HTTP a la API de GitHub, que obtiene este archivo de aquí, llamamos a $response->toArray() para decodificar ese JSON en una matriz $mixes... y luego lo representamos en la plantilla. Sí, ¡este archivo de GitHub es nuestra falsa base de datos temporal!

Un problema práctico es que ahora cada carga de la página hace una petición HTTP... y las peticiones HTTP son lentas. Si desplegáramos esto en producción, nuestro sitio sería tan popular, por supuesto, que alcanzaríamos rápidamente nuestro límite de la API de GitHub. Y entonces esta página explotaría.

Así que... estoy pensando: ¿y si guardamos en caché el resultado? Podríamos hacer esta petición HTTP y luego almacenar los datos en caché durante 10 minutos, o incluso una hora. ¿Cómo se almacenan las cosas en caché en Symfony? Lo has adivinado: ¡con un servicio! ¿Qué servicio? ¡No lo sé! Así que vamos a averiguarlo.

Encontrar el servicio de caché

Ejecuta

php bin/console debug:autowiring cache

para buscar servicios con "caché" en su nombre. Y... ¡sí! De hecho, ¡hay varios! Hay uno llamado CacheItemPoolInterface, y otro llamadoStoreInterface. Algunos de ellos no son exactamente lo que buscamos, peroCacheItemPoolInterface, CacheInterface, y TagAwareCacheInterface son todos servicios diferentes que puedes utilizar para el almacenamiento en caché. Todos hacen efectivamente lo mismo... pero el más fácil de usar es CacheInterface.

Así que vamos a coger ese .... haciendo nuestro elegante truco de autoconexión Añade otro argumento a nuestro método escrito con CacheInterface (asegúrate de obtener el deSymfony\Contracts\Cache) y llámalo, qué tal, $cache:

... lines 1 - 7
use Symfony\Contracts\Cache\CacheInterface;
... lines 9 - 11
class VinylController extends AbstractController
{
... lines 14 - 32
public function browse(HttpClientInterface $httpClient, CacheInterface $cache, string $slug = null): Response
{
... lines 35 - 46
}
}

Para utilizar el servicio $cache, copia estas dos líneas de antes, bórralas y sustitúyelas por $mixes = $cache->get(), como si fueras a sacar alguna clave de la caché. Podemos inventar la clave de la caché que queramos: ¿qué tal mixes_data.

El objeto caché de Symfony funciona de una manera única. Llamamos a $cache->get() y le pasamos esta clave. Si ese resultado ya existe en la caché, se devolverá inmediatamente. Si aún no existe en la caché, entonces llamará a nuestro segundo argumento, que es una función. Aquí, nuestro trabajo es devolver los datos que deben ser almacenados en la caché. Pega las dos líneas de código que hemos copiado antes. Este $httpClientno está definido, así que tenemos que añadir use ($httpClient) para que entre en el ámbito.

Ya está Y en lugar de establecer la variable $mixes, simplemente return esta línea$response->toArray():

... lines 1 - 11
class VinylController extends AbstractController
{
... lines 14 - 32
public function browse(HttpClientInterface $httpClient, CacheInterface $cache, string $slug = null): Response
{
... lines 35 - 36
$mixes = $cache->get('mixes_data', function() use ($httpClient) {
$response = $httpClient->request('GET', 'https://raw.githubusercontent.com/SymfonyCasts/vinyl-mixes/main/mixes.json');
return $response->toArray();
});
... lines 42 - 46
}
}

Si no has utilizado antes el servicio de caché de Symfony, esto puede parecer extraño, pero a mí me encanta La primera vez que actualicemos la página, todavía no habrá ningún mixes_dataen la caché. Así que llamará a nuestra función, devolverá el resultado y el sistema de caché lo almacenará en la caché. La próxima vez que actualicemos la página, la clave estará en la caché y devolverá el resultado inmediatamente. Así que no necesitamos ninguna sentencia "if" para ver si algo ya está en la caché... ¡sólo esto!

Depuración con el Perfilador de Caché

Pero... ¿se mezclará? Vamos a averiguarlo. Refresca y... ¡bien! El primer refresco seguía haciendo la petición HTTP con normalidad. Abajo, en la barra de herramientas de depuración de la web, podemos ver que hubo tres llamadas a la caché y una escritura en la caché. Abre esto en una nueva pestaña para saltar a la sección de caché del perfilador.

Genial: esto nos muestra que hubo una llamada a la caché para mixes_data, una escritura en la caché y un fallo en la caché. Un "miss" de la caché significa que se ha llamado a nuestra función y se ha escrito en la caché.

En la siguiente actualización, observa este icono de aquí. Desaparece Eso es porque no hubo ninguna petición HTTP. Si vuelves a abrir el perfil de la caché, esta vez hubo una lectura y un acierto. Ese acierto significa que el resultado se ha cargado desde la caché y no ha hecho ninguna petición HTTP. ¡Eso es exactamente lo que queríamos!

Configurar el tiempo de vida de la caché

Ahora te preguntarás: ¿cuánto tiempo permanecerá esta información en la caché? Ahora mismo... para siempre. Ooooh. Ese es el valor por defecto.

Para que caduque antes que para siempre, dale a la función un argumento CacheItemInterface-asegúrate de pulsar "tab" para añadir esa declaración de uso- y llámala$cacheItem. Ahora podemos decir $cacheItem->expiresAfter() y, para hacerlo más fácil, decir 5:

... lines 1 - 4
use Psr\Cache\CacheItemInterface;
... lines 6 - 12
class VinylController extends AbstractController
{
... lines 15 - 33
public function browse(HttpClientInterface $httpClient, CacheInterface $cache, string $slug = null): Response
{
... lines 36 - 37
$mixes = $cache->get('mixes_data', function(CacheItemInterface $cacheItem) use ($httpClient) {
$cacheItem->expiresAfter(5);
... lines 40 - 42
});
... lines 44 - 48
}
}

El artículo expirará después de 5 segundos.

Borrar la caché

Por desgracia, si intentamos esto, el elemento que ya está en la caché está configurado para no caducar nunca. Así que... esto no funcionará hasta que borremos la caché. Pero... ¿dónde se almacena la caché? ¡Otra gran pregunta! Hablaremos más sobre esto en un segundo... pero, por defecto, se almacena en var/cache/dev/... junto con un montón de otros archivos de caché que ayudan a Symfony a hacer su trabajo.

Podríamos borrar este directorio manualmente para limpiar la caché... ¡pero Symfony tiene una forma mejor! Es, por supuesto, otro comando de bin/console.

Symfony tiene un montón de diferentes "categorías" de caché llamadas "cache pools". Si ejecutas

php bin/console cache:pool:list

verás todas ellas. La mayoría de ellas están pensadas para que Symfony las utilice internamente. El pool de caché que estamos utilizando se llama cache.app. Para borrarla, ejecuta:

php bin/console cache:pool:clear cache.app

¡Eso es todo! Esto no es algo que necesites hacer muy a menudo, pero es bueno saberlo, por si acaso.

Bien, comprueba esto. Cuando refrescamos... obtenemos un fallo en la caché y puedes ver que hizo una llamada HTTP. Pero si volvemos a actualizar rápidamente... ¡ya no está! Vuelve a actualizar y... ¡ha vuelto! Eso es porque los cinco segundos acaban de expirar.

Bien, equipo: ahora estamos aprovechando un servicio de cliente HTTP y un servicio de caché... ambos preparados para nosotros por uno de nuestros bundles para que podamos simplemente... ¡utilizarlos!

Pero tengo una pregunta. ¿Qué pasa si necesitamos controlar estos servicios? Por ejemplo, ¿cómo podríamos decirle al servicio de caché que, en lugar de guardar cosas en el sistema de archivos de este directorio, queremos almacenarlas en Redis... o en memcache? Vamos a explorar la idea de controlar nuestros servicios mediante la configuración.

Leave a comment!

9
Login or Register to join the conversation
Fedale Avatar

In Symfony 6.1 new class' name is Symfony\Contracts\Cache\ItemInterface so you have to change callback like this:
function(ItemInterface $cacheItem) {}

Cheers
Danilo

1 Reply

Hey @Fedale!

Thanks for the note! In truth, both work just fine in any Symfony version, though I probably should have used ItemInterface. ItemInterface extends the CacheItemInterface that we use in this video, so both work. However, if you use ItemInterface , then you have addition ->tag() and ->getMetadata() methods to give you more flexibility.

Cheers!

Reply
Vlame Avatar

Does the php bin/console cache:pool:clear cache.app command the same as the php bin/console cache:clear command?

Reply

Hey @Vlame!

Excellent question! These are actually 2 different things for the 2 different types of cache in Symfony

A) cache:pool:clear is used to clear the "cache" that we were talking about in this tutorial. This is the cache where you can store items, including things for your application but also the core stores some things in cache pools. For example, Doctrine caches the DQL -> SQL generation in a cache pool. In short, when you think about a "caching system", this command is used to clear those.

B) cache:clear has a different purpose. When Symfony loads, internally, it caches a bunch of stuff - most importantly it caches your routes after parsing them and it caches all of your services. This is all stored in the var/cache/dev directory (or var/cache/prod in the prod environment) and it's not something you typically need to think about. If a routing file changes, Symfony automatically rebuilds this cache. These are fundamental cache file that Symfony needs to even function.

You'll run cache:clear each time you deploy, because you DO want to rebuild these cache files (these cache files are 100% built from your code - so if your code changes, you should rebuild the cache). But you'll rarely run cache:pool:clear as these are typically things that you want to keep in cache until they expire.

Let me know if that helps!

Cheers!

Reply
Vlame Avatar

Hello @weaverryan,

Thanks for your answer! It is clear for me what the differences are. Thanks a lot!

Reply
Default user avatar
Default user avatar Влада Петковић | posted hace 6 meses

Would there be a possibility to process some new examples of creating services and bundles in future lessons? For God's sake, since version Symfony 3 you have been demonstrating examples with the Cache and MarkDown helper - service.Are there any other examples besides those?

Reply

Hey Vlada,

We have a separate course about creating a Symfony bundle, I think you might be interested in it, you can check it out here: https://symfonycasts.com/sc...

Hm, the idea of this course is not about learning how write an implementation of a specific service, but how to write service in general. We cover everything you need to be able to create *any* service and inject any dependencies into them. But I see your point, we will try to find a different service example in future tutorials just to diversify out tutorials for those who are watching past courses as well. Thank you for you feedback! Also, if you have any ideas what specific services would be great to cover in your opinion - please, share it!

Cheers!

1 Reply
Default user avatar
Default user avatar Влада Петковић | Victor | posted hace 6 meses

I respect your work and the effort you put into creating new lessons, but I believe that there must be something new because sticking to old lessons and changing only the version of the Symphony does not contribute to the quality of learning and mastering the material. If the Symphony team claims that the Symphony is so powerful (I honestly believe that this is true) then demonstrate that strength and power through various examples and not through just one example. I'm not a lecturer, nor do I consider myself an excessively experienced programmer, but if you're already asking me to suggest specific examples for bundles and services, then you could, for example, in with your lessons, you show how to make a specific visual web control that could be used as a package in various web applications. For example, a DataGrid control with the implementation of data pagination, filtering and sorting data or DataView control as they exist in Yii2 framework. The KnpPaginatorBundle package almost always has some bugs and does not work in new versions of Symfony. The EasyAdmin bundle is too crowded with functions for simple display of data in a DataGrid with elementary possibilities that these controls should offer (pagination, sorting and filtering and nothing more ... ). An even better example would be the implementation of a separate layer of the database with business rules (n-tier web application) as a separate service.

Greeting.

Reply

Hey @Влада Петковић

Yea, sorry for the repetition. These "basic" courses aren't really meant to be different across Symfony versions... we just give them an "update" do keep the content current. If you're watching them across Symfony versions, you're definitely going to hear the same things over and over again :). What I need to do (and it's my goal for the summer) is to quickly finish these basic course updates so we can get into bigger and better things.

> For example, a DataGrid control with the implementation of data pagination, filtering and sorting data

That's a cool idea... and I've been liking the idea more lately of having some shorter tutorials that just solve some interesting problem vs teach a concept in Symfony (we will still have those, but just "coding up a feature" I think is an interesting idea).

Anyways, thank for the idea and patience - I realize that these "basic" Symfony tutorials are not much fun for seasoned Symfony devs :).

Cheers!

2 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
    }
}