Último login de usuario en Laravel (Refactoring)

Spread the love

En la actualidad es muy común que las aplicaciones requieran una forma de guardar la ultima sesión que tuvo un usuario.

Es muy sencillo agregar esa funcionalidad en Laravel, gracias a los eventos que tiene el Login.

Pero por alguna razón, es también común notar que esta tarea se hace directamente en el LoginController, sobre escribiendo el método authenticated.

/**
     * The user has been authenticated.
     *
     * @param \Illuminate\Http\Request $request
     * @param mixed $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        $user->update([
            'last_login_at' => Carbon::now(),
            'last_login_ip' => $request->getClientIp()
        ]);

    }

Pues bien, vamos a ver como podemos refactorizar esto, usando eventos y sin romper la funcionalidad original.

Creando prueba para el Login

comprobar-validaciones

Antes de comenzar cualquier movimiento en el código es recomendable crear una prueba.

La prueba nos asegura que no cambiemos el funcionamiento original conforme introducimos nuestros cambios.

Creando el archivo de pruebas.

Crear la prueba es muy sencillo usando el comando artisan.

$ php artisan make:test LoginControllerTest

Después de crear la prueba, abrimos el archivo tests/Feature/LoginControllerTest y agregamos el siguiente método:

/**
     * @test
     */
    public function successful_user_login_saves_date_and_ip_address(): void
    {
        Carbon::setTestNow(Carbon::createFromFormat('Y-m-d H:i:s', '2019-06-06 18:00:00'));

        $user = factory(User::class)->create([
            'password' => bcrypt($password = 'i-love-laravel'),
        ]);

        $response = $this->post(route('login'), [
            'email' => $user->email,
            'password' => $password,
        ]);

        $response->assertRedirect(route('home'));
        $this->assertAuthenticatedAs($user);

        $this->assertDatabaseHas('users', [
            'id' => $user->id,
            'last_login_at' => '2019-06-06 18:00:00',
            'last_login_ip' => '127.0.0.1'
        ]);
    }

¿Que hace la prueba?

La prueba valida que un usuario que logre sesión en el sistema, también guardara la fecha, la hora y la ip en la base de datos.

Pero que te parece si te explico cada parte del código

Creando fecha y hora de prueba

La prueba lo primero que hace es decirle a Carbon::now() que regrese la fecha y hora señaladas (‘2019-06-06 18:00:00’).

sin importar cuantas veces se llame, regresara el mismo resultado.

Carbon::setTestNow(Carbon::createFromFormat('Y-m-d H:i:s', '2019-06-06 18:00:00'));

Creando usuario de prueba

En este paso creamos un usuario en la base de datos para realizar el Login.

$user = factory(User::class)->create([
            'password' => bcrypt($password = 'i-love-laravel'),
        ]);

Lanzamos petición de inicio de sesión

Esta parte hace exactamente lo mismo que el formulario de login, cuando usas el botón de submit.

$response = $this->post(route('login'), [
            'email' => $user->email,
            'password' => $password,
        ]);

Si te das cuenta realizamos una petición post con el correo y el password que usamos para crear nuestro usuario.

Esta petición nos proporciona una respuesta ($response) que vamos a evaluar en los siguientes pasos.

Comprobamos el response

En este paso vamos a comprobar que la respuesta ($response) genero una redirección a la ruta home y que ademas el usuario realmente esta autentificado en el sistema.

$response->assertRedirect(route('home'));
        $this->assertAuthenticatedAs($user);

Validamos que se actualiza la tabla users.

Finalmente validamos que para el usuario que creamos, se actualizaron los campos last_login_at y last_login_ip en la tabla users.

$this->assertDatabaseHas('users', [
            'id' => $user->id,
            'last_login_at' => '2019-06-06 18:00:00',
            'last_login_ip' => '127.0.0.1'
        ]);

Si ejecutas la prueba debe de pasar sin problemas ya que solo estamos comprobando el funcionamiento original.

Es hora de comenzar los cambios.

Refactorizamos

Esto lo vamos a hacer moviendo la lógica que tenemos en el método authenticated a un Listener que escuche el evento Login que se dispara cuando un usuario realiza un a sesión exitosa en la aplicación.

Si el intento falla el evento no se dispara y por lo tanto no debe de actualizarse la base de datos para los campos last_login_at y last_login_ip

Crear Listener para el evento Login

Para poder guardar la fecha, hora e ip del usuario, vamos a tener que crear y registrar un Listener (SuccessfulLogin) que escuche o espere el evento Login.

El evento Login y el Listener se registran editando el EventServiceProvider,

Registra el Evento y el Listener.

Edita el archivo app/Providers/EventServiceProvider.

protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        Login::class => [
            SuccessfulLogin::class,
        ],
    ];

Observa que el evento se coloca en el key del arreglo y el Listener en el value (SuccessfulLogin).

Después de registrar el evento, tenemos que crear el Listener, usando el siguiente comando.

$ php artisan event:generate

Muy bien, ya casi terminamos.

Edita el archivo app/Listeners/SuccessfulLogin.php y agrega lo que esta en el método authenticated().

class SuccessfulLogin
{
    /**
     * @var Request
     */
    private $request;
    /**
     * @var Carbon
     */
    private $carbon;

    /**
     * Create the event listener.
     *
     * @param Request $request
     */
    public function __construct(Request $request, Carbon $carbon)
    {
        $this->request = $request;
        $this->carbon = $carbon;
    }

    /**
     * Handle the event.
     *
     * @param  Login  $event
     * @return void
     */
    public function handle(Login $event) : void
    {
        $event->user->update([
            'last_login_at' => $this->carbon,
            'last_login_ip' => $this->request->ip(),
        ]);
    }
}

Eliminamos el método autheticated() del LoginController. Y ejecutamos la prueba para verificar que todo esta correcto.

Validando mediante prueba

Ejecuta la prueba desde la terminal.

λ vendor\bin\phpunit --filter=LoginControllerTest::successful_user_login_saves_date_and_ip_address
PHPUnit 7.5.12 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 2.4 seconds, Memory: 28.00 MB

OK (1 test, 6 assertions)

Como puedes observar todo funciona de forma correcta.

Te recomiendo que hagas estos pasos ejecutando la prueba en cada cambio, para que veas que resultados obtienes.

Que sigue.

Hasta este punto notaras que las pruebas nos ayudan a verifica que cada cambios que hacemos no rompa la funcionalidad original.

Lo siguiente que se puede hacer es escribir pruebas para validar de forma separada el Listener y que se dispara el evento de Login.

Otro punto importante que debes de notar es que el código en el Listener SuccessfulLogin puede ser mejorado.

Desde mi punto de vista el usuario tiene conocimiento de información que no le pertenece, así que fácilmente se puede pasar a otro modelo o clase que se encargue de hacer esa tarea.

Estaría bien si nos dejas un comentario para saber que te parece el articulo.