This course is still being released! Check back later for more chapters.
Configuración del comando de calentamiento
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeYa tenemos nuestra clase de comando de calentamiento creada, ahora necesitamos configurarla como servicio. En tus aplicaciones, esto se haría automáticamente gracias al atributo AsCommand, pero en los bundles, como en cualquier otro servicio, tenemos que configurarlo manualmente.
Abre el archivo services.php de nuestro bundle. Añádelo como nuevo servicio con->set('.symfonycasts.object_translator.warmup_command'). Clase:ObjectTranslationWarmupCommand:
| // ... lines 1 - 9 | |
| return static function (ContainerConfigurator $container): void { | |
| $container->services() | |
| // ... lines 12 - 27 | |
| ->set('.symfonycasts.object_translator.warmup_command', ObjectTranslationWarmupCommand::class) | |
| // ... lines 29 - 38 | |
| ; | |
| }; |
Añade algunos args(): El primero es el servicio traductor de objetos, así que escribeservice() y copia y pega el ID de ese servicio. El siguiente es el gestor de mapeoservice(). Copia y pega también el ID de ese servicio. El siguiente service() es el conmutador de configuraciones regionales, es el mismo servicio que usamos antes, así que copia y pega eso.
El último argumento son las configuraciones regionales activadas, escribe param('kernel.enabled_locales'):
| // ... lines 1 - 9 | |
| return static function (ContainerConfigurator $container): void { | |
| $container->services() | |
| // ... lines 12 - 27 | |
| ->set('.symfonycasts.object_translator.warmup_command', ObjectTranslationWarmupCommand::class) | |
| ->args([ | |
| service('symfonycasts.object_translator'), | |
| service('.symfonycasts.object_translator.mapping_manager'), | |
| service('translation.locale_switcher'), | |
| param('kernel.enabled_locales'), | |
| ]) | |
| // ... lines 35 - 38 | |
| ; | |
| }; |
Por último, marca este servicio como comando de consola con ->tag('console.command'):
| // ... lines 1 - 9 | |
| return static function (ContainerConfigurator $container): void { | |
| $container->services() | |
| // ... lines 12 - 27 | |
| ->set('.symfonycasts.object_translator.warmup_command', ObjectTranslationWarmupCommand::class) | |
| // ... lines 29 - 34 | |
| ->tag('console.command') | |
| // ... lines 36 - 38 | |
| ; | |
| }; |
Vamos a probar este comando en el terminal. Ejecuta:
symfony console object-translation:warmup -v
El -v nos dará un seguimiento de cualquier error que encontremos.
Y... tenemos un error. Bueno, en realidad una advertencia: "Clave de matriz indefinida 0". Fíjate en la primera línea de la traza de la pila, nos está señalando la línea 23 de TranslatableMappingManager.
Arreglo de la clave de matriz indefinida
Comprueba esa clase y encuentra la línea.
Es sutil pero, estamos intentando utilizar el operador null safe en un array. No nos protege de las claves indefinidas. Para solucionarlo, ponlo entre corchetes y añade ?? null:
| // ... lines 1 - 12 | |
| final class TranslatableMappingManager | |
| { | |
| // ... lines 15 - 20 | |
| public function translatableTypeFor(object $object): string | |
| { | |
| // ... lines 23 - 28 | |
| $type = ($class->getAttributes(Translatable::class)[0] ?? null)?->newInstance()->name ?? null; | |
| // ... lines 30 - 35 | |
| } | |
| // ... lines 37 - 90 | |
| } |
Prueba de nuevo el comando:
symfony console object-translation:warmup -v
¡Genial! ¡Ha funcionado!
Pero en realidad hay un error aún más sutil que puede aparecer aquí...
Borra la caché sin calentamiento:
symfony console cache:clear --no-warmup
Ejecuta de nuevo el comando de calentamiento:
symfony console object-translation:warmup -v
Hmm... "La clase ProxiesCG\App\Entity\Category no es traducible" ¿Qué pasa con este nombre de clase?
Contabilización de los proxies de Doctrine
Lo que ocurre es que Doctrine genera una clase proxy que extiende tu clase entidad entre bastidores. En este caso, App\Entity\Category. Esta clase proxy es la que permite a Doctrine hacer su magia.
En TranslatableMappingManager, el objeto pasado es a veces uno de estos proxies. Y esa clase proxy no tiene el atributo Translatable, ése es el problema.
Recuerda que la clase proxy extiende nuestra clase entidad. Podemos utilizar la reflexión para obtener la clase padre si detectamos que es un proxy.
Después de crear ReflectionClass, escribe if ($class->implementsInterface(Proxy::class)). Importa la clase de Doctrine\Persistence:
| // ... lines 1 - 12 | |
| final class TranslatableMappingManager | |
| { | |
| // ... lines 15 - 20 | |
| public function translatableTypeFor(object $object): string | |
| { | |
| // ... lines 23 - 24 | |
| if ($class->implementsInterface(Proxy::class)) { | |
| // ... line 26 | |
| } | |
| // ... lines 28 - 35 | |
| } | |
| // ... lines 37 - 90 | |
| } |
Todos los proxies generados tienen esta interfaz.
Dentro, escribe $class = $class->getParentClass() para obtener la verdadera clase entidad:
| // ... lines 1 - 12 | |
| final class TranslatableMappingManager | |
| { | |
| // ... lines 15 - 20 | |
| public function translatableTypeFor(object $object): string | |
| { | |
| // ... lines 23 - 24 | |
| if ($class->implementsInterface(Proxy::class)) { | |
| $class = $class->getParentClass(); | |
| } | |
| // ... lines 28 - 35 | |
| } | |
| // ... lines 37 - 90 | |
| } |
Esto debería solucionar el problema, pero confírmalo ejecutando de nuevo el borrado de caché sin calentamiento:
symfony console cache:clear --no-warmup
Ejecuta de nuevo el comando de calentamiento:
symfony console object-translation:warmup -v
¡Bien! Esta vez no hay errores, y nuestra barra de progreso muestra 12 entidades procesadas. ¡Es un resultado con estilo!
Forzar el recálculo de la caché
Ahora nos queda una última cosa por hacer. Actualmente, cuando calentamos la caché, si una traducción ya está en la caché, no se recalcula. Actualmente, este comando sólo es realmente útil para calentar una caché vacía.
Necesitamos forzar el recálculo de las traducciones, aunque ya estén en caché.
¡Los Contratos de Caché de Symfony nos cubren las espaldas!
En ObjectTranslator::translationsFor(), profundiza en el método de caché get(). En CacheInterface. ¿Ves este parámetro float $beta? Echa un vistazo a su docblock: "Un flotador que, a medida que crece, controla la probabilidad de activar la expiración anticipada.0 la desactiva, INF fuerza la expiración inmediata"
Esta función ayuda con las estampidas de caché, si un montón de elementos caducan al mismo tiempo, hay una probabilidad de que algunos caduquen antes para ayudar a repartir la carga.
Para nuestro propósito, podemos pasar infinito para forzar la caducidad inmediata.
De nuevo en ObjectTranslator::translationsFor(), añade un nuevo parámetro: bool $forceRefresh = false:
| // ... lines 1 - 10 | |
| final class ObjectTranslator | |
| { | |
| // ... lines 13 - 48 | |
| private function translationsFor(object $object, string $locale, bool $forceRefresh): array | |
| { | |
| // ... lines 51 - 68 | |
| } | |
| } |
Ahora, para el tercer argumento de cache->get(), pasa $forceRefresh ? \INF : null:
| // ... lines 1 - 10 | |
| final class ObjectTranslator | |
| { | |
| // ... lines 13 - 48 | |
| private function translationsFor(object $object, string $locale, bool $forceRefresh): array | |
| { | |
| // ... lines 51 - 53 | |
| return $this->cache->get( | |
| // ... line 55 | |
| function(ItemInterface $item) use ($locale, $type, $id) { | |
| // ... lines 57 - 66 | |
| $forceRefresh ? \INF : null, | |
| ); | |
| } | |
| } |
Si es verdadero, usa infinito como $beta para forzar la caducidad, si no, pasa null para usar el comportamiento por defecto.
Arriba en translate(), PhpStorm se queja de que necesitamos pasar este nuevo parámetro. Añade un tercer argumento al método: array $options = []:
| // ... lines 1 - 10 | |
| final class ObjectTranslator | |
| { | |
| // ... lines 13 - 33 | |
| public function translate(object $object, ?string $locale = null, array $options = []): object | |
| { | |
| // ... lines 36 - 46 | |
| } | |
| // ... lines 48 - 69 | |
| } |
Podríamos haber utilizado un parámetro específico para esto, pero utilizar una matriz de opciones facilitará añadir más opciones en el futuro.
Amplía la llamada a translationsFor() y para el tercer argumento: $options['force_refresh'] ?? false:
| // ... lines 1 - 10 | |
| final class ObjectTranslator | |
| { | |
| // ... lines 13 - 33 | |
| public function translate(object $object, ?string $locale = null, array $options = []): object | |
| { | |
| // ... lines 36 - 41 | |
| return $this->translatedObjects[$object] ??= new TranslatedObject($object, $this->translationsFor( | |
| // ... lines 43 - 44 | |
| $options['force_refresh'] ?? false, | |
| )); | |
| } | |
| // ... lines 48 - 69 | |
| } |
Por último, de vuelta en nuestro comando de calentamiento, en el bucle más interno donde estamos llamando atranslate(), añade un tercer argumento: ['force_refresh' => true]:
| // ... 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, ['force_refresh' => true]); | |
| }); | |
| } | |
| // ... lines 46 - 47 | |
| } | |
| // ... lines 49 - 52 | |
| } | |
| } |
En el terminal, vuelve a ejecutar el comando de calentamiento:
symfony console object-translation:warmup -v
No hay errores, ¡eso es buena señal!
Si quieres comprobar que la actualización se produce realmente, puedes cambiar algunas traducciones en la base de datos y, a continuación, ejecutar el comando. Deberías ver reflejados estos cambios al actualizar la página - y ninguna nueva consulta a la base de datos.
A continuación, añadiremos dos comandos más que necesitamos para la versión 1.0