Arquitectura Hexagonal en PHP: código limpio sin dolor

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Tu código es una fortaleza, no un espagueti

\n\n

¿Alguna vez has tenido que reescribir un proyecto de Laravel a Symfony o de MySQL a PostgreSQL? Si es así, conoces ese dolor: controladores llenos de consultas SQL, modelos que lo saben todo sobre peticiones HTTP y una lógica de negocio que muere al cambiar de framework. ¿Te suena familiar?

\n\n

Bienvenido al mundo de la Arquitectura Hexagonal en PHP (también conocida como Ports & Adapters). No es una palabra de moda, sino un salvavidas para proyectos que quieren vivir larga y felizmente. En este artículo, veremos cómo construir una aplicación PHP que no le tema a los cambios: migrar de base de datos, cambiar de caché o integrar una nueva API se convertirá en un simple reemplazo de adaptador, no en reescribir la mitad del código.

\n\n

¿Qué es realmente la Arquitectura Hexagonal?

\n\n

Imagina que tu aplicación es un núcleo (core) que no sabe de dónde vienen los datos: si de la web, la consola, un bot de Telegram o los tests. Alrededor de este núcleo tienes puertos (interfaces) y en el exterior, adaptadores (implementaciones). La arquitectura hexagonal en PHP permite separar la lógica de negocio del código de infraestructura. Escribes las reglas una vez y las usas en todas partes.

\n\n

Principios clave:

\n
    \n
  • Inversión de dependencias — el código de alto nivel no depende del de bajo nivel. Todo es al revés.
  • \n
  • Lógica de negocio aislada — ningún framework penetra en tus Casos de Uso.
  • \n
  • Capacidad de prueba — puedes probar el núcleo sin base de datos ni HTTP.
  • \n
\n\n

A diferencia de la Clean Architecture de Robert Martin, la Hexagonal es más flexible: no dicta la cantidad de capas, solo dice: «Dentro está el dominio, fuera la infraestructura, conéctalos mediante interfaces».

\n\n

Estructura del proyecto: del caos al orden

\n\n

Vamos directo al grano. Esta es una estructura típica de carpetas para la Arquitectura Hexagonal en PHP:

\n\n
src/\n├── Domain/\n│   ├── Entity/\n│   │   └── Order.php\n│   ├── Repository/\n│   │   └── OrderRepositoryInterface.php\n│   └── Service/\n│       └── OrderService.php\n├── Application/\n│   ├── UseCase/\n│   │   └── CreateOrderUseCase.php\n│   └── Port/\n│       ├── Input/\n│       │   └── CreateOrderInputPort.php\n│       └── Output/\n│           └── OrderOutputPort.php\n├── Infrastructure/\n│   ├── Persistence/\n│   │   └── DoctrineOrderRepository.php\n│   └── Http/\n│       └── OrderController.php\n└── Framework/\n    └── Symfony/\n        └── Kernel.php
\n\n

¿Te diste cuenta? Domain y Application no saben que existen Doctrine, Symfony o MySQL. Solo trabajan con interfaces. Los adaptadores viven en Infrastructure y se conectan mediante Inyección de Dependencias.

\n\n

Paso 1: Definimos el núcleo — Domain

\n\n

Empecemos con la entidad de pedido. Sin anotaciones de ORM, sin setters que rompan invariantes:

\n\n
<?php\n// src/Domain/Entity/Order.php\nnamespace App\\Domain\\Entity;\n\nclass Order\n{\n    public function __construct(\n        private readonly string $id,\n        private readonly string $productName,\n        private float $totalAmount,\n        private string $status = 'pending'\n    ) {}\n\n    public function approve(): void\n    {\n        if ($this->status !== 'pending') {\n            throw new \\DomainException('Solo los pedidos pendientes pueden ser aprobados.');\n        }\n        $this->status = 'approved';\n    }\n\n    public function getId(): string { return $this->id; }\n    public function getProductName(): string { return $this->productName; }\n    public function getTotalAmount(): float { return $this->totalAmount; }\n    public function getStatus(): string { return $this->status; }\n}
\n\n

Y aquí el puerto (interfaz) para el repositorio. Nota: no sabe nada de Doctrine ni PDO:

\n\n
<?php\n// src/Domain/Repository/OrderRepositoryInterface.php\nnamespace App\\Domain\\Repository;\n\nuse App\\                

Blogs

Book Recommendations