PHP Academy · leksjon

Dependency inversion i praksis

Koble adaptere til kjernen med dependency inversion.

Leksjon 4 av 413 trinn

Dependency inversion i praksis er en gratis leksjon i PHP Academy på CoddyKit. Dette er leksjon 4 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i PHP Academy, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i PHP Academy inneholder totalt 4 leksjoner.

Inversjon kontra injeksjon

Mange blander sammen to forskjellige ideer. Avhengighetsinjeksjon er en teknikk: send samarbeidspartnere inn i stedet for å opprette dem. Avhengighetsinversjon (D-en i SOLID) er et prinsipp: høynivåpolicy og lavnivådetaljer avhenger begge av en abstraksjon, og abstraksjonen eies av høynivåmodulen. Denne leksjonen handler om å gjøre det andre prinsippet reelt — å koble konkrete adaptere til en kjerne som definerer grensesnittene.

Prinsippet, presist

DIP sier:

  • Høynivåmoduler skal ikke avhenge av lavnivåmoduler. Begge skal avhenge av abstraksjoner.
  • Abstraksjoner skal ikke avhenge av detaljer. Detaljer skal avhenge av abstraksjoner.

Det subtile poenget er at grensesnittet tilhører forbrukeren, ikke implementøren. Domenet Deres erklærer PaymentGateway; Stripe-adapteren følger dette grensesnittet — ikke omvendt.

Definer abstraksjonen i kjernen

Plasser grensesnittet ved siden av koden som trenger det, uttrykt i domenets begreper. Ingen Stripe-typer lekker inn.

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

La en adapter følge det

Infrastrukturadapteren implementerer kjernens grensesnitt og oversetter til leverandørens SDK. Avhengighetspilen peker fra adapteren (detaljen) til abstraksjonen (policyen) — inversjon oppnådd.

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

Konstruktørinjeksjon er standarden

Injiser gjennom konstruktøren og angi abstraksjonen som type. Avhengighetene blir eksplisitte og uforanderlige, og kan ikke glemmes. Unngå setter- og egenskapsinjeksjon for nødvendige samarbeidspartnere — det gjør det mulig å opprette halvferdige objekter.

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

Komposisjonsroten

All konkret kobling skjer på nøyaktig ett sted — komposisjonsroten — så nær main()/inngangspunktet som mulig. Ingen andre steder oppretter infrastruktur med new. Dette er det eneste stedet som vet at Stripe finnes.

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

Kobling med en DI-container

For mer omfattende apper automatiserer en container (PHP-DI, Symfony) koblingen. Det viktige grepet er å binde grensesnitt til implementasjoner. Autokobling finner konstruktører ut fra typer; det er bare grensesnitt→klasse-kartet som må deklareres.

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

Å bytte adaptere beviser poenget

Fordi kjernen bare avhenger av PaymentGateway, kan leverandør byttes eller testing utføres uten nett gjennom én linjes endring i koblingen. Her er en fake brukt i en enhetstest — CheckoutService er uendret og importerer aldri 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=1

Unngå service locator-fellen

Å injisere selve containeren og hente avhengigheter inne i metodene er service locator-anti-mønsteret. Det skjuler avhengigheter, omgår typesjekking og kobler koden til containeren igjen. Injiser det som trengs, eksplisitt.

<?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!
        // ...
    }
}

Hvor containeren kan brukes

Containeren hører hjemme i nøyaktig ett lag: komposisjonsroten og rammeverkskoblingen rundt den (for eksempel en kontrollerfabrikk). Domenet og applikasjonsklassene må forbli uavhengige av containeren — de mottar vanlige objekter via konstruktører og kan opprettes manuelt. En god test er følgende: Kan hele applikasjonen kobles sammen i én PHP-fil uten en container? Hvis svaret er ja, er avhengighetene ærlige.

Lat kobling uten service locator

Noen ganger er en avhengighet kostbar å opprette eller trengs bare under bestemte betingelser. Unngå å injisere containeren — injiser heller en fabrikk-closure. Avhengigheten forblir eksplisitt og typet, mens opprettingen utsettes til den faktisk brukes.

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

Rask kontroll

Hvem bør eie grensesnittet PaymentGateway?

Oppsummering

Adapterne er koblet til kjernen på riktig måte:

  • Inversjon ≠ injeksjon: injeksjon er mekanismen, mens inversjon innebærer at forbrukeren eier abstraksjonen.
  • Kjernen definerer grensesnittet; infrastrukturadapterne følger det.
  • Bruk konstruktørinjeksjon av abstraksjoner; utfør all konkret kobling i én komposisjonsrot (eller i en containerkonfigurasjon som binder grensesnitt→klasse).
  • Det blir en endring i én linje å bytte adaptere og bruke fakes i tester.
  • Unngå service locator-anti-mønsteret — hold containeren ved kanten.
Gratis å komme i gang

Lær deg PHP med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
49
Leksjoner
195

Ofte stilte spørsmål

Er leksjonen «Dependency inversion i praksis» gratis?

Ja – hele teksten i «Dependency inversion i praksis» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av PHP Academy-kurset, kan du oppgradere til CoddyKit PRO. Kurset i PHP Academy inneholder totalt 4 leksjoner.

Hva lærer jeg i «Dependency inversion i praksis»?

Koble adaptere til kjernen med dependency inversion. Du øver på PHP Academy med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med PHP Academy?

Ingen tidligere erfaring er nødvendig. PHP Academy på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.

Hvor lang tid tar leksjonen «Dependency inversion i praksis»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne PHP Academy-leksjonen?

Ja. Alle PHP Academy-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Fra lagdelt arkitektur til clean architecture
  2. Porter og adaptere forklart
  3. Use cases og applikasjonstjenester
  4. Dependency inversion i praksis
← Tilbake til PHP Academy