Login to bookmark this video
Buy Access to Course
15.

TDD `TranslatedObject` Traducciones

|

Share this awesome video!

|

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

This Chapter isn't quite ready...

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

La lógica actual de nuestro TranslatedObject es sólida. En efecto, pasa todas las llamadas a métodos y accesos a propiedades al objeto subyacente. Y... ¡tenemos las pruebas que lo demuestran!

Anteriormente utilizamos TDD para solucionar el problema de las llamadas a métodos Twig. Ahora, ¡utilicémoslo para crear una función!

Nuestro TranslatedObject necesita gestionar traducciones de propiedades. Antes de llamar al método o propiedad del objeto subyacente, debe comprobar si existe un valor traducido. Si existe, debe devolver ese valor traducido.

¡Manos a la obra!

Empieza con pruebas verdes

Primero, antes de hacer nada, confirma que nuestro conjunto de pruebas es todo verde. En tu terminal, ejecuta:

symfony php vendor/bin/phpunit object-translation-bundle/tests

Verde, ¡genial! Empezar una nueva función con TDD es una tontería si tus pruebas ya están fallando.

Nueva prueba

De vuelta en TranslatedObjectTest, añade una nueva prueba con:public function testCanTranslateProperties():

// ... lines 1 - 7
class TranslatedObjectTest extends TestCase
{
// ... lines 10 - 27
public function testCanTranslateProperties()
{
// ... lines 30 - 40
}
}
// ... lines 43 - 60

Escribimos esta prueba con la lógica que queremos ver. Para ello, copia la fase de configuración de la prueba anterior y pégala aquí.

Ahora, para el segundo argumento del constructor TranslatedObject, éste será un array de propiedades traducidas. A continuación traduciremos todas las propiedades de nuestroObjectForTranslationStub. Dentro de la matriz, escribe'prop1' => 'translated1', 'prop2' => 'translated2', 'prop3' => 'translated3',:

// ... lines 1 - 7
class TranslatedObjectTest extends TestCase
{
// ... lines 10 - 27
public function testCanTranslateProperties()
{
$object = new TranslatedObject(new ObjectForTranslationStub(), [
'prop1' => 'translated1',
'prop2' => 'translated2',
'prop3' => 'translated3',
]);
// ... lines 35 - 40
}
}
// ... lines 43 - 60

Puedes ver que PhpStorm está marcando todo esto como gris, ya que el constructor aún no acepta este parámetro.

Creo que esto queda bastante bien. Cuando pasas un array de valores traducidos, con clave por nombre de propiedad, al acceder a estas propiedades, deberíamos obtener de vuelta los valores traducidos.

Para las afirmaciones, copia éstas de la primera prueba y pégalas aquí. Ahora, cambia todos los valores esperados de value a translated. translated1,translated2, y translated3:

// ... lines 1 - 7
class TranslatedObjectTest extends TestCase
{
// ... lines 10 - 27
public function testCanTranslateProperties()
{
// ... lines 30 - 35
$this->assertSame('translated1', $object->prop1);
$this->assertTrue(isset($object->prop1), 'Public property should be accessible');
$this->assertFalse(isset($object->prop2), 'Private property should not be accessible');
$this->assertSame('translated2', $object->prop2());
$this->assertSame('translated3', $object->getProp3());
}
}
// ... lines 43 - 60

Creo que todos sabemos que esto no va a funcionar pero... ¡dejemos que las pruebas nos lo digan!

En tu terminal, vuelve a ejecutar las pruebas:

symfony php vendor/bin/phpunit object-translation-bundle/tests

¡Fallo! Pero totalmente esperado. Fallo al afirmar que "valor1" es "traducido1" en la línea 36.

De vuelta a la prueba, en la línea 36 es donde accedemos a la propiedad prop1. Así que, ¡añadamos la lógica para que esta prueba pase!

Esta prueba... impulsa... nuestro desarrollo, ¿lo pillas?

Inyectar traducciones

En TranslatedObject, añade una nueva propiedad al constructor:private array $_translations, - recuerda que el prefijo _ es una convención que utilizamos porque se trata de un mixin.

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 14
public function __construct(
// ... line 16
private array $_translations,
) {
}
// ... lines 20 - 40
}

Arriba, añade un bloque doc @param para este nuevo parámetro, de tipo: array. Seamos inteligentes y especifiquemos los tipos de clave y valor de esta matriz. Dentro de los corchetes angulares, escribe string,string. El primer string es el tipo de clave, el nombre de la propiedad, y el segundo string es el tipo de valor, el valor traducido. Por último, escribe $_translations para terminar este doc block.

Traducir el acceso a la propiedad

Recuerda que nuestra prueba falla al acceder a una propiedad. Así que, abajo en el método__get(), antes de devolver la propiedad interna, escribe$this->_translations[$name] ??:

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 31
public function __get(string $name): mixed
{
return $this->_translations[$name] ?? $this->_inner->$name;
}
// ... lines 36 - 40
}

Esto comprobará si existe un valor traducido para este nombre de propiedad. Si existe, lo devolverá. Si no, volverá a devolver la propiedad interna del objeto.

Bien, vuelve al terminal y ejecuta de nuevo las pruebas:

symfony php vendor/bin/phpunit object-translation-bundle/tests

Sigue fallando... pero fíjate bien: ahora falla porque "traducido2" y "valor2" no coinciden, y en la línea 39.

