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
newile 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-baglamagözleminde depokurulum.phpiçinde sarıldı, bir dosya değişti, çıkış 0 kaldı ve çıktıecekaldı. Fonksiyon dizisi en küçük nesne kabıdır. - Karşıtı
icerde-newkıran: iki hizmet depoyu kendi içinde kurduğundanDiziDepoyapıcısına parametre eklemek ikisini de durdurdu, çıkış 255. - Sınırlayıcı ölçüm
sozlesmesizkıran:kurulum.phpDepo’yu gerçekleştirmeyen bir nesne döndürünceProfilyapı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.