This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
22.

Configuración de la caché

|

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

¡Nuestras traducciones de objetos se están almacenando en caché con éxito! Ahora quiero permitir que el usuario configure su grupo de caché y el tiempo que desea almacenar en caché las traducciones. Para ello, vamos a utilizar a nuestro viejo amigo, la configuración de bundle.

Un ArrayNode

Abre nuestro ObjectTranslationBundle y busca el método configure(). Permitiremos dos opciones para la caché: la reserva de caché a utilizar y el tiempo de vida o ttl. Podemos agrupar estas dos opciones.

Debajo del ->stringNode()'s ->end(), añade un ->arrayNode() y llámalo cache. Ciérralo con un ->end(), y añade un espacio. Añade una descripción con ->info('Cache settings for object translations.'):

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
// ... lines 14 - 16
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
// ... lines 21 - 30
->arrayNode('cache')
->info('Cache settings for object translations.')
// ... lines 33 - 42
->end()
->end()
;
}
// ... lines 47 - 69

Quiero que el usuario pueda desactivar todo este nodo para evitar por completo el almacenamiento en caché. Hay un atajo para esto: ->canBeDisabled(). Esto añade automáticamente una opción booleana enabled y por defecto true:

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
// ... lines 14 - 16
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
// ... lines 21 - 30
->arrayNode('cache')
// ... line 32
->canBeDisabled()
// ... lines 34 - 42
->end()
->end()
;
}
// ... lines 47 - 69

Tip

También hay un método canBeEnabled() si quieres que el valor por defecto seafalse.

Añadir hijos

Ahora añade las dos subopciones: Escribe ->children() y ciérralo con un->end():

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
// ... lines 14 - 16
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
// ... lines 21 - 30
->arrayNode('cache')
// ... lines 32 - 33
->children()
// ... lines 35 - 42
->end()
->end()
;
}
// ... lines 47 - 69

El primer hijo será ->stringNode('pool')->end(). Dentro,->info('The cache pool to use for storing object translations.'). Sé que todas las aplicaciones Symfony tienen un grupo cache.app, así que úsalo como valor por defecto con ->defaultValue('cache.app'):

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
// ... lines 14 - 16
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
// ... lines 21 - 30
->arrayNode('cache')
// ... lines 32 - 33
->children()
->stringNode('pool')
->info('The cache pool to use for storing object translations.')
->defaultValue('cache.app')
->end()
// ... lines 39 - 42
->end()
->end()
;
}
// ... lines 47 - 69

A continuación, añade un ->integerNode('ttl')->end() y dentro,->info('The time-to-live for cached translations, in seconds. null for no expiration'). Aunque se trata de un nodo entero, a menos que lo desautoricemos explícitamente, se puede utilizar null. Por defecto, no quiero ninguna caducidad, así que ->defaultNull():

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
// ... lines 14 - 16
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
// ... lines 21 - 30
->arrayNode('cache')
// ... lines 32 - 33
->children()
// ... lines 35 - 38
->integerNode('ttl')
->info('The time-to-live for cached translations, in seconds. null for no expiration.')
->defaultNull()
->end()
->end()
->end()
;
}
// ... lines 47 - 69

Depurar la configuración

Comprueba que todo parece correcto saltando a tu terminal y ejecutando:

symfony console config:dump-reference symfonycasts_object_translation

¡Bonita configuración bien documentada! Comprueba la opción enabled añadida automáticamente por canBeDisabled().

Hay otro comando que muestra la configuración actual (y todos los valores por defecto) de un bundle. Ejecuta:

symfony console debug:config symfonycasts_object_translation

Genial, esta es la configuración que cargará nuestro bundle.

Opcional CacheInterface

Ahora preparemos nuestro código para la nueva configuración. Abre ObjectTranslator.php. Como ahora la caché es opcional, necesitamos permitir null para CacheInterface. Primero, copia esta propiedad y pégala encima del constructor.

En el constructor, elimina private, lo queremos como un argumento normal. Hazlo anulable anteponiendo a la sugerencia de tipo ? y dale un valor por defecto de null:

// ... lines 1 - 13
final class ObjectTranslator
{
private CacheInterface $cache;
// ... lines 17 - 18
public function __construct(
// ... lines 20 - 23
?CacheInterface $cache = null,
// ... line 25
) {
// ... lines 27 - 28
}
// ... lines 30 - 101
}

A continuación, añade un nuevo argumento de propiedad para el ttl: private ?int $cacheTtl = null:

// ... lines 1 - 13
final class ObjectTranslator
{
// ... lines 16 - 18
public function __construct(
// ... lines 20 - 24
private ?int $cacheTtl = null,
) {
// ... lines 27 - 28
}
// ... lines 30 - 101
}

Podríamos añadir algunas comprobaciones para que sólo se utilice la caché si se ha inyectado un adaptador de caché... pero... hay una forma más limpia de hacerlo. El patrón de objeto nulo.

Dentro del constructor, escribe $this->cache = $cache ?? new NullAdapter():

// ... lines 1 - 13
final class ObjectTranslator
{
// ... lines 16 - 18
public function __construct(
// ... lines 20 - 25
) {
$this->cache = $cache ?? new NullAdapter();
// ... line 28
}
// ... lines 30 - 101
}

Genial, ¡ahora no tenemos que cambiar ningún código que utilice la caché!

