Inek

Sign up y Login en PHP con Clean Architecture II

¡Tu mensaje fue recibido! Una vez que sea aprobado, estará visible para los demás visitantes.

Cerrar

21 Jun 2024

En mi post anterior, comencé a desarrollar una librería en PHP utilizando Clean Architecture para gestionar la creación de usuarios e inicio de sesión. En esta segunda parte, me enfocaré en ampliar y mejorar el caso de uso de registro (Sign Up), permitiendo flexibilidad de la librería para adaptarse a diferentes escenarios. Veremos cómo podemos estructurarla de manera que sea fácilmente extensible, permitiendo validaciones personalizadas según las necesidades específicas de cada proyecto. Esta continuación no solo busca mejorar nuestra implementación inicial, sino también proporcionar una base robusta y adaptable para futuros desarrollos.

Si todavía no tenés el código fuente, te recomiendo que descargues el repositorio de la librería en la versión en donde quedamos en el post anterior.

Permitir diferentes validaciones durante el Sign up

En la versión inicial de nuestro Sign Up, validamos, dentro del propio interactor, que el nombre de usuario sea un email:

class SignUpInteractor implements SignUpInputPort
{
    ...

    public function signUp(SignUpRequest $request): void
    {
        if (!filter_var($request->username, FILTER_VALIDATE_EMAIL)) {
            $this->output->invalidUsername($request->username);
            return;
        }

        ..
    }
}

Podríamos querer permitir nombres de usuario que no sean email, o podríamos querer que el password cumpla ciertas características. Para que nuestro caso de uso sea flexible, vamos a delegar la validación a otra clase, para la cual definiremos la siguiente interfaz:

interface SignUpValidator {
    public function validate(SignUpRequest $request): ValidationResult;
}

Con este enfoque permitimos que quien quiera usar la librería, pueda definir sus propios validadores.

Para representar posibles errors que puedan surgir de la validación, vamos a usar una clase para representar el resultado de la evaluación:

class ValidationResult
{
    private array $errors = [];

    public function addError(string $field, string $message): void
    {
        $this->errors[$field] = $message;
    }

    public function hasErrors(): bool
    {
        return count($this->errors) > 0;
    }

    public function getErrors(): array
    {
        return $this->errors;
    }
}

Ahora debemos expresar la dependencia entre el interactor y la interfaz del validador:

class SignUpInteractor implements SignUpInputPort
{
    public function __construct(
        private UserRepository $userRepository,
        private SignUpOutputPort $output,
        private ?SignUpValidator $validator = new EmailValidator()
    ) {
    }

    public function signUp(SignUpRequest $request): void
    {
        $validation = $this->validator->validate($request);
        if ($validation->hasErrors()) {
            $this->output->invalidData($validation);
            return;
        }
...
}

Por defecto, si no especificamos ningún validador, tendrá lugar la validación por correo. En la carpeta examples en el repositorio, dejo un ejemplo donde se define un validador personalizado, donde además de validar el username, se valida el password:

$validator = new class implements SignUpValidator {
    public function validate(SignUpRequest $request): ValidationResult
    {
        $validationResult = new ValidationResult();

        if (!filter_var($request->username, FILTER_VALIDATE_EMAIL)) {
            $validationResult->addError(
                'username',
                "{$request->username} is not a valid email"
            );
        }

        // validate that passwords must contains letters, numbers and some special characters
        if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d])/', $request->password)) {
            $validationResult->addError(
                'password',
                'Password must contain at least one lowercase letter, one uppercase letter, one number and one special character'
            );
        }

        return $validationResult;
    }
};

Conclusión y próximos pasos

A la implementación original del caso de uso SignUp le añadimos la posibilidad de personalizar la validación. De este modo, la librería permite definir qué tipo de username es válido.

En el próximo post vamos a añadir una entidad para representar al usuario. Esto permitirá extender esa clase para que nuestro caso de uso pueda almacenar más información durante el registro.

¿Qué te pareció el post?

No hay comentarios.