0Pricing
PHP Academy · Lección

De la arquitectura por capas a la arquitectura limpia

Comprenda por qué las dependencias deben apuntar hacia el interior.

De la arquitectura por capas a la arquitectura limpia es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de PHP Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de PHP Academy incluye 4 lecciones en total.

Por qué la Arquitectura Limpia

Ya conoce la clásica pila PHP de tres capas: Controller → Service → Repository → Database. Funciona, pero la lógica de negocio termina acoplada a Eloquent, Doctrine, la solicitud HTTP y el ciclo de vida del framework. La Arquitectura Limpia invierte la dirección de las dependencias para que su dominio no sepa nada de la infraestructura. La recompensa: casos de uso fáciles de probar, adaptadores intercambiables y una base de código que resiste las actualizaciones del framework.

La regla de dependencias

La única regla de la Arquitectura Limpia: las dependencias del código fuente apuntan únicamente hacia dentro. Los círculos internos (entidades, casos de uso) nunca deben hacer referencia a los círculos externos (controladores, ORM y frameworks). En tiempo de ejecución, el flujo de control se dirige hacia fuera mediante interfaces, pero en el momento de compilar o importar, nada interno importa elementos externos.

  • Entidades: reglas empresariales
  • Casos de uso: reglas de aplicación
  • Adaptadores: controladores, presentadores, gateways
  • Frameworks y drivers: DB, HTTP, la web

Ejemplo de capas acopladas

Este es el tipo de servicio que incluyen la mayoría de las aplicaciones PHP. Observe que la lógica de dominio está entrelazada con Eloquent y la respuesta HTTP. No puede probar unitariamente la regla de descuentos sin una base de datos y un framework.

<?php
class OrderService
{
    public function place(Request $request)
    {
        $user = User::find($request->user_id); // Eloquent
        $total = 0;
        foreach ($request->items as $i) {
            $total += Product::find($i['id'])->price * $i['qty'];
        }
        if ($user->is_vip) {
            $total *= 0.9; // business rule trapped in infra code
        }
        Order::create(['user_id' => $user->id, 'total' => $total]);
        return response()->json(['total' => $total]);
    }
}

Entidades: dominio independiente del framework

Una entidad codifica reglas empresariales generales y no depende de nada. Es PHP puro, sin anotaciones ni una clase base del ORM. Se puede construir completamente en una prueba.

<?php
final class Money
{
    public function __construct(public readonly int $cents) {
        if ($cents < 0) throw new InvalidArgumentException('negative money');
    }
    public function multiply(float $factor): self {
        return new self((int) round($this->cents * $factor));
    }
}

final class Order
{
    /** @param array<int,int> $lineCents */
    public function __construct(private array $lineCents, private bool $vip) {}
    public function total(): Money {
        $sum = array_sum($this->lineCents);
        $money = new Money($sum);
        return $this->vip ? $money->multiply(0.9) : $money;
    }
}

echo (new Order([1000, 2000], true))->total()->cents, PHP_EOL; // 2700

Los casos de uso controlan el flujo de trabajo

Un caso de uso (interactor) orquesta entidades y se comunica con el exterior únicamente mediante interfaces (puertos). Recibe un DTO de solicitud y devuelve un DTO de respuesta, nunca un objeto HTTP.

<?php
interface OrderRepository {
    public function save(Order $order): void;
}

final class PlaceOrder
{
    public function __construct(private OrderRepository $orders) {}

    public function execute(array $lineCents, bool $vip): int {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return $order->total()->cents;
    }
}

El límite es una interfaz

El caso de uso declara la interfaz OrderRepository que necesita. La interfaz vive en el círculo interno; la implementación concreta basada en Eloquent o Doctrine vive fuera y depende de la parte interna. Este es el Principio de inversión de dependencias aplicado a un límite arquitectónico.

Dirección de la dependencia del código fuente: EloquentOrderRepository → OrderRepository (interfaz), nunca a la inversa.

<?php
// Lives in infrastructure layer, points INWARD to the domain interface
final class EloquentOrderRepository implements OrderRepository
{
    public function save(Order $order): void {
        OrderModel::create(['total' => $order->total()->cents]);
    }
}

Pruebas sin infraestructura

Como el caso de uso depende de una interfaz, las pruebas inyectan un doble de prueba. Sin base de datos ni arranque del framework: pruebas unitarias que tardan microsegundos y verifican el comportamiento puro del negocio.

<?php
final class InMemoryOrders implements OrderRepository {
    public array $saved = [];
    public function save(Order $o): void { $this->saved[] = $o; }
}

