0Pricing
PHP Academy · Lezione

Dall'architettura a livelli alla Clean Architecture

Comprendete perché le dipendenze dovrebbero puntare verso l'interno.

Dall'architettura a livelli alla Clean Architecture è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.

Perché Clean Architecture

Conosce già il classico stack PHP a tre livelli: Controller → Service → Repository → Database. Funziona, ma la logica di business finisce accoppiata a Eloquent, Doctrine, alla richiesta HTTP e al ciclo di vita del framework. Clean Architecture inverte la direzione delle dipendenze, così il dominio non sa nulla dell'infrastruttura. Il vantaggio: casi d'uso testabili, adattatori sostituibili e una codebase che resiste agli aggiornamenti del framework.

La Dependency Rule

La regola fondamentale di Clean Architecture: le dipendenze del codice sorgente puntano solo verso l'interno. I cerchi interni (entità, casi d'uso) non devono mai fare riferimento ai cerchi esterni (controller, ORM, framework). A runtime il flusso di controllo procede verso l'esterno tramite interfacce, ma in fase di compilazione/importazione nulla di interno importa qualcosa di esterno.

  • Entità: regole aziendali
  • Casi d'uso: regole applicative
  • Adattatori: controller, presenter, gateway
  • Framework e driver: DB, HTTP, web

Un esempio di architettura a livelli accoppiata

Ecco il tipo di servizio distribuito dalla maggior parte delle app PHP. Noti come la logica di dominio sia intrecciata con Eloquent e la risposta HTTP. Non è possibile testare con unit test la regola dello sconto senza un database e 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]);
    }
}

Entità: dominio indipendente dal framework

Un'entità codifica regole valide per l'intera azienda e non dipende da nulla. PHP puro, nessuna annotazione, nessuna classe base dell'ORM. È possibile costruirla interamente in un test.

<?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

I casi d'uso gestiscono il flusso di lavoro

Un caso d'uso (interactor) orchestra le entità e comunica con il mondo esterno solo tramite interfacce (porte). Riceve un DTO di richiesta e restituisce un DTO di risposta, mai un oggetto 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;
    }
}

Il confine è un'interfaccia

Il caso d'uso dichiara l'interfaccia OrderRepository di cui ha bisogno. L'interfaccia risiede nel cerchio interno; l'implementazione concreta basata su Eloquent/Doctrine risiede all'esterno e dipende verso l'interno. Questo è il Dependency Inversion Principle applicato a un confine architetturale.

Direzione della dipendenza del codice sorgente: EloquentOrderRepository → OrderRepository (interfaccia), mai il contrario.

<?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]);
    }
}

Testare senza infrastruttura

Poiché il caso d'uso dipende da un'interfaccia, i test vi iniettano un fake. Nessun database, nessun avvio del framework: unit test veloci in microsecondi che verificano il comportamento puro di business.

<?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;

I controller diventano adattatori sottili

Il controller è ora un adattatore: traduce HTTP in una chiamata al caso d'uso e il risultato in HTTP. Non contiene regole di business. Sostituisca REST con CLI o con un worker di coda e il caso d'uso rimane invariato.

<?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);
    }
}

Un'architettura che esprime il dominio

Anche la struttura delle cartelle dovrebbe esprimere chiaramente il dominio, non il framework. Eviti Controllers/, Models/ al livello superiore. Organizzi per capacità, così chi entra nel progetto capisce cosa fa l'app.

  • src/Ordering/Domain/ — entità, oggetti valore
  • src/Ordering/Application/ — casi d'uso, interfacce delle porte
  • src/Ordering/Infrastructure/ — repository Eloquent, controller HTTP

Ogni Bounded Context è una cartella al livello superiore; il framework vive ai margini.

Applicare la Dependency Rule

La disciplina si indebolisce senza strumenti. Usi deptrac o phparkitect nella CI per far fallire la build quando Domain importa Infrastructure. La regola diventa una garanzia in fase di compilazione anziché una speranza affidata alla revisione del codice.

# 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]

Attraversare i confini con i DTO

Per evitare che le entità trapelino verso l'esterno, i dati che attraversano un confine viaggiano come un semplice DTO, non come un'entità o un modello ORM. Il caso d'uso restituisce una struttura piatta che l'adattatore può serializzare, così l'oggetto di dominio non esce mai dal nucleo e il livello esterno non ottiene mai un riferimento allo stato 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);
    }
}

Verifica rapida

Quale direzione delle dipendenze è consentita dalla Dependency Rule?

Riepilogo

È passato da uno stack a livelli accoppiato a Clean Architecture:

  • La Dependency Rule: le dipendenze del codice sorgente puntano solo verso l'interno.
  • Le entità contengono regole a livello aziendale in PHP indipendente dal framework.
  • I casi d'uso orchestrano tramite interfacce delle porte e restituiscono DTO, non HTTP.
  • I controller e i repository ORM sono adattatori esterni che dipendono verso l'interno (DIP).
  • La struttura dovrebbe esprimere chiaramente il dominio e strumenti come deptrac applicano la regola nella CI.

Domande Frequenti

La lezione «Dall'architettura a livelli alla Clean Architecture» è gratuita?

Sì — il testo completo di «Dall'architettura a livelli alla Clean Architecture» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.

Cosa imparerò in «Dall'architettura a livelli alla Clean Architecture»?

Comprendete perché le dipendenze dovrebbero puntare verso l'interno. Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare PHP Academy?

Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Dall'architettura a livelli alla Clean Architecture»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione PHP Academy?

Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Dall'architettura a livelli alla Clean Architecture
  2. Porte e adapter spiegati
  3. Casi d'uso e servizi applicativi
  4. Inversione delle dipendenze nella pratica
← Torna a PHP Academy