Aplica el Patrón de Diseño Command en LARAVEL

Spread the love

El Patrón Command separa un objeto que invoca una operación del que sabe cómo llevarla a cabo, es decir que un objeto puede solicitar digamos una operación de edición sin conocer como se realiza realmente.

pero es muy seguro que te preguntes:

¿Qués es una operación?

Cualquier acción que afecta el estado de otro objeto, puede ser considerado una operación como por ejemplo cuando oprimes el boton de actualización de un formnulario la petición del navegador seria esa acción que se ejecutara finalmente en un controlador.

Pero veamos esto con un ejemplo sencillo para que quede clara la idea.

Imagina que tienes el siguiente método update de un controlador:

<?php

namespace App\Http\Controllers;

//...

class UserController extends Controller
{
    //...

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
    {
        $user->update(
            collect($request->validated())->filter()->all()
        );

        $user->notify(new UpdatedProfileNotification());

        return back()->withSuccess('Successful update data');
    }

    //...
}

Hasta aqui todo va bien , pero si mañana decides que quieres editar el usuario con un comando de artisan tendrias que copiar el código.

Y esto no es muy optimo porque cambios en la logica en el controlador probablemente tambien requieran su ajuste en el comando de artisan.

Y es aqui donde el patrón comando se vuelve muy util, ya que nos ayuda a mover las operaciones como su fueran una unidad que podemos usarl donde lo necesitemos.

Por ejemplo nuestro comando podria ser algo como esto:

<?php

namespace App\Http\Controllers;

//...

class UserController extends Controller
{
    //...

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
    {
        UpdateCommand::new($user, $request->validated())
            ->execute();

        return back()->withSuccess('Successful update data');
    }

    //...
}

¿Qué te parece?

Ahora vamos a ver paso a paso como lograr ese resultado, pero antes veamos de que va el patrón command.

Repositorio

Si quieres ver todos los detalles de la implementación Seba Carrasco Poblete a realizado un gran trabajo creando un repositorio que recrea este ejemplo basándose en la explicación 👍🏼

Vídeo

¿Qué es el Patrón de Diseño Command?

El Patrón Command encapsula una solicitud como un objeto, lo que le permite parametrizar los clientes con diferentes solicitudes, poner en cola o registrar las solicitudes, y admitir las operaciones realizables.

GoF

¿Cuándo debes de aplicar el patrón Command?

Utiliza el patrón Command cuando quieras parametrizar objetos con operaciones: Con parametrización se refiere a que el comando encapsula al objeto que hace la tarea (Ejemplo: salvar un modelo) y los parametros necesarios para hacer esa tarea, de esta forma el comando puede ser usado en cualquier lugar donde se requiera ejecutar la acción que representa el comando.

Utiliza el patrón Command cuando quieras poner operaciones en cola, programar su ejecución, o ejecutarlas de forma remota: Los comandos al ser clases relativamente sencillas son muy utiles ya que se pueden serilizar y usarlos para ser ejecitados mediantes queues de forma posterior.

Utiliza el patrón Command cuando quieras implementar operaciones reversibles. Debido a que se pueden serializar como cualquier clase de php, es posible almacenarlos y con esto es posible implementar operaciones para deshacer cambios. Por ejemplo puedes requerir deshacer el borrado de un usuario de la base de datos.

📕 Estructura del Patrón Command

Modelo UML Patrón Command
Modelo del Patrón Command

De la grafica de clases que participan en este patrón es importante que notes que la clase Invoker puede o no estar y puede ser un servicio o culquier objeto que le pueda dar uso al comando.

La idea del comando concreto es que el invoker pueda solicitar una operación o acción sin saber como se realiza, por ejemplo en lugar de invocar el método update directamente de un modelo(Receiver), lo que hacemos es pasar un objeto ModelUpdate, y dentro de esta clase se llamaria al método update de ese modelo.

Obviamente para lograr esto el comando debe de recibir como parametro el modelo y los parametros a usar.

Vamos a ver esto con mas detalle en la imprementación

🎯 Participantes del Modelo

  • Command: define la interfaz de ejecución de operaciones
  • ConcreteCommand (StoreCommand, UpdateCommand): Implementa
    la interfaz de ejecución invocando operaciones del Receiver. De este modo relaciona una acción con un Receiver (receptor)
  • Client (Application): crea un comando concreto (ConcreteCommand) e indica a quién va
    dirigido (receiver)
  • Invoker: contiene el comando asociado a la petición
  • Receiver: sabe cómo realizar las operaciones asociadas a una petición. Cualquier clase puede actuar como receptor.

