Buy Access to Course
18.

Recurso API de usuario

|

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

Tenemos una entidad User... pero aún no forma parte de nuestra API. ¿Cómo hacemos que forme parte de la API? Ah, ¡ya lo sabemos! Ve encima de la clase y añade el atributo ApiResource.

118 lines | src/Entity/User.php
// ... lines 1 - 4
use ApiPlatform\Metadata\ApiResource;
// ... lines 6 - 12
#[ApiResource]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 16 - 116
}

Actualiza la documentación. ¡Fíjate! ¡Seis nuevas rutas para la clase User! Y gracias a nuestros fixtures, deberíamos poder ver los datos inmediatamente. Probemos la ruta de recogida. Ejecuta y... está vivo.

Aunque... es un poco raro que aparezcan campos como roles y password. Ah, nos preocuparemos de eso en un minuto.

API Platform y UUIDs

Antes de seguir avanzando, quiero mencionar una cosa rápida sobre los UUID. Como puedes ver, estamos utilizando UUID autoincrementados para nuestra API: siempre es/api/users/ y luego el id de la entidad. Pero puedes utilizar un UUID en su lugar. Y eso es algo que haremos en un futuro tutorial.

Pero... ¿por qué utilizar UUIDs? Bueno, a veces puede hacer la vida más fácil en JavaScript cuando se trabaja con frameworks frontales. De hecho, puedes generar elUUID en JavaScript y luego enviarlo a tu API al crear un nuevo recurso. Esto puede ser útil porque tu JavaScript conoce el identificador del recurso inmediatamente y puede actualizar el estado... en lugar de esperar a que termine la petición Ajax para obtener el nuevo identificador autoincrementado.

En cualquier caso, lo que quiero decir es que API Platform admite UUIDs. Podrías añadir una nueva columna UUID y decirle a API Platform que ese debe ser tu identificador. Ah, pero ten en cuenta que algunos motores de bases de datos -como MySQL- pueden tener un rendimiento deficiente si haces del UUID la clave primaria. En ese caso, mantén id como clave principal y añade una columna UUID adicional.

Añadir los grupos de serialización

En cualquier caso, ¡volvamos a nuestro recurso User! Ahora mismo, devuelve demasiados campos. Afortunadamente, sabemos cómo solucionarlo. Arriba, en ApiResource, añade una clavenormalizationContext con groups establecida en user:read para seguir el mismo patrón que utilizamos en DragonTreasure. Añade también denormalizationContextfijado en user:write.

125 lines | src/Entity/User.php
// ... lines 1 - 13
#[ApiResource(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']],
)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 20 - 123
}

Ahora sólo tenemos que decorar los campos que queramos en la API. No necesitamos id... ya que siempre tenemos @id, que es más útil. Pero sí queremos email. Así que añade el atributo #Groups(), pulsa tabulador para añadir esa declaración use y pasa user:read y user:write.

125 lines | src/Entity/User.php
// ... lines 1 - 9
use Symfony\Component\Serializer\Annotation\Groups;
// ... lines 11 - 13
#[ApiResource(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']],
)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 20 - 25
#[Groups(['user:read', 'user:write'])]
private ?string $email = null;
// ... lines 28 - 123
}

Copia eso... y baja a password. Necesitamos que la contraseña sea escribible pero no legible. Así que añade user:write.

125 lines | src/Entity/User.php
// ... lines 1 - 9
use Symfony\Component\Serializer\Annotation\Groups;
// ... lines 11 - 13
#[ApiResource(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']],
)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 20 - 25
#[Groups(['user:read', 'user:write'])]
private ?string $email = null;
// ... lines 28 - 35
#[Groups(['user:write'])]
private ?string $password = null;
// ... lines 38 - 123
}

Esto todavía no es del todo correcto. El campo password debe contener la contraseña cifrada. Pero nuestros usuarios, por supuesto, enviarán las contraseñas en texto plano a través de la API cuando creen un usuario o actualicen su contraseña. Entonces haremos el hash. Eso es algo que resolveremos en un tutorial futuro, cuando hablemos más de seguridad, pero esto bastará por ahora.

Ah, y encima de username, añade también user:read y user:write.

125 lines | src/Entity/User.php
// ... lines 1 - 9
use Symfony\Component\Serializer\Annotation\Groups;
// ... lines 11 - 13
#[ApiResource(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']],
)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 20 - 25
#[Groups(['user:read', 'user:write'])]
private ?string $email = null;
// ... lines 28 - 35
#[Groups(['user:write'])]
private ?string $password = null;
// ... lines 38 - 39
#[Groups(['user:read', 'user:write'])]
private ?string $username = null;
// ... lines 42 - 123
}

¡Genial! Actualiza los documentos... y abre la ruta de las colecciones para probarlo. El resultado... ¡exactamente lo que queríamos! Sólo vuelven email y username.

Y si creáramos un nuevo usuario... ¡sí! Los campos escribibles son email,username, y password.

Añadir validación

Vale, ¿qué más nos falta? ¿Qué tal la validación? Si probamos la ruta POST con datos vacíos... obtendremos el desagradable error 500. ¡Hora de arreglarlo!

De nuevo en el archivo, empieza por encima de la clase para asegurarte de que tanto email comousername son unique. Añade UniqueEntity pasando fields a email... e incluso podemos incluir un mensaje. Repite lo mismo... pero cambia emailpor username.

131 lines | src/Entity/User.php
// ... lines 1 - 10
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
// ... lines 12 - 18
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
#[UniqueEntity(fields: ['username'], message: 'It looks like another dragon took your username. ROAR!')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 23 - 129
}

A continuación, abajo en email, añade NotBlank... luego añadiré el Assert delante... y retocaré la declaración use para que funcione igual que la última vez.

131 lines | src/Entity/User.php
// ... lines 1 - 10
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
// ... lines 13 - 18
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
#[UniqueEntity(fields: ['username'], message: 'It looks like another dragon took your username. ROAR!')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 23 - 29
#[Assert\NotBlank]
// ... line 31
private ?string $email = null;
// ... lines 33 - 129
}

Bien. el correo electrónico necesita uno más - Assert\Email - y encima de username, añadir NotBlank.

131 lines | src/Entity/User.php
// ... lines 1 - 10
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
// ... lines 13 - 18
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
#[UniqueEntity(fields: ['username'], message: 'It looks like another dragon took your username. ROAR!')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// ... lines 23 - 29
#[Assert\NotBlank]
#[Assert\Email]
private ?string $email = null;
// ... lines 33 - 45
#[Assert\NotBlank]
private ?string $username = null;
// ... lines 48 - 129
}

Ahora mismo no me preocupa demasiado password... porque ya es un poco raro.

¡Vamos a probar esto! Desplázate hacia arriba y envía un campo password. Y... ¡sí! El simpático código de estado 422 con errores de validación. Prueba ahora con datos válidos: pasa un email y un username... aunque no estoy seguro de que este tipo sea realmente un dragón... quizá necesitemos un captcha.

Pulsa Ejecutar. Ya está ¡Código de estado 201 con email y username devueltos!

Nuestro recurso tiene validación, paginación y contiene una gran información! E incluso podríamos añadir filtros fácilmente. En otras palabras, ¡lo estamos machacando!

Y ahora llegamos a la parte realmente interesante. Tenemos que "relacionar" nuestros dos recursos para que cada tesoro pertenezca a un usuario. ¿Qué aspecto tiene eso en API Platform? Es superinteresante, y es lo siguiente.