İçeriğe geç
academia.sh

Ders 12 / 12

Bağımlılık Enjeksiyonu

Yapıcıdan enjeksiyon, arayüze bağımlılık ve el ile bağlama; bir hizmetin bağımlılığının yalnız kurulum dosyasında değişmesi; bağımlılığı içeride kurmanın değişikliği hizmetlere taşıması; bağlantı noktasının sözleşme korunduğu sürece geçerli olması; on iki dersin ölçü tablosu.

Yayın Güncelleme

Bu derste

  • Yapıcıdan enjeksiyonla kurulan bir hizmetin bağımlılığının yalnız kurulum dosyasında değiştiğini ölçer
  • Bağımlılığını içeride kuran hizmetin, bağımlılığın yapıcısı değişince durduğunu karşılaştırır
  • Bağlantı noktasının yalnız sözleşme korunduğu sürece bağlantıda kaldığını gösterir
  • Kurs boyunca ölçülen on iki değişikliği dört sınıfa göre özetler
İçindekiler

Bağımlılık Yöneticisi dersi, kodun bağlı olduğu paketin hangi sürümünün kurulacağını tek bir kilit dosyasında topladı. Sınıfın bağımlılığını ise kim kuruyor? Bu derste soru şu: bir hizmetin ihtiyaç duyduğu nesne değişirse bu değişiklik nereye ulaşıyor ve hangi dosya düzenleniyor? M10/K04 bir bileşenin neye ihtiyaç duyduğunu sordu; burada ölçülen, o gereksinim değiştiğinde değişikliğin kullananlara ulaşıp ulaşmadığıdır.

Bağımlılığı Kim Kurar

Bir hizmetin ihtiyaç duyduğu nesneye bağımlılık (dependency) denir. Hizmet bağımlılığını iki yoldan edinir: ya kendi içinde new ile kurar ya da kurulmuş hâlde dışarıdan alır. İkinci yola bağımlılık enjeksiyonu (dependency injection) denir ve PHP’de bir biçimi yapıcıdan enjeksiyondur: nesne bağımlılığını yapıcı işlevinin parametresi olarak ister.

İkinci karar, parametrenin tipidir. Hizmet somut bir sınıfın adını değil bir arayüzün adını yazarsa, o arayüzü gerçekleştiren her sınıf kabul edilir. Hizmet artık bağımlılığının hangi sınıf olduğunu bilmez, yalnız hangi sözleşmeyi karşıladığını bilir. Nesneleri bir araya getiren işe bağlama (wiring) denir ve el ile yapıldığında tek bir yerde, genellikle bir kurulum dosyasında toplanır; bu dosya bağlantı noktasıdır.

ST5. Ders yalnız yapıcıdan enjeksiyonu ölçer. Özellik ya da yöntem üzerinden enjeksiyon, bir nesne kabının bağımlılıkları kendiliğinden çözmesi ve nesnelerin paylaşılıp paylaşılmaması modellenmez. Ölçümdeki bağlama, bir fonksiyon dizisidir.

Ölçüm Düzeneği

yayilim.php önceki derslerle aynıdır: aynı programı değişiklikten önce ve sonra kurup koşturur. Sırayla sorar: çıkış 0 değilse kıran, çıktı farklıysa örtük, düzenlenen dosya bağlantı listesindeyse bağlantıda, hiçbiri değilse yerinde. Önceki sürüm 0 ile çıkmalıdır ve kullanan main.php düzenlenmez.

<?php
// yayilim.php — bir degisikligin nereye ulastigini olcen yardimcilar

function yaz(string $dizin, array $dosyalar): void
{
    foreach ($dosyalar as $ad => $icerik) {
        if ($icerik === null) {
            continue; // null: dosya silindi
        }
        $yol = "$dizin/$ad";
        if (!is_dir(dirname($yol))) {
            mkdir(dirname($yol), 0777, true);
        }
        file_put_contents($yol, $icerik);
    }
}

