loading...

Usuarios autenticados durante el procesado asíncrono de colas

lito profile image Lito ・2 min read

Laravel (así como cualquier otro framework) nos permite el uso colas para ejecutar procesos en background que puedan bloquear la carga de la página en algún punto.

El típico proceso que se ejecuta mediante colas es el envío de emails.

El problema en concreto con el envío de emails es que no dispones de posibilidad de controlar la ejecución una vez está en la cola, como sí puedes cuando trabajas con Jobs.

Si por algún motivo necesitas usar algún dato del usuario autenticado para tus plantillas, tienes que realizar la autenticación durante la ejecución de la propia cola.

Un caso presentado, por ejemplo, es el uso de un método helper llamado money que obtiene la moneda del usuario autenticado para mostrar precios con el símbolo adecuado en frontend. El problema de esto es que no se puede utilizar para, por ejemplo, enviar un mail con un pedido que refleje correctamente la moneda:

<td>{{ money($item->price) }}</td>
/**
 * @param mixed $value
 * @param int $decimals = 2
 *
 * @return string
 */
function money($value, int $decimals = 2): string
{
    return number($value, $decimals).app('currency')->symbol;
}
<?php declare(strict_types=1);

namespace App\Providers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * @return void
     */
    public function register()
    {
        $this->singletons();
    }

    /**
     * @return void
     */
    protected function singletons()
    {
        $this->app->singleton('user', function () {
            return Auth::user();
        });

        $this->app->singleton('currency', function () {
            return app('user')->currency;
        });
    }
}

Tal y como podeis seguir money hace uso del singleton currency que a su vez accede al usuario autenticado a través de user para obtener la referencia por sesión.

El problema de esto es que durante la ejecución de colas, no existe sesión, y por lo tanto no será posible obtener el usuario de referencia.

Para solucionar esto, podemos aplicar una sencilla solución como es autenticar al usuario de referencia antes de la ejecución de cada cola.

<?php declare(strict_types=1);

namespace App\Providers;

use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * @return void
     */
    public function boot()
    {
        $this->queues();
    }

    /**
     * @return void
     */
    public function register()
    {
        $this->singletons();
    }

    /**
     * @return void
     */
    protected function singletons()
    {
        $this->app->singleton('user', function () {
            return Auth::user();
        });

        $this->app->singleton('currency', function () {
            return app('user')->currency;
        });
    }

    /**
     * @return void
     */
    protected function queues()
    {
        Queue::before(function (JobProcessing $event) {
            $command = $event->job->payload()['data']['command'];

            if (preg_match('/"App\\\Models\\\User";s:2:"id";i:([0-9]+)/', $command, $matches)) {
                Auth::loginUsingId($matches[1]);
            } else {
                Auth::logout();
            }
        });
    }
}

Con este sencillo parche podremos autenticar al usuario referenciado en cada evento antes de comenzar su ejecución.

Lógicamente esta solución podría estar mucho más afinada según las necesidades de cada tarea, por ejemplo, sólo aplicarse en ciertas tareas concretas.

Posted on by:

Discussion

markdown guide