PHP Academy · Oppitunti

Riippuvuuksien kääntäminen käytännössä

Yhdistäkää sovittimet ytimeen riippuvuuksien kääntämisen avulla.

Oppitunti 4/413 vaihetta

Riippuvuuksien kääntäminen käytännössä on ilmainen PHP Academy-oppitunti CoddyKitissä. Tämä on oppitunti 4/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu PHP Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. PHP Academy-kurssilla on yhteensä 4 oppituntia.

Inversio ja injektointi

Kaksi eri käsitettä sekoitetaan usein keskenään. Riippuvuusinjektio on tekniikka: yhteistoimijat välitetään sisään sen sijaan, että ne luotaisiin siellä. Riippuvuuksien kääntäminen (SOLID:n D) on periaate: korkean tason politiikka ja matalan tason yksityiskohdat riippuvat molemmat abstraktiosta, jonka korkean tason moduuli omistaa. Tässä oppitunnissa tehdään jälkimmäisestä käytäntöä — konkreettiset adapterit kytketään ytimeen, joka määrittelee rajapinnat.

Periaate täsmällisesti

DIP:n mukaan:

  • Korkean tason moduulit eivät saa riippua matalan tason moduuleista. Molemmat riippuvat abstraktioista.
  • Abstraktiot eivät saa riippua yksityiskohdista. Yksityiskohdat riippuvat abstraktioista.

Hienovarainen kohta on tämä: rajapinta kuuluu käyttäjälle, ei toteuttajalle. Toimialueenne määrittelee PaymentGateway-rajapinnan; Stripe-adapteri mukautuu siihen — ei päinvastoin.

Määritä abstraktio ytimessä

Sijoittakaa rajapinta sitä tarvitsevan koodin viereen ja ilmaiskaa se toimialueen termeillä. Stripe-tyypit eivät saa vuotaa mukaan.

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

Mukauta adapteri siihen

Infrastruktuurin adapteri toteuttaa ytimen rajapinnan ja muuntaa kutsut toimittajan SDK:n muotoon. Riippuvuusnuoli osoittaa adapterista (yksityiskohdasta) abstraktioon (politiikkaan) — inversio on saavutettu.

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

Konstruktorin injektointi on oletus

Injektoikaa riippuvuudet konstruktorin kautta ja käyttäkää tyyppinä abstraktiota. Riippuvuudet muuttuvat eksplisiittisiksi ja muuttumattomiksi, eikä niitä voi unohtaa. Välttäkää setter- ja property-injektiota pakollisille yhteistoimijoille — ne mahdollistavat puolivalmiit oliot.

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

Composition root

Kaikki konkreettinen kytkentä tapahtuu täsmälleen yhdessä paikassa — composition rootissa — mahdollisimman lähellä main()-funktiota tai sisääntulopistettä. Mikään muu ei luo infrastruktuuria new-operaattorilla. Tämä on ainoa paikka, jossa tiedetään Stripen olevan käytössä.

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

Kytkentä DI-kontainerilla

Monimutkaisemmissa sovelluksissa kontaineri (PHP-DI, Symfony) automatisoi kytkennän. Keskeinen toimenpide on rajapintojen sitominen toteutuksiin. Automaattinen kytkentä ratkaisee konstruktorit tyypin perusteella; tarvitsee ilmoittaa vain rajapinta→luokka-kartoitus.

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

Adapterien vaihtaminen todistaa asian

Koska ydin riippuu vain PaymentGateway-rajapinnasta, palveluntarjoajan vaihtaminen tai offline-testaus vaatii vain yhden rivin muutoksen kytkentään. Tässä on yksikkötestissä käytettävä feikki — CheckoutService pysyy muuttumattomana eikä koskaan tuo Stripea mukaan.

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

Välttäkää service locator -ansaa

Kontainerin itsensä injektointi ja riippuvuuksien hakeminen metodien sisällä on service locator -antimalli. Se piilottaa riippuvuudet, tekee tyyppitarkistuksesta tehottoman ja kytkee koodin jälleen kontaineriin. Injektoikaa tarvitsemanne riippuvuudet eksplisiittisesti.

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

Missä kontaineri saa esiintyä

Kontaineri on oikeutettu täsmälleen yhdessä kerroksessa: composition rootissa ja sen ympärillä olevassa frameworkin liimakoodissa (esimerkiksi ohjaimen tehtaassa). Toimialue- ja sovellusluokkien on pysyttävä kontainerista riippumattomina — ne saavat tavalliset oliot konstruktoreilla, ja ne voitaisiin luoda käsin. Hyvä testi: voisitteko kytkeä koko sovelluksen yhteen PHP-tiedostoon ilman kontaineria? Jos voisitte, riippuvuutenne ovat rehellisiä.

Laiska kytkentä ilman service locatoria

Joskus riippuvuuden luominen on kallista tai sitä tarvitaan vain tietyissä tilanteissa. Älkää injektoiko kontaineria — injektoikaa sen sijaan factory closure. Riippuvuus säilyy eksplisiittisenä ja tyypitettynä, mutta sen luomista lykätään siihen asti, kunnes sitä todella tarvitaan.

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

Pikatarkistus

Kenen tulisi omistaa PaymentGateway-rajapinta?

Kertaus

Kytkitte adapterit ytimeen oikealla tavalla:

  • Inversio ≠ injektio: injektio on mekanismi, inversio tarkoittaa, että kuluttaja omistaa abstraktion.
  • Ydin määrittelee rajapinnan; infrastruktuurin adapterit mukautuvat siihen.
  • Käyttäkää abstraktioiden konstruktorin injektiota ja tehkää kaikki konkreettinen kytkentä yhdessä composition rootissa (tai kontainerin määrityksessä, joka sitoo rajapinnan→luokan).
  • Adapterien vaihtaminen ja feikkien käyttäminen testeissä muuttuu yhden rivin muutokseksi.
  • Välttäkää service locator -antimallia — pitäkää kontaineri vain reunalla.
Aloita maksutta

Opi PHP tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
49
Oppitunnit
195

Usein kysytyt kysymykset

Onko oppitunti ”Riippuvuuksien kääntäminen käytännössä” ilmainen?

Kyllä – oppitunnin ”Riippuvuuksien kääntäminen käytännössä” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko PHP Academy-kurssin, päivitä CoddyKit PROhon. PHP Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Riippuvuuksien kääntäminen käytännössä”?

Yhdistäkää sovittimet ytimeen riippuvuuksien kääntämisen avulla. Harjoittelet PHP Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni PHP Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin PHP Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 4/4.

Kuinka kauan ”Riippuvuuksien kääntäminen käytännössä”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä PHP Academy-oppitunnilla?

Kyllä. Jokainen PHP Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Kerrosarkkitehtuurista Clean Architectureen
  2. Portit ja sovittimet selitettyinä
  3. Käyttötapaukset ja sovelluspalvelut
  4. Riippuvuuksien kääntäminen käytännössä
← Takaisin: PHP Academy