function oku(string $dizin): array
{
    $dosyalar = [];
    $gezgin = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dizin, FilesystemIterator::SKIP_DOTS)
    );
    foreach ($gezgin as $dosya) {
        $ad = substr($dosya->getPathname(), strlen($dizin) + 1);
        $dosyalar[$ad] = file_get_contents($dosya->getPathname());
    }
    return $dosyalar;
}

function kostur(string $dizin): array
{
    $komut = 'cd ' . escapeshellarg($dizin) . ' && php -d display_errors=0 main.php 2>/dev/null';
    exec($komut, $satirlar, $kod);
    return [$kod, implode("\n", $satirlar)];
}

function olc(string $ad, array $taban, array $degisiklik, array $baglanti = []): string
{
    if (array_key_exists('main.php', $degisiklik)) {
        throw new LogicException("$ad: kullanan duzenlenmez");
    }
    yaz("olcum/$ad/once", $taban);
    yaz("olcum/$ad/sonra", array_merge($taban, $degisiklik));
    [$kodOnce, $ciktiOnce] = kostur("olcum/$ad/once");
    [$kodSonra, $ciktiSonra] = kostur("olcum/$ad/sonra");
    if ($kodOnce !== 0) {
        throw new LogicException("$ad: taban calismadi");
    }
    $sinif = match (true) {
        $kodSonra !== 0 => 'kiran',
        $ciktiOnce !== $ciktiSonra => 'ortuk',
        array_intersect(array_keys($degisiklik), $baglanti) !== [] => 'baglantida',
        default => 'yerinde',
    };
    printf(
        "%-18s %d dosya  cikis=%-3d cikti %-6s -> %s\n",
        $ad,
        count($degisiklik),
        $kodSonra,
        $ciktiOnce === $ciktiSonra ? 'ayni' : 'farkli',
        $sinif
    );
    return $sinif;
}

El ile Bağlama: Bağlantıda

Gözlem el-ile-baglama: Profil hizmeti bir Depo ister ve bir adı yazıp geri okur. Tabanda üç dosya var. depo.php arayüzü ve DiziDepo gerçekleştirimini taşır. main.php içindeki Profil yapıcısında Depo tipli bir parametre ister ve hangi depoyu alacağını bilmez. kurulum.php ise bir fonksiyon dizisi döndürür: depo anahtarı çağrıldığında bir DiziDepo kurar.

O dizi en küçük nesne kabıdır (IoC container). Bir ad verirsiniz, kurulmuş nesne alırsınız; nesnenin nasıl kurulduğu yalnız bu dosyada yazılıdır. Aşağıdaki küçük blok, her çağrının yeni bir örnek kurduğunu ve dizinin gerçekleştirimin sınıf adını yalnız burada verdiğini gösterir.

<?php
// kap.php — fonksiyon dizisi en kucuk nesne kabidir
interface Depo
{
    public function oku(string $anahtar): ?string;
}
class DiziDepo implements Depo
{
    public function oku(string $anahtar): ?string { return null; }
}
$kurulum = ['depo' => fn () => new DiziDepo()];
$a = $kurulum['depo']();
$b = $kurulum['depo']();
echo get_class($a), "\n";
echo $a === $b ? 'ayni ornek' : 'ayri ornek', "\n";
DiziDepo
ayri ornek

Değişiklik tek dosyada: kurulum.php artık depoyu, yazmaları sayan bir SayanDepo ile sarıyor. Profil ve main.php yerinde duruyor. Sonraki blok tabanı taban.php olarak diske yazar; sonraki sürücüler onu çeker. Dördüncü argüman bağlantı listesidir ve kurulum.php’yi bağlantı noktası olarak adlandırır.

