Inversione delle dipendenze nella pratica
Collegate gli adapter al core con l'inversione delle dipendenze.
Inversione delle dipendenze nella pratica è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 4 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.
Inversione e iniezione
Spesso si confondono due idee distinte. La Dependency Injection è una tecnica: si passano i collaboratori dall'esterno invece di crearli. La Dependency Inversion (la D di SOLID) è un principio: sia le politiche di alto livello sia i dettagli di basso livello dipendono da un'astrazione, che è posseduta dal modulo di alto livello. Questa lezione mostra come rendere concreto il secondo principio, collegando adattatori concreti a un nucleo che definisce le interfacce.
Il principio, con precisione
Il DIP stabilisce quanto segue:
- I moduli di alto livello non dovrebbero dipendere dai moduli di basso livello. Entrambi devono dipendere da astrazioni.
- Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli devono dipendere dalle astrazioni.
Il punto più sottile è questo: l'interfaccia appartiene al consumatore, non all'implementatore. Il dominio dichiara PaymentGateway; l'adattatore Stripe si conforma a tale interfaccia, non il contrario.
Definire l'astrazione nel nucleo
Collocate l'interfaccia accanto al codice che ne ha bisogno ed esprimetela nei termini del dominio. Nessun tipo di Stripe deve trapelare.
<?php
// src/Billing/Application/Port/PaymentGateway.php
interface PaymentGateway
{
public function charge(Money $amount, CardToken $token): ChargeId;
}
final class Money {
public function __construct(public readonly int $cents, public readonly string $currency) {}
}
final class CardToken { public function __construct(public readonly string $value) {} }
final class ChargeId { public function __construct(public readonly string $value) {} }Conformare un adattatore all'astrazione
L'adattatore infrastrutturale implementa l'interfaccia del nucleo e traduce le chiamate nell'SDK del fornitore. La freccia della dipendenza va dall'adattatore (dettaglio) all'astrazione (politica): l'inversione è stata ottenuta.
<?php
final class StripePaymentGateway implements PaymentGateway
{
public function __construct(private \Stripe\StripeClient $stripe) {}
public function charge(Money $amount, CardToken $token): ChargeId {
$intent = $this->stripe->paymentIntents->create([
'amount' => $amount->cents,
'currency' => strtolower($amount->currency),
'payment_method' => $token->value,
'confirm' => true,
]);
return new ChargeId($intent->id);
}
}L'iniezione nel costruttore è la scelta predefinita
Iniettate le dipendenze tramite il costruttore e dichiarate il tipo dell'astrazione. Le dipendenze diventano esplicite, immutabili e impossibili da dimenticare. Evitate l'iniezione tramite setter o proprietà per i collaboratori obbligatori: consente di creare oggetti costruiti solo parzialmente.
<?php
final class CheckoutService
{
public function __construct(
private PaymentGateway $payments, // abstraction, not StripeClient
private OrderRepository $orders,
) {}
public function pay(OrderId $id, CardToken $token): ChargeId {
$order = $this->orders->get($id);
$charge = $this->payments->charge($order->total(), $token);
$order->markPaid($charge);
$this->orders->save($order);
return $charge;
}
}La composition root
Ogni configurazione concreta avviene in un unico punto, la composition root, il più vicino possibile a main()/al punto di ingresso. Nessun altro componente istanzia l'infrastruttura con new. Questo è l'unico punto in cui si sa che Stripe esiste.
<?php
// public/index.php — composition root
$stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET'));
$gateway = new StripePaymentGateway($stripe);
$orders = new PdoOrderRepository(new PDO(getenv('DB_DSN')));
$checkout = new CheckoutService($gateway, $orders);
// Everything below depends only on abstractions
$controller = new CheckoutController($checkout);Configurare con un container DI
Per applicazioni non banali, un container (PHP-DI, Symfony) automatizza la configurazione. Il passaggio fondamentale consiste nel collegare le interfacce alle implementazioni. L'autowiring risolve i costruttori in base ai tipi: dovete dichiarare solo la mappa interfaccia→classe.
<?php
use function DI\autowire;
use function DI\get;
return [
PaymentGateway::class => autowire(StripePaymentGateway::class),
OrderRepository::class => autowire(PdoOrderRepository::class),
\Stripe\StripeClient::class => fn() => new \Stripe\StripeClient(getenv('STRIPE_SECRET')),
PDO::class => fn() => new PDO(getenv('DB_DSN')),
];Sostituire gli adattatori dimostra il principio
Poiché il nucleo dipende solo da PaymentGateway, cambiare fornitore o testare offline richiede una modifica a una sola riga nella configurazione. Ecco un fake usato in un test unitario: CheckoutService non cambia e non importa mai Stripe.
<?php
final class FakeGateway implements PaymentGateway {
public array $charges = [];
public function charge(Money $a, CardToken $t): ChargeId {
$this->charges[] = $a;
return new ChargeId('ch_test_' . count($this->charges));
}
}
$fake = new FakeGateway();
$id = $fake->charge(new Money(2500, 'EUR'), new CardToken('tok_visa'));
echo $id->value, ' charges=', count($fake->charges), PHP_EOL; // ch_test_1 charges=1Evitare la trappola del service locator
Iniettare il container stesso e recuperare le dipendenze all'interno dei metodi è l'anti-pattern del service locator. Nasconde le dipendenze, vanifica il controllo dei tipi e ricrea l'accoppiamento con il container. Iniettate esplicitamente ciò di cui avete bisogno.
<?php
// ANTI-PATTERN: hidden dependencies, container leaks everywhere
final class BadCheckout {
public function __construct(private ContainerInterface $c) {}
public function pay($id, $token) {
$gateway = $this->c->get(PaymentGateway::class); // hidden!
// ...
}
}Dove può comparire il container
Il container è legittimo in un solo livello: la composition root e il codice di integrazione del framework che la circonda (ad esempio, una factory di controller). Le classi di dominio e applicative devono rimanere indipendenti dal container: ricevono semplici oggetti tramite i costruttori e potrebbero essere istanziate manualmente. Un buon test è chiedersi: potreste configurare l'intera applicazione in un unico file PHP senza container? Se sì, le vostre dipendenze sono dichiarate correttamente.
Configurazione lazy senza service locator
A volte una dipendenza è costosa da costruire o serve solo in determinate condizioni. Resistete alla tentazione di iniettare il container: iniettate invece una closure factory. La dipendenza rimane esplicita e tipizzata, mentre la sua costruzione viene rimandata fino al momento dell'effettivo utilizzo.
<?php
final class ReportService
{
/** @param Closure():PaymentGateway $gatewayFactory */
public function __construct(private Closure $gatewayFactory) {}
public function refundIfNeeded(bool $needed): void {
if (!$needed) return;
$gateway = ($this->gatewayFactory)(); // built only when required
// $gateway->charge(...) etc.
}
}
// Composition root supplies the factory, not the container
$svc = new ReportService(fn() => new StripePaymentGateway($stripe ?? null));
echo 'lazy dependency wired', PHP_EOL;Verifica rapida
Chi dovrebbe possedere l'interfaccia PaymentGateway?
Riepilogo
Avete collegato correttamente gli adattatori al nucleo:
- Inversione ≠ iniezione: l'iniezione è il meccanismo, mentre l'inversione consiste nel far possedere l'astrazione al consumatore.
- Il nucleo definisce l'interfaccia; gli adattatori infrastrutturali vi si conformano.
- Usate l'iniezione nel costruttore delle astrazioni; eseguite ogni configurazione concreta in un'unica composition root (o in una configurazione del container che associ interfaccia→classe).
- Sostituire gli adattatori e usare fake nei test diventa una modifica a una sola riga.
- Evitate l'anti-pattern del service locator: mantenete il container solo al margine.
Domande Frequenti
La lezione «Inversione delle dipendenze nella pratica» è gratuita?
Sì — il testo completo di «Inversione delle dipendenze nella pratica» è 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 «Inversione delle dipendenze nella pratica»?
Collegate gli adapter al core con l'inversione delle dipendenze. 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 4 di 4.
Quanto tempo richiede la lezione «Inversione delle dipendenze nella pratica»?
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
- Dall'architettura a livelli alla Clean Architecture
- Porte e adapter spiegati
- Casi d'uso e servizi applicativi
- Inversione delle dipendenze nella pratica