Ahora a utilizar el valor cacheTtl. Abajo en translationsFor(), dentro de la llamada, ya inyectamos el ItemInterface - esto es en lo que fijamos la caducidad.

Añade una comprobación para ver si el ttl está establecido: if ($this->cacheTtl). Dentro:$item->expiresAfter($this->cacheTtl):

// ... lines 1 - 13
final class ObjectTranslator
{
// ... lines 16 - 48
private function translationsFor(object $object, string $locale): array
{
// ... lines 51 - 73
return $this->cache->get(
// ... line 75
function(ItemInterface $item) use ($locale, $type, $id) {
// ... lines 77 - 80
if ($this->cacheTtl) {
$item->expiresAfter($this->cacheTtl);
}
// ... lines 84 - 98
}
);
}
}

¡Listo!

Utilizar la configuración

Por último, tenemos que ajustar nuestra definición de servicio para que utilice nuestra nueva configuración.

Abre el services.php de nuestro bundle y elimina el argumento service('cache.app'). Ahora es opcional y se configurará en función de la configuración del usuario.

Ahora, ve a ObjectTranslationBundle::loadExtension(). Para volver a comprobar el aspecto de nuestro $config, dd... y... de nuevo en el navegador, actualiza.

Perfecto, aquí está nuestra matriz de caché, las dos opciones, más la activada.

De vuelta en loadExtension(), elimina el dd. Como vamos a utilizar esta definición de servicio varias veces, crea una variable para ella. Copia el $builder->getDefinition(...), y encima, escribe$objectTranslatorDef = y... pega:

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
{
// ... lines 15 - 54
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
// ... lines 57 - 58
$objectTranslatorDef = $builder->getDefinition('symfonycasts.object_translator');
// ... lines 60 - 66
}
}

Abajo, refactoriza para llamar a ->setArgument() en nuestra nueva variable:

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
{
// ... lines 15 - 54
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
// ... lines 57 - 60
$objectTranslatorDef->setArgument(2, $config['translation_class']);
// ... lines 62 - 66
}
}

Establecer el translation_class siempre es necesario, pero sólo establecemos la caché si está activada.

Escribe if ($config['cache']['enabled']):

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
{
// ... lines 15 - 54
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
// ... lines 57 - 62
if ($config['cache']['enabled']) {
// ... lines 64 - 65
}
}
}

Dentro, configura el pool de caché y los argumentos ttl. Primero, $objectTranslatorDef->setArgument(). Encuentra el índice de argumentos saltando rápidamente al constructor ObjectTranslator, y cuenta los argumentos, 0, 1, 2, 3, 4. ¡Ya está!

Utiliza 4 como primer argumento, y para el segundo, no podemos utilizar simplemente la cadenapool sin procesar: tiene que ser una referencia de servicio. Así que escribenew Reference(), asegúrate de importarlo del espacio de nombres DependencyInjection. Dentro, pasa $config['cache']['pool']:

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
{
// ... lines 15 - 54
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
// ... lines 57 - 62
if ($config['cache']['enabled']) {
$objectTranslatorDef->setArgument(4, new Reference($config['cache']['pool']));
// ... line 65
}
}
}

Para el ttl, podemos utilizar el entero sin procesar, así que$objectTranslatorDef->setArgument(5, $config['cache']['ttl']):

// ... lines 1 - 12
final class ObjectTranslationBundle extends AbstractBundle
{
// ... lines 15 - 54
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
// ... lines 57 - 62
if ($config['cache']['enabled']) {
// ... line 64
$objectTranslatorDef->setArgument(5, $config['cache']['ttl']);
}
}
}

¡Creo que ya podemos empezar! Primero, asegurémonos de que la caché de nuestra aplicación está limpia. En tu terminal, ejecuta:

symfony console cache:clear

En el navegador, actualiza... 4 consultas, esto debería estar calculando y configurando la caché. Actualiza de nuevo... Se ha reducido a 1 consulta.

Vale, en realidad no ha cambiado mucho... ¡así que vamos a configurarlo con una reserva personalizada!

Grupo de caché personalizado

En cache.yaml de nuestra aplicación, descomenta la sección pools. Nombra nuestro poolobject_translation.cache. Por defecto, se basará en nuestra reservacache.app. Pero vamos a activar el etiquetado añadiendo tags: true:

21 lines | config/packages/cache.yaml
framework:
cache:
// ... lines 3 - 17
pools:
object_translation.cache:
tags: true

Ahora, en symfonycasts_object_translation.yaml, configura nuestro pool personalizado poniendo: cache: pool:. ¿Cómo lo llamamos aquí? object_translation.cache cópialo y pégalo:

symfonycasts_object_translation:
// ... line 2
cache:
pool: 'object_translation.cache'

De nuevo en el navegador, actualiza... 4 consultas, esto debería estar utilizando el nuevo pool. Actualiza de nuevo... Se ha reducido a 1 consulta. Perfecto

Invalidación de etiquetas de caché (de verdad)

Ahora deberíamos poder ver realmente cómo funciona la invalidación de etiquetas de caché. Ve a tu terminal y ejecuta:

symfony console cache:pool:invalidate-tags object-translation

Vuelve al navegador y actualiza... 4 consultas. ¡Eso significa que los elementos etiquetados de la caché se invalidaron y tuvieron que calcularse de nuevo!

Bien, nuestro ObjectTranslator se ha convertido en un monstruo. A continuación, ¡refactoricemos esta bestia!