<?php
// taban.php — Profil, Depo ve kurulum dosyasi
return [
    'depo.php' => <<<'PHP'
        <?php
        interface Depo
        {
            public function yaz(string $anahtar, string $deger): void;
            public function oku(string $anahtar): ?string;
        }
        class DiziDepo implements Depo
        {
            private array $veri = [];
            public function yaz(string $anahtar, string $deger): void { $this->veri[$anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$anahtar] ?? null; }
        }
        PHP,
    'kurulum.php' => <<<'PHP'
        <?php
        return ['depo' => fn () => new DiziDepo()];
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'depo.php';
        class Profil
        {
            public function __construct(private Depo $depo) {}
            public function adAta(string $ad): string { $this->depo->yaz('ad', $ad); return $this->depo->oku('ad'); }
        }
        $kurulum = require 'kurulum.php';
        echo (new Profil($kurulum['depo']()))->adAta('ece'), "\n";
        PHP,
];
<?php
// baglama.php — depo kurulum dosyasinda sarilir
require_once 'yayilim.php';

olc('el-ile-baglama', require 'taban.php', [
    'kurulum.php' => <<<'PHP'
        <?php
        class SayanDepo implements Depo
        {
            public int $yazma = 0;
            public function __construct(private Depo $ic) {}
            public function yaz(string $anahtar, string $deger): void { $this->yazma++; $this->ic->yaz($anahtar, $deger); }
            public function oku(string $anahtar): ?string { return $this->ic->oku($anahtar); }
        }
        return ['depo' => fn () => new SayanDepo(new DiziDepo())];
        PHP,
], ['kurulum.php']);

echo kostur('olcum/el-ile-baglama/once')[1], ' -> ', kostur('olcum/el-ile-baglama/sonra')[1], "\n";
el-ile-baglama     1 dosya  cikis=0   cikti ayni   -> baglantida
ece -> ece

İlk satır ölçümün sonucu: bir dosya değişti, çıkış 0 kaldı, çıktı aynı kaldı ve değişen dosya bağlantı listesinde olduğu için sınıf bağlantıda. İkinci satır iki sürümün çıktısıdır: ece önce de sonra da. Profil yeni bir bağımlılık aldığını bilmedi, çünkü yalnız Depo tipini tanıyordu. Sayma özelliği, yani hizmete eklenen yeni bir davranış, Profil düzenlenmeden geldi.

Karşıt: Bağımlılığı İçeride Kurmak

Aynı hizmetler bağımlılıklarını kendi yapıcılarında new DiziDepo() ile kursaydı, ilk bakışta fark görünmezdi. Fark değişiklikte çıkar. Aşağıdaki tabanda Profil ve Sepet adlı iki hizmet depolarını kendileri kuruyor ve DiziDepo’nun yapıcısı parametresiz. Değişiklik tek dosyada ve yalnız DiziDepo’ya dokunuyor: yapıcı bir anahtar öneki istiyor.

<?php
// icerde.php — iki hizmet bagimliligini kendi icinde kuruyor
require_once 'yayilim.php';

