Command Pattern en PHP

Francisco Ugalde
Jun 12, 2020 · 4 min read

El patrón Comando ó Command Pattern es un patrón de comportamiento muy común a tener en cuenta en nuestros desarrollos.

El principio de este patrón es convertir operaciones o requests en objetos.

Encapsular una petición como un objeto, de modo que puedan parametrizarse otros objetos con distintas peticiones o colas de peticiones.

Command Pattern

La idea se basa en la necesidad de ejecutar una acción, para la cual creamos un objeto el cual es denominado Command con la información requerida (Propiedades) para ser luego procesada por un handler.

Cada comando al ser despachado, tendrá su propio handler el cual sabra que acción debe realizar con los datos recibidos por el comando.

Los actores que están involucrados en este patrón son:

  • Cliente/Invoker: Es quien inicia la acción a ejecutar, puede ser un controlador o comando de shell.
  • Command: Es la clase que encapsula todos los datos requeridos para la ejecución de la acción. Los Command actúan como simples DTO’s por lo que son clases que tienen que ser fácilmente serializables.
  • CommandHandler/Handler: Es la clase que es invocada en función del comando despachado y la cual contiene toda la lógica de la acción a realizar.

Por convención, todos los handlers deben tener un método en común para ejecutar los comandos. Este método puede llamarse “handle” ó “execute”, o simplemente podemos aprovechar el método mágico de php y llamar al método __invoke.

Adicionalmente, los comandos deberían implementar una misma interfaz Command y todos los handlers una interfaz CommandHandler con un método handle, execute o __invoke por ejemplo.

De esta manera podemos aprovechar el type hinting para que cada handler sepa a qué comando le corresponde atender.

Por razones de comodidad y estandarización, yo particularmente prefiero utilizar el método mágico __invoke de php para los handlers pero es cuestión de cada quien cómo más cómodo con la implementación se sienta.

Pero realmente lo recomiendo usar puesto que se ha convertido en una buena práctica de implementación para este patrón de diseño.

Puedes leer un poco más sobre el método mágico de php __invoker

Lectura recomendada

Ejemplo de implementación

namespace Application\Command\User;

final class RegisterUserCommand
{
private $name
private $email;
private $password;

public function __construct
(
string $name,
string $email,
string $password
){
$this->name = $name;
$this->email = $email;
$this->password = $password;
}

public function name(): string
{
return $this->name;
}

public function email(): string
{
return $this->email;
}

public function password(): string
{
return $this->password;
}
}
namespace Application\CommandHandler;interface CommandHandlerInterface
{
public function __invoke();
}
namespace Application\CommandHandler\User;use Application\Command\User\RegisterUserCommand;
use Application\CommandHandler\CommandHandlerInterface
use Application\Domain\Repository\UserRepository;
use Application\Entity\User;
final class RegisterUserHandler implements CommandHandlerInterface
{
const MINIMUM_LENGHT = 15;

private $repository;

public function __construct (UserRepository $repository)
{
$this->repository = $repository;
}

public function __invoke(RegisterUserCommand $command)
{
$name = $command->name();
$email = $command->email();
$password = $command->password();

$this->checkEmail();
$this->checkPassword();

$user = new User($name, $email, $password);
$this->repository->save($user);
}

private function checkEmail($email)
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \Exception('Invalid email');
}

if ($this->repository->userExists($email)) {
throw new \Exception('User already exist');
}
}

private function checkPassword($password)
{
if (self::MINIMUM_LENGHT > strlen($password)) {
throw new \Exception('Password too short');
}
}
}
namespace Application\Controller\User;use Application\Command\User\RegisterUserCommand;
use Application\CommandHandler\User\RegisterUserHandler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
final class UserController
{
public function registerUser(Request $request): Response
{
$name = $request->request->get('name');
$email = $request->request->get('email');
$password = $request->request->get('password');

$command = new RegisterUserCommand($name, $email, $password);

$userRepository = $this->get('redis.user.respository');
$handler = new RegisterUserHandler($userRepository);

try {
$handler($command);
} catch (\Exception $e) {
return new Response($e->getMessage(), Response::HTTP_BAD_REQUEST);
}

return JsonResponse::create('', Response::HTTP_CREATED);
}
}

Casos de uso

  • Las librerías de CommandBus mas conocidas como Tactician o Broadway hacen uso de este patrón.

Conclusión

En sí este patrón es muy limpio y 100% testable, su implementación es prácticamente fácil y sencilla y en combinación con el uso de un CommandBus como el de la librería Tactician o Broadway harán de este patrón tu mejor aliado

El patrón Command es una herramienta muy potente, que nos permite crear código muy limpio y testable, su implementación básica es muy sencilla, y el hacer uso de un CommandBus como Tactician es muy sencillo también.

En lo personal lo utilizo muchísimo sobre todo con la arquitectura CQRS. He experimentado como utilizar el componente Messenger de Symfony para tratar de reemplazar las librerías mencionadas y la verdad es que va muy bien.

He logrado implementar el componente para hacer uso de su CommandBus e incluso de sus Transports para encolar los comandos con RabbitMQ. Todo sin acoplarme al framework y mantener los principios de la arquitectura hexagonal intactos.

Hasta aquí dejaré este post sobre este maravilloso patrón de diseño.

Recuerda que si tienes alguna sugerencia o pregunta, no dudes en dejar tus comentarios al final del post.

Si te gustó este post, ayúdame a que pueda servirle a muchas más personas, compartiendo mis contenidos en tus redes sociales.

Espero que este post haya sido de gran ayuda para ti, y como siempre, cualquier inquietud o duda que tengas, puedes contactarme por cualquiera de las vías disponibles, o dejando tus comentarios al final de este post. También puedes sugerir que temas o post te gustaría leer a futuro.

Francisco Ugalde

Blog sobre tecnología, desarrollo de software y mobile

Francisco Ugalde

Written by

Senior Software Engineer | I love coding, software architectures and all new technologies. I’ll try to share, in an easy way, all the bits of my head.

Francisco Ugalde

Blog sobre tecnología, desarrollo de software y mobile

Francisco Ugalde

Written by

Senior Software Engineer | I love coding, software architectures and all new technologies. I’ll try to share, in an easy way, all the bits of my head.

Francisco Ugalde

Blog sobre tecnología, desarrollo de software y mobile

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store