🛠 Cómo funciona

Vamos a leer el diagrama de izquierda a derecha pero comenzando desde el cliente que finalmente es el que ejecuta todo el código.

  1. El cliente crea un objeto ConcreteCommand y especifica su Receiver new Command($receiver).
  2. Un objeto Invoker almacena el objeto ConcreteCommand setCommand($command).
  3. El Invoker emite una petición llamando a execute en el comando. $command->execute().
  4. El objeto ConcreteCommand invoca operaciones en su Receiver para llevar a cabo
    la solicitud $receiver->action().

Siguiendo las instrucciones desde un controlador tendrias un código como el que sigue:

$command = new ConcreteCommand(new Receiver());
$invoker = (new Invoker)->setCommand($command);

//Esto ejecuta de forma interna $command->execute()
$invoker->execute(); 

Solucionemos el Problema

Muy bien, ya viste de que va el patrón command, ahora es momento de implementar una solución para la operación del método update de UserController.

Implementación patron command

Para nuestro caso el cliente sera el controlador, el invoker sera una clase UserService, pero no la voy a implementar. la operación de actualización sera el comando UpdateCommand y el Receiver sera el modelo User como se muestra en el diagrama.

Crear una interfaz para los comandos

Todas las operaciones (Edit, Store, etc), seran representadas por la interfaz CommandInterface.

namespace App\Services\Commands\Users;

interface CommandInterface
{
    public function execute();
}

Implementa Comandos Concretos

El siguiente paso es ir creando comando para cada operación, para nuestro ejemmplo tomaremos el método update de UserController y lo usaremos para crear el comando UpdateCommand.

<?php

namespace App\Services\Commands\Users;

use App\Models\User;
use App\Notifications\UpdatedProfileNotification;

class UpdateCommand implements CommandInterface
{
    private User $user;
    private array $parameters;

    public function __construct(User $user, array $parameters)
    {
        $this->user = $user;
        $this->parameters = $parameters;
    }

    public function execute(): User
    {
        $this->user->update(
            collect($this->parameters)->filter()->all()
        );

        $this->user->notify(new UpdatedProfileNotification());

        return $this->user;
    }

    public static function new(User $user, array $parameters)
    {
        return new static($user, $parameters);
    }
}

Observa que el comando hace uso de un named constructor para evitar hacer un new explicito cuando se utilice.

Actualiza el código con la solución.

Para finalizar solo tenemos que actualizar UserController

<?php

namespace App\Http\Controllers;

//...

class UserController extends Controller
{
    //...

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
    {
        UpdateCommand::new($user, $request->validated())
            ->execute();

        return back()->withSuccess('Successful update data');
    }

    //...
}

Y eso es todo, ahora queda un código mas claro y podemos usar el comando sin cambios para actualizar el modelo User desde un Api Rest, o usarlo en un Comando de artisan.

Si no te gusta como se ve el código, puedes moverlo a una clase que funcione como el Invoker que puede ser la clase UserService como indique en el diagrama de clases.

<?php

namespace App\Http\Controllers;

//...

class UserController extends Controller
{
    //...

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
    {
        UserService::new()
            ->handle(UpdateCommand::new($user, $request->validated()));

        return back()->withSuccess('Successful update data');
    }

    //...
}

Tambien como lo mencione antes, es posible usar el comando sin cambios en otras clases como por ejemplo en un comando de artisan:

class UpdateUserCommand extends Command
{
    //...

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        //..
        $user = User::whereEmail($email)->firstOrFail();

        UpdateCommand::new($user, $parameters); //👍🏼

        $this->info('The command was successful!');

        return 0;
    }
}

Conclusión

El patrón command como vimos es muy util cuando queremos representar operaciones como objetos y esto nos permite separar de mejor forma el código de nuestas aplicaciones.

En general este patrón no proporciona algunas ventajas.

  • Desacopla el objeto que invoca la operación del que sabe cómo llevarla a cabo.
  • Los comandos se pueden manipular y extender como cualquier otro objeto.
  • Es fácil añadir nuevos comandos ya que no es necesario cambiar las clases existentes.
  • Son relativamente sencillos de testear.

¿Te gusto el articulo?

Entonces no olvides dejar tu comentario