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
24.

Orden de calentamiento 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

De acuerdo, por defecto, nuestras traducciones de objetos se almacenan en caché indefinidamente. Podemos configurar una caducidad, pero ¿y si quisiéramos forzar una actualización anticipada? Quizás hemos actualizado la base de datos con nuevas traducciones, y queremos que el sitio lo refleje. Claro, podríamos borrar toda la caché o invalidar la etiqueta de caché. Pero esto podría afectar a miles de traducciones y, en un sitio con mucho tráfico, podría provocar ralentizaciones mientras se repobla la caché.

Creemos una función de calentamiento de la caché. Esto borrará cada elemento de la caché y simultáneamente la refrescará con la última versión. Para ello, crearemos un comando de consola.

En tu IDE, dentro de nuestro directorio object-translation-bundle's src, crea una nueva clase PHP. Llámala ObjectTranslationWarmupCommand. Estará en el espacio de nombres Command. Con PhpStorm, podemos actualizar el espacio de nombres aquí, y lo creará en el directorio adecuado.

Configurar el comando

Allá vamos. Primero, marca la clase como final y @internal. El comando en sí será público, pero el código del comando será interno.

A continuación, haz que extienda Command del componente de consola Symfony. En la parte superior, añade el atributo AsCommand. Nombre: object-translation:warmup. Descripción: Warms up the object translation cache..

Dentro de la clase, anula dos métodos: el constructor y execute().

En el constructor, no queremos este argumento $name, pero aún así tenemos que llamar al constructor padre, sólo que sin argumentos.

// ... lines 1 - 13
/**
* @internal
*/
#[AsCommand(
name: 'object-translation:warmup',
description: 'Warms up the object translation cache.',
)]
final class ObjectTranslationWarmupCommand extends Command
{
public function __construct(
// ... lines 24 - 27
) {
parent::__construct();
}
// ... line 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... lines 34 - 40
}
}

Inyectar servicios

Haz sitio e inyecta los siguientes servicios:private ObjectTranslator $translator y private TranslatableMappingManager $mappingManager.

A continuación, private LocaleSwitcher $localeSwitcher, un servicio especial que nos permite cambiar temporalmente la configuración regional de toda la aplicación mientras ejecutamos código. Esto será útil para calentar las traducciones en todas las locales disponibles.

Por último, necesitamos esas configuraciones regionales, así que inyecta private array $locales:

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
public function __construct(
private ObjectTranslator $translator,
private TranslatableMappingManager $mappingManager,
private LocaleSwitcher $localeAware,
private array $locales,
) {
// ... line 29
}
// ... lines 31 - 41
}

execute() Método

Abajo en execute(), elimina la llamada al método padre, ya que está vacío. Este método debe devolver un número entero como código de estado. Así que, antes de que se nos olvide, devuelve self::SUCCESS.

Arriba, escribe, $io = new SymfonyStyle($input, $output). Esto nos proporciona una bonita forma de interactuar con la consola utilizando el estilo recomendado por Symfony.

Añade un título para este comando: $io->title('Warming up Object Translation Cache').

Inicia una variable de recuento: $count = 0. Esto nos ayudará a llevar la cuenta de cuántos objetos hemos calentado.

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
// ... lines 23 - 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Warming up Object Translation Cache');
$count = 0;
return self::SUCCESS;
}
}

Iterar sobre objetos traducibles

Necesitamos una forma de obtener todos los objetos traducibles. Así que, en nuestro TranslatableMappingManager, crea un nuevo método: public function allTranslatableObjects(). Tipo de retorno: iterable:

// ... lines 1 - 11
final class TranslatableMappingManager
{
// ... lines 14 - 70
public function allTranslatableObjects(): iterable
{
// ... lines 73 - 83
}
}

Primero, recorre los gestores de objetos: foreach ($this->doctrine->getManagers() as $om):

// ... lines 1 - 11
final class TranslatableMappingManager
{
// ... lines 14 - 70
public function allTranslatableObjects(): iterable
{
foreach ($this->doctrine->getManagers() as $om) {
// ... lines 74 - 82
}
}
}

Las aplicaciones complejas pueden tener varios gestores de objetos, así que esto nos asegurará que los cubrimos todos.

Dentro de esto, necesitamos obtener todas las clases de entidad que admite este gestor. Así que foreach ($om->getMetadataFactory()->getAllMetadata() as $metadata):

// ... lines 1 - 11
final class TranslatableMappingManager
{
// ... lines 14 - 70
public function allTranslatableObjects(): iterable
{
foreach ($this->doctrine->getManagers() as $om) {
foreach ($om->getMetadataFactory()->getAllMetadata() as $metadata) {
// ... lines 75 - 81
}
}
}
}

Aquí, obtén el nombre de la clase de entidad con $class = $metadata->getName(). Ahora tenemos que omitir las clases que no son traducibles. Hazlo conif (!(new \ReflectionClass($class))->getAttributes(Translatable::class)). Esto devolverá una matriz vacía si la clase no tiene el atributo Translatable. En este caso, continue para pasar a la siguiente clase:

