Cómo poner orden en un proyecto PHP: Arquitectura Hexagonal 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

Cuando su monolito PHP empieza a resquebrajarse

\n

¿Alguna vez se ha encontrado en una situación donde cambiar la base de datos de MySQL a PostgreSQL se convertía en una misión de una semana? ¿O cuando necesitaba añadir un nuevo cliente API, y el código estaba tan entrelazado con el framework que era más fácil escribirlo todo desde cero? ¿Le suena familiar?

\n

La mayoría de los desarrolladores PHP, tarde o temprano, se topan con "espaguetis" en los controladores y modelos. Frameworks como Laravel o Symfony ofrecen herramientas convenientes, pero también imponen un fuerte acoplamiento con ellos mismos. Hay una solución: la Arquitectura Hexagonal (también conocida como "puertos y adaptadores" o arquitectura hexagonal).

\n

En este artículo, analizaremos cómo construir una aplicación en PHP que no dependa del framework, la base de datos o servicios externos. Verá ejemplos de código que puede aplicar inmediatamente en su proyecto. ¡Allá vamos!

\n\n

¿Qué es la Arquitectura Hexagonal y para qué sirve?

\n

Imagine un hexágono. En el centro está su lógica de negocio (dominio). Alrededor, los "puertos" (interfaces) a través de los cuales el mundo exterior se comunica con el núcleo. Y en el exterior, los "adaptadores" que implementan estos puertos: controladores, repositorios, clientes API.

\n

La idea principal: el núcleo de la aplicación no sabe nada del mundo exterior. No importa Laravel, no conoce PDO ni llama a curl. Toda la dependencia apunta hacia adentro: los adaptadores dependen de los puertos, y no al revés.

\n

Las ventajas de este enfoque son evidentes:

\n
    \n
  • Capacidad de prueba: la lógica de negocio se puede probar sin base de datos ni solicitudes HTTP.
  • \n
  • Flexibilidad: cambiar la BD, la cola o el framework es simplemente escribir un nuevo adaptador.
  • \n
  • Limpieza del código: no mezcla "qué hacer" con "cómo hacerlo".
  • \n
\n

¿Suena a magia? Veamos el código.

\n\n

Estructura del proyecto: esqueleto básico

\n

Para el ejemplo, tomemos un escenario simple: registro de usuario. Necesitamos guardar el usuario en la BD y enviar un correo de bienvenida.

\n

Así es como se vería una estructura de directorios típica:

\n
src/\n├── Domain/\n│   ├── Entity/\n│   │   └── User.php\n│   ├── Repository/\n│   │   └── UserRepositoryInterface.php\n│   ├── Service/\n│   │   └── UserRegistrationService.php\n│   └── Event/\n│       └── UserRegisteredEvent.php\n├── Infrastructure/\n│   ├── Persistence/\n│   │   └── DoctrineUserRepository.php\n│   ├── Mail/\n│   │   └── SymfonyMailerAdapter.php\n│   └── Controller/\n│       └── RegisterUserController.php\n└── Application/\n    └── UseCase/\n        └── RegisterUserUseCase.php
\n

Observe: Domain es la lógica de negocio pura. Infrastructure son los adaptadores. Application son los casos de uso (use cases).

\n\n

Paso 1: El núcleo — dominio y puertos

\n

Entidad (Entity)

\n

Empecemos por lo más simple: el usuario. Es solo una clase con datos, sin dependencias.

\n
<?php\nnamespace App\\Domain\\Entity;\n\nclass User\n{\n    public function __construct(\n        private readonly string $id,\n        private string $name,\n        private string $email\n    ) {}\n\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function getEmail(): string\n    {\n        return $this->email;\n    }\n}
\n\n

Puerto — interfaz del repositorio

\n

Ahora definamos cómo guardaremos al usuario. Esto es un puerto — una interfaz que será implementada por un adaptador.

\n
<?php\nnamespace App\\Domain\\Repository;\n\nuse App\\Domain\\Entity\\User;\n\ninterface UserRepositoryInterface\n{\n    public function save(User $user): void;\n    public function findByEmail(string $email): ?User;\n}
\n\n

Puerto — interfaz del servicio de correo

\n

De manera similar para el envío de correos:

\n
<?php\nnamespace App\\Domain\\Service;\n\nuse App\\                

Blogs

Book Recommendations