0Pricing
PHP Academy · Lezione

Porte e adapter spiegati

Isolate il core con porte e adapter collegabili.

Porte e adapter spiegati è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 2 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.

L'idea dell'esagono

Ports & Adapters — la Hexagonal Architecture di Alistair Cockburn — rappresenta l'applicazione come un esagono. All'interno si trova la logica di business pura. Ogni interazione con il mondo esterno (HTTP, DB, coda, orologio, e-mail) attraversa una porta, e ogni porta è implementata da uno o più adattatori. La forma non privilegia né la parte superiore né quella inferiore: l'interfaccia utente e il database sono simmetrici, entrambi semplicemente adattatori.

Le porte sono interfacce

Una porta è un'interfaccia posseduta dal nucleo applicativo che esprime un'esigenza o una capacità nei termini del dominio. Non deve esporre il vocabolario dell'infrastruttura: niente PDOStatement, niente GuzzleResponse, niente Eloquent.

<?php
// Driven (outbound) port: the core needs to persist users
interface UserRepository
{
    public function byId(UserId $id): ?User;
    public function save(User $user): void;
}

Porte driving e driven

Esistono due varianti:

  • Porte driving (primarie, in ingresso) — l'API che il mondo esterno chiama per pilotare l'applicazione. Di solito sono le interfacce dei casi d'uso.
  • Porte driven (secondarie, in uscita) — interfacce che l'applicazione chiama per raggiungere il mondo esterno: repository, mailer, orologi.

Gli adattatori driving chiamano il nucleo; il nucleo comunica verso l'esterno tramite gli adattatori driven.

<?php
// Driving (inbound) port — the public capability of the core
interface RegisterUser
{
    public function handle(string $email, string $plainPassword): UserId;
}

Il nucleo implementa le porte driving

Il caso d'uso implementa una porta driving e dipende dalle porte driven. Nota: accetta PasswordHasher e Clock come porte iniettate; anche il tempo e l'hashing sono astratti, così il nucleo rimane deterministico e testabile.

<?php
final class RegisterUserService implements RegisterUser
{
    public function __construct(
        private UserRepository $users,
        private PasswordHasher $hasher,
        private Clock $clock,
    ) {}

    public function handle(string $email, string $plain): UserId {
        if ($this->users->byEmail($email)) {
            throw new EmailAlreadyTaken($email);
        }
        $user = User::register(
            $email,
            $this->hasher->hash($plain),
            $this->clock->now()
        );
        $this->users->save($user);
        return $user->id();
    }
}

Un adattatore driven

Un adattatore driven implementa una porta driven usando una tecnologia concreta. Qui un adattatore PDO soddisfa UserRepository. Lo sostituisca con Doctrine, Redis o un client API HTTP senza modificare il nucleo.

<?php
final class PdoUserRepository implements UserRepository
{
    public function __construct(private PDO $pdo) {}

