Un proyecto puede comenzar con un sencillo if/else para validar un flujo, y antes de darnos cuenta, nos encontramos con una montaña de variables booleanas, condiciones anidadas y casos imposibles de entender… o de probar.

El patrón State Machine surge precisamente como respuesta a este problema. Su propuesta es simple, pero poderosa:

  • Define de manera explícita los estados por los que puede pasar tu entidad.
  • Especifica los eventos que disparan cambios entre estados.
  • Establece reglas claras sobre qué transiciones están permitidas y cuáles deben ser bloqueadas.

Al hacerlo, conviertes la lógica escondida en condicionales dispersos en un modelo visible, comprensible y seguro.

¿Por qué merece la pena usar una State Machine en PHP?

La respuesta es obvia ¿porque no lo conocía de antes? ;)

La realidad es que los flujos complejos no se mantienen simples durante mucho tiempo, aunque en un inicio lo parezca.

Un sistema de pedidos que empieza con tres estados (“nuevo”, “pagado”, “enviado”) en cuestión de meses incorporarás reembolsos, cancelaciones, devoluciones y excepciones. Lo que parecía trivial se convierte en terreno fértil para bugs.

Con state machine, en cambio, se obtienen beneficios claros:

  • Claridad: todos los posibles movimientos se concentran en un mapa único.
  • Seguridad: movimientos prohibidos (como cancelar un pedido ya enviado) simplemente no existen en el modelo.
  • Testabilidad: como las transiciones están completamente definidas, resulta sencillo realizar tests unitarios.
  • Observabilidad: cada transición puede registrarse y auditarse, dando trazabilidad a lo que ocurre en el sistema.

Conceptos básicos de State Machine en PHP

Para entender cómo funciona sigamos con el ejemplo de una entidad como un pedido de e-commerce:

  • Un Estado describe dónde está el pedido en un momento dado: Nuevo, Pagado, Enviado, Cancelado.
  • Un Evento representa algo que ocurre y que puede cambiar el estado: Pagar, Enviar, Cancelar.
  • Una Transición conecta un estado con otro a través de un evento permitido.
  • Un Guard añade condiciones: por ejemplo, “solo se puede pagar si el importe autorizado es igual o superior al total del pedido”.
  • Una Acción define qué ocurre cuando la transición tiene éxito, como enviar un correo de confirmación o notificar a un servicio externo.

La idea es que la decisión de “si se puede o no cambiar de estado” sea siempre pura, predecible y fácil de repetir.

Los efectos secundarios, en cambio, deben quedar en la periferia: se disparan después, de manera controlada y, a ser posible, idempotente (que no pase nada malo si se ejecutan dos veces).

Un ejemplo de State Machine en PHP

Imaginemos el escenario habitual en un sistema sin State Machine. El código podría parecerse a esto:

Con esta lógica, basta con que alguien olvide actualizar una variable condicional para encontrarnos con un pedido que está enviado y reembolsado al mismo tiempo.

Ahora bien, al aplicar el patrón, lo primero es definir los estados y eventos de forma explícita:

Y después, dibujar las transiciones posibles:

  • Nuevo → Pagar → Pagado
  • Nuevo → Cancel → Cancelado
  • Pagar → Enviar → Enviado
  • Pagar → Devolver → Devuelto
  • Enviado → no permite devolver 'refund'

De esta manera, el modelo ya nos garantiza que un pedido no podrá estar en un estado contradictorio. Ejemplo de uso:

Salida esperada:

Estado actual: paid
Evento generado: ReceiptToSend

Estado actual: shipped
Evento generado: ShipmentToNotify

Con este ejemplo ya tienes un flujo realista y extendido, los guards impiden transiciones inválidas (ej: no pagar sin dinero, no reembolsar sin permisos), los domain events encapsulan efectos secundarios sin romper la pureza del modelo y se puede testear cada transición de manera aislada.

Más allá de la transición: Guards y Domain Events

En sistemas reales, los flujos no se limitan a cambiar de estado. Necesitamos validar condiciones adicionales (los Guards) y disparar efectos (las Acciones).

Siguiendo con el ejemplo:

  • Un pedido solo puede pasar de Nuevo a Pagado si la pasarela de pago ha autorizado el importe completo.
  • Cuando ocurre esa transición, en lugar de enviar directamente un email, el sistema puede generar un evento de dominio (ReceiptToSend) y guardarlo en un outbox.
  • Otro proceso se encargará de procesar esos eventos y ejecutar las tareas externas, con reintentos si es necesario.

Este enfoque mantiene la pureza del modelo y hace que los efectos secundarios ens los que ahora no somos capaces de predecir sean más confiables y fáciles de manejar.

Testear y observar el State Machine

Las pruebas unitarias se vuelven mucho más sencillas. En vez de verificar múltiples condiciones, basta con comprobar que:

  • Un pedido Nuevo con pago válido → pasa a Pagado y emite un evento ReceiptToSend.
  • Un pedido Nuevo con pago insuficiente → lanza una excepción.
  • Un pedido Nuevo no puede ser Enviado directamente → transición inválida.

Y en producción, envolver la transición con un logger nos permite registrar cada cambio, medir latencias e identificar fallos antes de que escalen.

Documentación viva con diagramas

Otro beneficio es la documentación. Como las reglas de transición están centralizadas, es posible generar automáticamente diagramas con Mermaid o PlantUML que representan el flujo de estados.

Esto evita el clásico problema de los diagramas desactualizados y que el código sea la fuente de verdad, y la documentación se deriva de él.

Conclusión

El patrón State Machine convierte la lógica condicional caótica en un modelo explícito, predecible y fácil de mantener.

Con herramientas modernas de PHP como enums y objetos inmutables, su implementación es clara y robusta.

Lo importante no es empezar programando directamente, sino diseñar primero los estados y eventos en lenguaje natural. Una vez tienes el mapa, la implementación se convierte en un ejercicio mecánico.

El resultado: flujos más seguros, código más testeable, mejores logs y documentación automática.

Y, lo más valioso de todo, un sistema que evoluciona con orden, en lugar de hundirse en la maraña de condicionales.

Referencias:
· Creating a State Machine using PHP
· Welcome to the State Machine Pattern

Compartir es construir