$repo = new InMemoryOrders();
$useCase = new PlaceOrder($repo);
$total = $useCase->execute([1000, 2000], true);

assert($total === 2700);
assert(count($repo->saved) === 1);
echo "PASS total=$total saved=" . count($repo->saved) . PHP_EOL;

Los controladores se convierten en adaptadores delgados

El controlador ahora es un adaptador: traduce HTTP en una llamada al caso de uso y el resultado en HTTP. No contiene reglas de negocio. Cambie REST por CLI o un trabajador de colas y el caso de uso no cambia.

<?php
final class OrderController
{
    public function __construct(private PlaceOrder $placeOrder) {}

    public function store(Request $request): JsonResponse {
        $total = $this->placeOrder->execute(
            lineCents: $request->input('lineCents'),
            vip: (bool) $request->input('vip'),
        );
        return new JsonResponse(['total' => $total], 201);
    }
}

Arquitectura que grita

La estructura de carpetas debe gritar el dominio, no el framework. Evite Controllers/ y Models/ en el nivel superior. Organice por capacidades para que una persona recién incorporada vea qué hace la aplicación.

  • src/Ordering/Domain/ — entidades y objetos de valor
  • src/Ordering/Application/ — casos de uso e interfaces de puertos
  • src/Ordering/Infrastructure/ — repositorios Eloquent y controladores HTTP

Cada contexto delimitado es una carpeta de nivel superior; el framework vive en los extremos.

Hacer cumplir la regla de dependencias

Sin herramientas, la disciplina se erosiona. Utilice deptrac o phparkitect en CI para que la compilación falle cuando Dominio importe Infraestructura. La regla se convierte en una garantía en tiempo de compilación, en lugar de depender de la esperanza de una revisión de código.

# deptrac.yaml
deptrac:
  layers:
    - name: Domain
      collectors: [{ type: directory, value: src/.*/Domain/.* }]
    - name: Application
      collectors: [{ type: directory, value: src/.*/Application/.* }]
    - name: Infrastructure
      collectors: [{ type: directory, value: src/.*/Infrastructure/.* }]
  ruleset:
    Domain: []                       # Domain may depend on nothing
    Application: [Domain]
    Infrastructure: [Application, Domain]

Cruzar límites con DTO

Para evitar que las entidades se filtren hacia fuera, los datos que cruzan un límite viajan como un DTO sencillo, no como una entidad ni como un modelo de ORM. El caso de uso devuelve una estructura plana que el adaptador puede serializar, de modo que el objeto de dominio nunca sale del núcleo y la capa externa nunca obtiene acceso al estado interno.

<?php
final class OrderSummary // boundary DTO, no behavior, no domain types
{
    public function __construct(
        public readonly string $orderId,
        public readonly int $totalCents,
    ) {}
}

final class PlaceOrderV2 {
    public function __construct(private OrderRepository $orders) {}
    public function execute(array $lineCents, bool $vip): OrderSummary {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return new OrderSummary('ord_1', $order->total()->cents);
    }
}

Comprobación rápida

¿Qué dirección de dependencia está permitida según la regla de dependencias?

Repaso

Ha pasado de una pila de capas acoplada a la Arquitectura Limpia:

  • La regla de dependencias: las dependencias del código fuente apuntan únicamente hacia dentro.
  • Las entidades contienen reglas empresariales en PHP independiente del framework.
  • Los casos de uso orquestan mediante interfaces de puertos y devuelven DTO, no HTTP.
  • Los controladores y los repositorios del ORM son adaptadores externos que dependen hacia dentro (DIP).
  • La estructura debe gritar el dominio, y herramientas como deptrac hacen cumplir la regla en CI.

Preguntas frecuentes

¿La lección «De la arquitectura por capas a la arquitectura limpia» es gratis?

Sí — el texto completo de «De la arquitectura por capas a la arquitectura limpia» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de PHP Academy, actualiza a CoddyKit PRO. El curso de PHP Academy incluye 4 lecciones en total.

¿Qué aprenderé en «De la arquitectura por capas a la arquitectura limpia»?

Comprenda por qué las dependencias deben apuntar hacia el interior. Practicas PHP Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar PHP Academy?

No se requiere experiencia previa. PHP Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.

¿Cuánto tiempo toma la lección «De la arquitectura por capas a la arquitectura limpia»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de PHP Academy?

Sí. Cada lección de PHP Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. De la arquitectura por capas a la arquitectura limpia
  2. Puertos y adaptadores explicados
  3. Casos de uso y servicios de aplicación
  4. Inversión de dependencias en la práctica
← Volver a PHP Academy