// ... lines 1 - 11
final class TranslatableMappingManager
{
// ... lines 14 - 70
public function allTranslatableObjects(): iterable
{
foreach ($this->doctrine->getManagers() as $om) {
foreach ($om->getMetadataFactory()->getAllMetadata() as $metadata) {
$class = $metadata->getName();
// ... line 76
if (!(new \ReflectionClass($class))->getAttributes(Translatable::class)) {
continue;
}
// ... lines 80 - 81
}
}
}
}

Ahora sabemos que tenemos una clase traducible, así que escribe:yield from $this->doctrine->getRepository($class)->findAll():

// ... lines 1 - 11
final class TranslatableMappingManager
{
// ... lines 14 - 70
public function allTranslatableObjects(): iterable
{
foreach ($this->doctrine->getManagers() as $om) {
foreach ($om->getMetadataFactory()->getAllMetadata() as $metadata) {
// ... lines 75 - 80
yield from $om->getRepository($class)->findAll();
}
}
}
}

Estamos devolviendo un generador que itera sobre todos los objetos traducibles en todos los gestores de objetos.

Vale, esta no es la forma más eficiente de hacerlo, ya que estamos cargando todas las entidades en memoria - potencialmente 10's de miles... Tomemos nota de esto como una futura mejora.

Volviendo a nuestro comando, SymfonyStyle tiene un método superútil para iterar cosas con una barra de progreso. Después de iniciar la variable $count, escribe:foreach ($io->progressIterate($this->mappingManager->allTranslatableObjects()) as $object):

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
// ... lines 23 - 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... lines 34 - 39
foreach ($io->progressIterate($this->mappingManager->allTranslatableObjects()) as $object) {
// ... lines 41 - 47
}
// ... lines 49 - 52
}
}

progressIterate esencialmente envuelve el iterable que le demos, y maneja la salida de la barra de progreso en el terminal por nosotros.

Dentro, crea un bucle anidado para las localizaciones: foreach ($this->locales as $locale):

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
// ... lines 23 - 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... lines 34 - 39
foreach ($io->progressIterate($this->mappingManager->allTranslatableObjects()) as $object) {
foreach ($this->locales as $locale) {
// ... lines 42 - 44
}
// ... lines 46 - 47
}
// ... lines 49 - 52
}
}

Ahora, escribe $this->localeSwitcher->runWithLocale(). Este método toma dos argumentos: la $locale a la que queremos cambiar, y un callable. Para ello use ($object, $locale):

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
// ... lines 23 - 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... lines 34 - 39
foreach ($io->progressIterate($this->mappingManager->allTranslatableObjects()) as $object) {
foreach ($this->locales as $locale) {
$this->localeAware->runWithLocale($locale, function () use ($object, $locale) {
// ... line 43
});
}
// ... lines 46 - 47
}
// ... lines 49 - 52
}
}

Cambiará la configuración regional de toda la aplicación mientras dure la llamada. Después, volverá a la configuración anterior.

Dentro de la llamada, $this->translator->... ¿Dónde está mi autocompletado? Olvidé importar la clase ObjectTranslator. Ya está.

Vuelve abajo, escribe translate($object, $locale):

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
// ... lines 23 - 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... lines 34 - 39
foreach ($io->progressIterate($this->mappingManager->allTranslatableObjects()) as $object) {
foreach ($this->locales as $locale) {
$this->localeAware->runWithLocale($locale, function () use ($object, $locale) {
$this->translator->translate($object, $locale);
});
}
// ... lines 46 - 47
}
// ... lines 49 - 52
}
}

Al final del bucle exterior, aumenta la cuenta con $count++:

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
// ... lines 23 - 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... lines 34 - 39
foreach ($io->progressIterate($this->mappingManager->allTranslatableObjects()) as $object) {
// ... lines 41 - 46
++$count;
}
// ... lines 49 - 52
}
}

Por último, antes de volver, añade un mensaje de resumen de éxito:$io->success("Warmed up the cache for {$count} translatable objects."):

// ... lines 1 - 20
final class ObjectTranslationWarmupCommand extends Command
{
// ... lines 23 - 31
protected function execute(InputInterface $input, OutputInterface $output): int
{
// ... lines 34 - 49
$io->success("Warmed up cache for {$count} translatable objects.");
// ... lines 51 - 52
}
}

Sobreescribiendo la configuración regional en translate()

Arriba, donde estamos llamando a translate(), PhpStorm no está contento con este argumento$locale. Eso es porque no existe en la firma del método.

Entra en ObjectTranslator::translate() y añádelo:?string $locale = null:

// ... lines 1 - 10
final class ObjectTranslator
{
// ... lines 13 - 33
public function translate(object $object, ?string $locale = null): object
{
// ... lines 36 - 42
}
// ... lines 44 - 64
}

Donde estamos obteniendo la configuración regional actual, intenta utilizar primero la configuración regional pasada: $locale ??:

// ... lines 1 - 10
final class ObjectTranslator
{
// ... lines 13 - 33
public function translate(object $object, ?string $locale = null): object
{
$locale = $locale ?? $this->localeAware->getLocale();
// ... lines 37 - 42
}
// ... lines 44 - 64
}

Si se pasa, se utilizará; si no, se extraerá de la petición.

De vuelta a nuestro comando, ¡PhpStorm está contento!

A continuación, conectaremos este comando y lo probaremos