    public function byId(UserId $id): ?User {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([(string) $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ? User::fromRow($row) : null;
    }
    public function save(User $user): void {
        // INSERT ... ON CONFLICT UPDATE
    }
}

Un adattatore driving

Un adattatore driving traduce un trigger esterno in una chiamata a una porta driving. Un controller HTTP, un comando CLI, un consumer di messaggi: tutti sono adattatori driving intercambiabili per lo stesso caso d'uso.

<?php
// CLI driving adapter
final class RegisterUserCommand
{
    public function __construct(private RegisterUser $register) {}

    public function run(array $argv): int {
        [$email, $password] = array_slice($argv, 1);
        $id = $this->register->handle($email, $password);
        fwrite(STDOUT, "Created user $id\n");
        return 0;
    }
}

Adattatori in memoria per i test

Il vantaggio principale: ogni porta driven ha un fake veloce. I test eseguono il caso d'uso reale con adattatori in memoria, orologi deterministici e un hasher no-op.

<?php
final class FixedClock implements Clock {
    public function __construct(private DateTimeImmutable $t) {}
    public function now(): DateTimeImmutable { return $this->t; }
}
final class PlainHasher implements PasswordHasher {
    public function hash(string $p): string { return 'h:' . $p; }
}

$service = new RegisterUserService(
    new InMemoryUsers(),
    new PlainHasher(),
    new FixedClock(new DateTimeImmutable('2026-01-01'))
);
echo 'wired OK', PHP_EOL;

Gli adattatori traducono, non decidono

Un errore comune consiste nel lasciare che le regole di business penetrino negli adattatori. La regola generale è questa: un adattatore traduce solo formati e protocolli dei dati. Se trovate un if relativo a prezzi, idoneità o stato all'interno di un controller o di un repository, quel codice appartiene al nucleo.

  • Mappatura JSON ↔ DTO: adattatore
  • Idratazione SQL ↔ entità: adattatore
  • "A chi è VIP spetta uno sconto del 10%": nucleo

Una porta, molti adattatori

Le porte consentono la sostituzione e persino adattatori paralleli. Una NotificationPort può avere adattatori per email, SMS e Slack, combinati tra loro. Il nucleo invoca un solo metodo; la configurazione decide quanti canali devono rispondere.

<?php
interface Notifier { public function send(string $to, string $msg): void; }

final class CompositeNotifier implements Notifier {
    /** @param Notifier[] $channels */
    public function __construct(private array $channels) {}
    public function send(string $to, string $msg): void {
        foreach ($this->channels as $c) $c->send($to, $msg);
    }
}

$notifier = new CompositeNotifier([new EmailNotifier(), new SmsNotifier()]);
echo 'composed', PHP_EOL;

Come si mappa l'esagono nelle cartelle

Una struttura PHP pragmatica per un contesto delimitato:

  • Domain/ — entità, oggetti valore, servizi di dominio
  • Application/Port/In/ — interfacce delle porte di ingresso (casi d'uso)
  • Application/Port/Out/ — interfacce delle porte pilotate (repository, orologio)
  • Application/ — implementazioni dei casi d'uso
  • Infrastructure/Adapter/In/ — controller, CLI, consumer
  • Infrastructure/Adapter/Out/ — adattatori PDO/Doctrine/HTTP

La composition root (configurazione del container DI) collega gli adattatori In e Out alle porte.

Testare l'intero esagono

Oltre ai test unitari, le porte consentono rapidi test di accettazione che pilotano l'applicazione attraverso la sua porta principale e verificano il risultato tramite adattatori secondari in memoria, coprendo un caso d'uso completo senza HTTP né database. In seguito, la stessa suite di test può essere eseguita sugli adattatori reali come test di integrazione, offrendo una strategia di test a più livelli senza doverla riscrivere.

<?php
// Acceptance test: real use case, fake driven adapters, no I/O
$users = new InMemoryUsers();
$service = new RegisterUserService($users, new PlainHasher(),
    new FixedClock(new DateTimeImmutable('2026-01-01')));

$id = $service->handle('dev@coddykit.com', 'pw');

assert($users->byId($id) !== null);
echo 'acceptance: user persisted via in-memory adapter', PHP_EOL;

Verifica rapida

Quale affermazione sulle porte e sugli adattatori è corretta?

Riepilogo

Ports & Adapters isola il nucleo dietro le interfacce:

  • Le porte sono interfacce espresse nel linguaggio del dominio e possedute dal nucleo.
  • Le porte di ingresso vengono invocate dagli adattatori in ingresso; le porte pilotate vengono invocate dal nucleo e soddisfatte dagli adattatori in uscita.
  • Gli adattatori traducono solo protocolli e formati: non prendono mai decisioni di business.
  • La stessa porta supporta molti adattatori (fake per i test, compositi per il fan-out).
  • La composition root collega ogni componente; l'esagono rimane indipendente dai framework.

Domande Frequenti

La lezione «Porte e adapter spiegati» è gratuita?

Sì — il testo completo di «Porte e adapter spiegati» è 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 «Porte e adapter spiegati»?

Isolate il core con porte e adapter collegabili. 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 2 di 4.

Quanto tempo richiede la lezione «Porte e adapter spiegati»?

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