Construir DTOs al comienzo puede ser una tarea simple, fácil e incluso divertida. Pero, eso es solo al comienzo. Luego de que hayas construido varios DTOs y tengas que hacer más, te darás cuenta lo repetitivo que es e incluso hace sentirte que "pierdes" tiempo construyéndolos y validándolos.
Es por eso, que construí una librería que se encarga de validarlos y construirlos de forma dinámica. Mediante el uso de los attributes de PHP introducidos en la versión 8.0.
Dto Builder
Así le puse a esta librería. Te dejo el enlace al repositorio de Github: https://github.com/fedejuret/dto-builder
¿Cómo funciona esta librería? Es muy simple.
Pero primero, veamos cómo hariamos un DTO de forma tradicional.
class User {
private int $id;
private string $name;
private string $email;
private string $password;
public function getId(): int {
return $this->id;
}
public function setId(string $id): self {
$this->id = $id;
return $this;
}
... todos los otros getters y setters
}
De esta forma estaríamos llenando las propiedades con los valores que necesitemos. Claramente, tendríamos que ir llamando setter por setter asignándole el valor. Lo mismo con las validaciones, ¿cómo me aseguro que $email realmente sea un email?, eso sería tarea del DTO también (o del consumidor del dto, es decir, quien lo buildea).
¿Cuál es el problema con la forma tradicional?
No hay problema como tal, y puedes seguir haciendolo de esa forma si lo prefieres.
¿Entonces?
Lo que aporta esta libreria es: optimizacion de tiempos y segregacion de responsabilidades.
Al usarla, estamos construyendo y validando dtos de forma mucho mas rapida. Y, las validaciones, pasan a ser responsabilidad de otra parte de mi sistema, y no mía como desarrollador.
Cómo generar DTOs con la librería
Primero tenemos que instalar la librería en nuestro proyecto usando composer.
Para ello ejecutamos:
composer require fedejuret/dto-builder
Una vez instalada, entonces continuamos usando el ejemplo anterior:
class User {
use Loadable;
#[Property]
#[Required]
#[IsNumber]
private int $id;
#[Property]
#[Required]
#[IsString]
#[Length(min: 4, max: 50)]
private string $name;
#[Property]
#[Required]
#[IsEmail]
private string $email;
#[Property]
#[IsRegex(pattern: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/')]
private string $password;
public function getId(): int {
return $this->id;
}
... todos los otros getters
}
Bien, ahora hablemos de las diferencias entre este código y el anterior.
La primera diferencia notable son los atributos que están arriba de las propiedades.
La otra diferencia es que se está haciendo uso de un trait llamado: Loadable.
Y la última, son los setters. Se han eliminado. Solo nos quedamos con los getters dado que el scope de las propiedades es privado. Si tuvieran propiedades con scope público (cosa que no es recomedable), entonces ni getters tendrían.
Ahora voy a explicar qué es cada uno de estos puntos mencionados anteriormente.
Atributos
Los atributos en que se encuentran arriba de las propiedades son de dos tipos:
Property: El atributo #[Property] indica que esa propiedad debe asignarse su valor de forma dinámica.
Validación: Son todos los demás atributos, que básicamente son validaciones que se ejecutan antes de asignar el valor a la propiedad.
¿Cuántas validaciones hay? De momento existen todas las que puedes encontrar en este link: https://github.com/fedejuret/dto-builder/tree/master/src/Attributes/Validations
Traits: Loadable & Arrayable
El trait Loadable es quien se encarga de construir el DTO de forma dinámica. ¿Cómo lo hace? Mediante el método loadFromArray. Veamos un ejemplo:
$dto = (new User())->loadFromArray([
'id' => 1,
'name' => 'Federico Juretich',
'email' => '[email protected]',
'password' => 'SomeSecureP45W07D@'
]);
De esta manera, estoy mapeando cada índice del array con una propiedad de mí DTO. Pero, previamente a que se haga la asignación, se corren todas las validaciones que cada propiedad tiene. Entonces, si, por ejemplo, no me envían un email con formato válido, el programa lanza una excepción del tipo ValidationException
Y de esta forma sencilla, de un array que podría ser una query a la base de datos, una request, un comando, o cualquier cosa de uso que se imaginen, estoy validando y contruyendo un DTO.
El trait Arrayable lo que hace, es habilitar un método al DTO que es toArray, esto sirve para convertir el objeto User al array original.
Validaciones Custom
La librería permite que cada desarrollador cree sus propias validaciones. Para ello, veamos un ejemplo de cómo hay que hacer.-
use Attribute;
use Fedejuret\DtoBuilder\Interfaces\ValidationInterface;
#[Attribute(Attribute::TARGET_PROPERTY)]
class IsSecurePassword implements ValidationInterface {
public function validate(ReflectionProperty $property, mixed $value): void
{
if (preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/', $value) !== 1) {
throw new CustomException(); // La contraseña no es segura
}
}
}
De esta forma, ahora podremos usar el atributo IsSecurePassword sobre la propiedad que queremos validar. Y la librería automáticamente ejecutará la lógica dentro del método validate.