Dependency inversion in de praktijk
Verbind adapters met de kern via dependency inversion.
Dependency inversion in de praktijk is een gratis PHP Academy-les op CoddyKit. Dit is les 4 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject PHP Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus PHP Academy bevat in totaal 4 lessen.
Inversie versus injectie
Men haalt twee verschillende ideeën door elkaar. Afhankelijkheidsinjectie is een techniek: geef samenwerkende objecten mee in plaats van ze zelf te maken. Afhankelijkheidsinversie (de D in SOLID) is een principe: beleid op hoog niveau en details op laag niveau zijn allebei afhankelijk van een abstractie, en die abstractie is eigendom van de module op hoog niveau. Deze les laat zien hoe je dat tweede principe daadwerkelijk toepast — concrete adapters koppelen aan een kern die de interfaces definieert.
Het principe precies geformuleerd
DIP stelt:
- Modules op hoog niveau mogen niet afhankelijk zijn van modules op laag niveau. Beide zijn afhankelijk van abstracties.
- Abstracties mogen niet afhankelijk zijn van details. Details zijn afhankelijk van abstracties.
Het subtiele punt: de interface is eigendom van de afnemer, niet van de implementerende partij. Je domein definieert PaymentGateway; de Stripe-adapter voldoet eraan — niet andersom.
Definieer de abstractie in de kern
Zet de interface naast de code die haar nodig heeft en druk haar uit in domeintermen. Er lekken geen Stripe-typen naar binnen.
<?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) {} }Laat een adapter eraan voldoen
De infrastructuuradapter implementeert de interface van de kern en vertaalt naar de SDK van de leverancier. De afhankelijkheidspijl wijst van de adapter (detail) naar de abstractie (beleid) — inversie bereikt.
<?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);
}
}Injectie via de constructor is de standaard
Injecteer via de constructor en gebruik een typehint voor de abstractie. Afhankelijkheden worden expliciet, onveranderlijk en onmogelijk om te vergeten. Vermijd injectie via setters en eigenschappen voor verplichte samenwerkers — daarmee kunnen half opgebouwde objecten ontstaan.
<?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;
}
}De compositiewortel
Alle concrete bedrading gebeurt op precies één plek — de compositiewortel — zo dicht mogelijk bij main()/het ingangspunt. Nergens anders wordt infrastructuur met new aangemaakt. Dit is de enige plek die weet dat Stripe bestaat.
<?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);Bedrading met een DI-container
Voor niet-triviale toepassingen automatiseert een container (PHP-DI, Symfony) de bedrading. De belangrijkste stap is interfaces aan implementaties koppelen. Autobedrading bepaalt constructors op basis van typen; je declareert alleen de interface→klasse-toewijzing.
<?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')),
];Adapters verwisselen bewijst het principe
Omdat de kern alleen afhankelijk is van PaymentGateway, kun je van aanbieder wisselen of offline testen met één regel in de bedrading. Hier zie je een nepimplementatie die in een unittest wordt gebruikt — CheckoutService blijft ongewijzigd en importeert Stripe nooit.
<?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=1Vermijd de valkuil van de service locator
De container zelf injecteren en afhankelijkheden binnen methoden ophalen is het service-locatorantipatroon. Het verbergt afhankelijkheden, maakt typecontrole onmogelijk en koppelt je code opnieuw aan de container. Injecteer expliciet wat je nodig hebt.
<?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!
// ...
}
}Waar de container mag voorkomen
De container is precies in één laag gerechtvaardigd: de compositiewortel en de frameworkkoppeling eromheen (bijvoorbeeld een controllerfabriek). Je domein- en applicatieklassen moeten onafhankelijk blijven van de container — ze ontvangen gewone objecten via constructors en kunnen met de hand worden geïnstantieerd. Een goede test: zou je de hele toepassing in één PHP-bestand zonder container kunnen bedraden? Zo ja, dan zijn je afhankelijkheden eerlijk.
Luie bedrading zonder service location
Soms is een afhankelijkheid duur om op te bouwen of alleen onder bepaalde voorwaarden nodig. Weersta de neiging om de container te injecteren — injecteer in plaats daarvan een fabrieksclosure. De afhankelijkheid blijft expliciet en getypeerd, terwijl het opbouwen ervan wordt uitgesteld tot het werkelijk nodig is.
<?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;Snelle controle
Wie moet eigenaar zijn van de interface PaymentGateway?
Samenvatting
Je hebt adapters op de juiste manier aan de kern gekoppeld:
- Inversie ≠ injectie: injectie is het mechanisme, inversie betekent dat de afnemer eigenaar is van de abstractie.
- De kern definieert de interface; infrastructuuradapters voldoen eraan.
- Gebruik injectie via de constructor van abstracties; doe alle concrete bedrading in één compositiewortel (of containerconfiguratie die interface→klasse koppelt).
- Adapters verwisselen en nepimplementaties in tests gebruiken wordt een wijziging van één regel.
- Vermijd het service-locatorantipatroon — houd de container alleen aan de rand.
Leer PHP met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 49
- Lessen
- 195
Veelgestelde vragen
Is de les “Dependency inversion in de praktijk” gratis?
Ja — de volledige tekst van “Dependency inversion in de praktijk” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus PHP Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus PHP Academy bevat in totaal 4 lessen.
Wat leer ik in “Dependency inversion in de praktijk”?
Verbind adapters met de kern via dependency inversion. Je oefent met PHP Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met PHP Academy te beginnen?
Ervaring vooraf is niet nodig. PHP Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.
Hoe lang duurt de les “Dependency inversion in de praktijk”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over PHP Academy?
Ja. Elke les over PHP Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Van layered naar clean architecture
- Ports en adapters uitgelegd
- Use cases en application services
- Dependency inversion in de praktijk