olc('icerde-new', [
    'depo.php' => <<<'PHP'
        <?php
        class DiziDepo
        {
            private array $veri = [];
            public function yaz(string $anahtar, string $deger): void { $this->veri[$anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$anahtar] ?? null; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'depo.php';
        class Profil
        {
            private DiziDepo $depo;
            public function __construct() { $this->depo = new DiziDepo(); }
            public function adAta(string $ad): string { $this->depo->yaz('ad', $ad); return $this->depo->oku('ad'); }
        }
        class Sepet
        {
            private DiziDepo $depo;
            public function __construct() { $this->depo = new DiziDepo(); }
            public function urunAta(string $urun): string { $this->depo->yaz('urun', $urun); return $this->depo->oku('urun'); }
        }
        echo (new Profil())->adAta('ece'), ' ', (new Sepet())->urunAta('kalem'), "\n";
        PHP,
], [
    'depo.php' => <<<'PHP'
        <?php
        class DiziDepo
        {
            private array $veri = [];
            public function __construct(private string $onek) {}
            public function yaz(string $anahtar, string $deger): void { $this->veri[$this->onek . $anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$this->onek . $anahtar] ?? null; }
        }
        PHP,
]);

echo kostur('olcum/icerde-new/once')[1], "\n";
icerde-new         1 dosya  cikis=255 cikti farkli -> kiran
ece kalem

Sınıf kıran: bir dosya değişti, çıkış 255. Son satır önceki sürümün çıktısıdır; sonraki sürüm hiçbir şey basamadı. Profil ve Sepet düzenlenmedi ama yapıcıdaki new DiziDepo() çağrısı yeni zorunlu parametreyi veremedi ve program durdu. Aynı kalıp her hizmette yinelendiği için bağımlılığın değişimi bu iki hizmetin ikisine de ulaştı. Birincisinde bağımlılığı kuran yer tek bir dosyaydı; burada kuran yer her hizmetin kendi gövdesidir, yani bağlantı noktası hizmet sayısı kadardır.

ST6. Karşıt ölçümde kıran sonucu, DiziDepo yapıcısına eklenen zorunlu parametreden gelir. Parametre isteğe bağlı olsaydı ya da yapıcı hiç değişmeseydi ölçüm başka sonuç verirdi; ders bunu ölçmez.

Sınırlayıcı Ölçüm

Bağlantı noktası değişikliği karşıladı, ama her değişikliği karşılamaz. Bağlantının geçerliliği sözleşmeye bağlıdır: Profil yapıcısı Depo tipini ister. Bu ölçümde kurulum.php, yaz ve oku yöntemlerini taşıyan ama Depo’yu gerçekleştirmeyen bir nesne döndürür. Yöntem adları aynıdır; yalnız implements Depo yoktur. Taban el-ile-baglama tabanıdır ve değişiklik yine yalnız kurulum.php’dedir.

<?php
// sozlesme.php — kurulum, Depo'yu gerceklestirmeyen bir nesne dondurur
require_once 'yayilim.php';

olc('sozlesmesiz', require 'taban.php', [
    'kurulum.php' => <<<'PHP'
        <?php
        class SozlukDepo
        {
            private array $veri = [];
            public function yaz(string $anahtar, string $deger): void { $this->veri[$anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$anahtar] ?? null; }
        }
        return ['depo' => fn () => new SozlukDepo()];
        PHP,
], ['kurulum.php']);
sozlesmesiz        1 dosya  cikis=255 cikti farkli -> kiran

Aynı dosya, aynı bağlantı listesi, ama sınıf kıran oldu: çıkış 255. Değişen dosya bağlantı listesindeydi; bu yetmedi, çünkü sıralamada çıkış kodu bağlantıdan önce gelir. Profil yapıcısındaki tip bildirimi SozlukDepo’yu geri çevirdi. Bu ölçümde ileti basılmadı; durduran düzenek Tip Bildirimleri dersindeki aynı tip denetimidir. SozlukDepo doğru yöntemlere sahip olsa da sözleşmeyi bildirmedi. Bağlantı noktası yalnız sözleşme korunduğu sürece değişikliği tek dosyada tutar; sözleşmeyi bozan bir bağlama kullananı durdurur.

ST7. sozlesmesiz ölçümü bağlama kodunun hatalı olduğu durumu modeller: bir tip bildirimi var ve bağlayan yanlış nesneyi veriyor. Tip bildirimi olmasaydı Profil aynı yöntem adlarıyla çalışabilirdi; ders bu durumu ölçmez.

Özet

  • Bağımlılık enjeksiyonu, bir hizmetin ihtiyaç duyduğu nesneyi içeride new ile kurmak yerine yapıcısından almasıdır; arayüz tipi hizmeti gerçekleştirimden ayırır.
  • Bu dersin ölçü sınıfı bağlantıda. el-ile-baglama gözleminde depo kurulum.php içinde sarıldı, bir dosya değişti, çıkış 0 kaldı ve çıktı ece kaldı. Fonksiyon dizisi en küçük nesne kabıdır.
  • Karşıtı icerde-new kıran: iki hizmet depoyu kendi içinde kurduğundan DiziDepo yapıcısına parametre eklemek ikisini de durdurdu, çıkış 255.
  • Sınırlayıcı ölçüm sozlesmesiz kıran: kurulum.php Depo’yu gerçekleştirmeyen bir nesne döndürünce Profil yapıcısındaki tip bildirimi durdurdu, çıkış 255.
  • Bağlantı noktası yalnız sözleşme korunduğu sürece değişikliği tek dosyada tutar.

Kurs Kapanışı

Kurs tek bir soruyu on iki kez sordu: bu değişiklik nereye ulaşıyor? Her ders çekirdekten tek bir değişiklik aldı ve dört sınıftan birine yerleştirdi. Aşağıdaki tablo her dersin sınıfını ## Özet bölümünden, sınırlayıcı ölçümünü dersin ## Sınırlayıcı Ölçüm bölümünden alır.

Ders Ölçülen değişiklik Sınıf Sınırlayıcı ölçüm
Sınıflar ve Nesneler özel özelliğin adı yerinde public özelliğe yazan kullanan: yerinde, E_DEPRECATED
Kalıtım ve Soyut Sınıflar üst sınıf iç çağrıyı bıraktı örtük bileşimle sayaç: yerinde, 3
Arayüzler arayüze yöntem eklendi kıran gövdeli soyut sınıf: yerinde, 120
Trait’ler trait’e çakışan ad eklendi kıran sınıfın kendi yöntemi: yerinde
Statik Üyeler ve Sabitler yapıcı imzası, statik fabrika yerinde sabit değeri 20 → 18: örtük, 120 → 118
Sihirli Yöntemler __get anahtarının adı örtük bilinmeyen adda istisna: kıran
Tip Bildirimleri parametreye int eklendi kıran ?int ve '5': yerinde, float(0) ve float(6)
Ad Alanları dış kod aynı adlı sınıf ekledi yerinde nitelenmemiş strlen: örtük, 5 → 0
Otomatik Yükleme sınıf dosyası taşındı, 3 dosya bağlantıda urun.php küçük harf: kıran
Ortak Arayüz Standartları gerçekleştirim değişti bağlantıda doldurulmuyor: örtük
Bağımlılık Yöneticisi alt sürüm yayımlandı, kilitsiz örtük 2.0.0 yayımlandı: yerinde
Bağımlılık Enjeksiyonu depo kurulumda sarıldı bağlantıda Depo olmayan nesne: kıran

Derslerin dağılımı 3 yerinde · 3 bağlantıda · 3 kıran · 3 örtüktür. Her ders çekirdekten tek bir gözlem taşıdığı için bu dağılım çekirdeğin on iki değişikliğinin dağılımıyla aynıdır; çekirdeğin kendisi hiçbir derste koşturulup basılmadı, dağılım derslerin sınıf hücrelerinden sayılmıştır.

Tablodan çıkan iki gözlem kursun tezidir. Birincisi, sınıf değişikliğin büyüklüğünden değil, kullananın neye dokunduğundan gelir: aynı tanım değişikliği okuyan kullananı örtük, yazan kullananı yerinde bıraktı; aynı yöntem eklemesi gövdesizse kıran, gövdeliyse yerinde oldu. İkincisi, en tehlikeli sınıf örtüktür: kıran değişiklik ilk koşumda kendini ele verir, örtük değişiklik ancak sonuca bakılınca görülür. Sınırlayıcı ölçümler her derste sınıfın bir koşula bağlı olduğunu gösterdi; dört sınıf bir tanımın değil, tanımla kullananın arasındaki bağın ölçüsüdür.

Sıradaki kurs M12/K03, Web ile PHP: Oturum, Veri ve Güvenlik. O kurs oturum ve çerezlerin durumu isteklere nasıl taşıdığını, veritabanı erişimini ve parametreli sorguyu, ardından web güvenliğini konu alır. Bu kursun getirdiği ayrım, değişikliğin nereye ulaştığını sınıflandıran dört ad, orada da bir değişikliğin kullananı nasıl etkilediğini okurken işe yarar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat