Dependency inversion i praksis
Forbind adapters med kernen ved hjælp af dependency inversion.
Dependency inversion i praksis er en gratis PHP Academy-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i PHP Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. PHP Academy-kurset indeholder 4 lektioner i alt.
Inversion kontra injektion
Folk blander to forskellige idéer sammen. Afhængighedsinjektion er en teknik: send samarbejdskomponenter ind i stedet for at oprette dem. Afhængighedsinversion (D'et i SOLID) er et princip: politik på højt niveau og detaljer på lavt niveau afhænger begge af en abstraktion, og abstraktionen ejes af modulet på højt niveau. Denne lektion handler om at gøre det andet princip konkret — at forbinde konkrete adaptere til en kerne, der definerer grænsefladerne.
Princippet præcist formuleret
DIP siger:
- Moduler på højt niveau bør ikke afhænge af moduler på lavt niveau. Begge bør afhænge af abstraktioner.
- Abstraktioner bør ikke afhænge af detaljer. Detaljer afhænger af abstraktioner.
Det vanskelige punkt er, at grænsefladen tilhører forbrugeren, ikke implementøren. Dit domæne erklærer PaymentGateway; Stripe-adapteren følger den — ikke omvendt.
Definér abstraktionen i kernen
Læg grænsefladen ved siden af den kode, der har brug for den, og udtryk den i domænetermer. Ingen Stripe-typer slipper ind.
<?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) {} }Lad en adapter følge den
Infrastrukturadapteren implementerer kernens grænseflade og oversætter til leverandørens SDK. Afhængighedspilen peger fra adapteren (detaljen) til abstraktionen (politikken) — inversionen er opnået.
<?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ørinjektion er standarden
Injicér via konstruktøren, og angiv abstraktionen som type. Afhængigheder bliver eksplicitte, uforanderlige og umulige at glemme. Undgå injektion via settere og egenskaber til nødvendige samarbejdskomponenter — det giver mulighed for halvfærdige 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;
}
}Sammensætningsroden
Al konkret sammenkobling sker præcis ét sted — sammensætningsroden — så tæt på main()/indgangspunktet som muligt. Intet andet opretter infrastruktur med new. Dette er det eneste sted, der ved, at Stripe findes.
<?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);Sammenkobling med en DI-container
For ikke-trivielle applikationer automatiserer en container (PHP-DI, Symfony) sammenkoblingen. Det afgørende er binding af grænseflader til implementeringer. Automatisk sammenkobling finder konstruktører ud fra deres typer; du skal kun erklære grænseflade→klasse-tilknytningen.
<?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')),
];Udskiftning af adaptere beviser pointen
Fordi kernen kun afhænger af PaymentGateway, er det at skifte udbyder eller teste offline en ændring på én linje. Her er et testdobbelt, der bruges i en enhedstest — CheckoutService er uændret og importerer aldrig 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=1Undgå tjenestelokaliseringsfælden
At injicere selve containeren og hente afhængigheder inde i metoder er antimønsteret service locator. Det skjuler afhængigheder, omgår typekontrol og kobler igen din kode til containeren. Injicér det, du har brug for, eksplicit.
<?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 må optræde
Containeren er kun legitimt placeret i ét lag: sammensætningsroden og rammeværkskoden omkring den (f.eks. en controllerfabrik). Dit domæne og dine applikationsklasser skal forblive containeruafhængige — de modtager almindelige objekter via konstruktører og kan oprettes manuelt. En god test: kunne du koble hele applikationen sammen i én PHP-fil uden en container? Hvis ja, er dine afhængigheder ærlige.
Udskudt sammenkobling uden tjenestelokalisering
Nogle gange er en afhængighed dyr at opbygge eller kun nødvendig under bestemte betingelser. Modstå fristelsen til at injicere containeren — injicér i stedet en fabriksfunktion. Afhængigheden forbliver eksplicit og typetjekket, mens oprettelsen udskydes, indtil den faktisk bruges.
<?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;Hurtigt tjek
Hvem bør eje grænsefladen PaymentGateway?
Opsummering
Du har forbundet adapterne med kernen på den rigtige måde:
- Inversion ≠ injektion: Injektion er mekanismen, mens inversion handler om, at forbrugeren ejer abstraktionen.
- Kernen definerer grænsefladen; infrastrukturadaptere følger den.
- Brug konstruktørinjektion af abstraktioner; udfør al konkret sammenkobling i én enkelt sammensætningsrod (eller i en containerkonfiguration, der binder grænseflade→klasse).
- Det bliver en ændring på én linje at udskifte adaptere og bruge testdoubler i tests.
- Undgå antimønsteret service locator — hold kun containeren ved kanten.
Lær PHP med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 49
- Lektioner
- 195
Ofte stillede spørgsmål
Er lektionen “Dependency inversion i praksis” gratis?
Ja — hele teksten til “Dependency inversion i praksis” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af PHP Academy-kurset, skal du opgradere til CoddyKit PRO. PHP Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Dependency inversion i praksis”?
Forbind adapters med kernen ved hjælp af dependency inversion. Du øver dig i PHP Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på PHP Academy?
Der kræves ingen tidligere erfaring. PHP Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.
Hvor lang tid tager lektionen “Dependency inversion i praksis”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne PHP Academy-lektion?
Ja. Alle PHP Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Fra layered til clean architecture
- Ports og adapters forklaret
- Use cases og application services
- Dependency inversion i praksis