Vuelve a la prueba. En la línea 39 es donde llamamos al método prop2(). Que haya llegado tan lejos significa que nuestra lógica de acceso a la propiedad traducida de la línea 36 ¡funciona! ¡Genial!

Traducir llamadas a métodos

Ahora vamos a manejar las llamadas a métodos. En TranslatedObject::__call(), en la parte superior, añade if (isset($this->_translations[$name])). Dentro,return $this->_translations[$name];.

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 20
public function __call(string $name, array $arguments): mixed
{
if (isset($this->_translations[$name])) {
return $this->_translations[$name];
}
// ... lines 26 - 33
}
// ... lines 35 - 44
}

Esto comprueba si existe un valor traducido para este nombre exacto de método. Si existe, devuelve ese valor.

¡Ya sabes lo que tienes que hacer! De vuelta al terminal, ejecuta de nuevo las pruebas:

symfony php vendor/bin/phpunit object-translation-bundle/tests

Ahora falla en la línea 40 - "traducido3" y "valor3". Comprueba esta línea en nuestra prueba. Ahh... el método getter... Tenemos que tenerlo en cuenta, pero más o menos al revés de lo que hicimos con el problema de la llamada al método Twig. Tenemos que comprobar si el nombre del método existe como propiedad traducida sin el prefijo get. ¡Qué complicado!

Traducir métodos Getter

Vuelve a comprobarlo en TranslatedObject::__call(). Este método se está haciendo un poco largo, así que vamos a refactorizarlo y añadir nuestra nueva lógica en un método privado. A continuación, escribe private function translatedValue(string $name): ?string:

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 45
private function translatedValue(string $name): ?string
{
// ... lines 48 - 58
}
}

Esto aceptará el nombre del método y devolverá el valor traducido como una cadena, o null si no existe.

Vuelve a __call(), corta la declaración if (isset(...)) y pégala en nuestro nuevo método privado:

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 45
private function translatedValue(string $name): ?string
{
if (isset($this->_translations[$name])) {
return $this->_translations[$name];
}
// ... lines 51 - 58
}
}

Esto comprueba si el nombre exacto del método existe como propiedad traducida.

A continuación, escribe if (!str_starts_with($name, 'get')). Esto comprueba si el nombre del método no es un getter. No hay nada que hacer en este caso, así que, return null:

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 45
private function translatedValue(string $name): ?string
{
// ... lines 48 - 51
if (!str_starts_with($name, 'get')) {
return null;
}
// ... lines 55 - 58
}
}

A continuación, escribe $property = lcfirst(substr($name, 3)). substr corta los 3 primeros caracteres del nombre -que sabemos que es get. lcfirst escribe el primer carácter en minúsculas, dejándonos el nombre de la propiedad:

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 45
private function translatedValue(string $name): ?string
{
// ... lines 48 - 55
$property = lcfirst(substr($name, 3));
// ... lines 57 - 58
}
}

Por último, return $this->_translations[$property] ?? null. Devuelve el valor traducido de esta propiedad si existe; si no, devuelve null:

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 45
private function translatedValue(string $name): ?string
{
// ... lines 48 - 57
return $this->_translations[$property] ?? null;
}
}

De vuelta a __call(), comprueba si existe un valor traducido conif ($translatedValue = $this->translatedValue($name)). Dentro,return $translatedValue:

// ... lines 1 - 9
final class TranslatedObject
{
// ... lines 12 - 20
public function __call(string $name, array $arguments): mixed
{
if ($translatedValue = $this->translatedValue($name)) {
return $translatedValue;
}
// ... lines 26 - 33
}
// ... lines 35 - 59
}

Ejecuta las pruebas, ¡ejecuta!

symfony php vendor/bin/phpunit object-translation-bundle/tests

Hmm, tenemos algunos errores. Desplázate un poco hacia arriba para ver el resumen. Las dos primeras pruebas dieron error, pero nuestra tercera prueba, la de las propiedades traducidas, está pasando. Son las pruebas originales las que tienen problemas. ¡Por eso era importante ejecutar las pruebas antes de iniciar esta función! Sabemos con seguridad que hicimos algo que rompió la funcionalidad existente.

Arreglar la funcionalidad existente

Comprueba el error "Demasiados pocos argumentos para... TranslatedObject::__construct()"

Ahh, hemos añadido un nuevo parámetro obligatorio al constructor de esta clase. Las dos primeras pruebas no están pasando la matriz $_translations.

En nuestra clase de prueba, desplázate hasta las dos primeras pruebas. PhpStorm incluso nos advierte de esto. En ambas pruebas, pasa un array vacío como segundo argumento:

// ... lines 1 - 7
class TranslatedObjectTest extends TestCase
{
public function testCanAccessUnderlyingObject()
{
$object = new TranslatedObject(new ObjectForTranslationStub(), []);
// ... lines 13 - 18
}
// ... line 20
public function testCallUsesGetterIfAvailable()
{
$object = new TranslatedObject(new ObjectForTranslationStub(), []);
// ... lines 24 - 25
}
// ... lines 27 - 41
}
// ... lines 43 - 60

¿Ya hemos terminado? Averígualo volviendo a ejecutar nuestras pruebas:

symfony php vendor/bin/phpunit object-translation-bundle/tests

¡Woo! ¡Todas las pruebas son correctas! ¡Nueva función añadida con éxito!

A continuación, daremos un paso al lado y veremos cómo marcaremos las entidades de nuestra aplicación para su traducción.