Php Dependecy İnjection Örnek Uygulama

Bir önceki yazımızda dependency injection hakkında temel bilgiler edinerek giriş yapmıştık. Burada ise php ile uygulamalı dependency injection kullanıyor olacağız.

Örneğimizde; bizim bir haber sitemiz var veya bir platformda haber içerikleri yayınlıyor olalım. Uygulamamıza haberleri ise anlaşmalı olduğumuz AA(Anadolu Ajansı) kurumundan aldığımızı varsayalım. İki çeşit veri alabilelim.

1) İstediğimiz adet kadar manşet haberleri alalım. getTopNews($top) methodu

2) Verdiğimiz haber id sinden haberin detaylarını alabilelim. getNewsDetail($newsId) methodu

 

Class AnadoluHaber
{
 public function getTopNews($top = 5)
 {
 // anadolu haber ajansından manset haberleri getir
 echo "AA haber ajansından gelen manşet haberler.";
 }
 
 public function getNewsDetail($newsId)
 {
 // anadolu haber ajansında yer alan haber detayını getirir
 echo "AA {$newsId} nolu haber detayı";
 }
}

// anasayfamizi temsil eden class
class MainPage
{
 public function showTopNews()
 {
 // anasayfamizda ust slider kisminda mansetleri gosterelim
 $haberAjans = new AnadoluHaber();
 $haberAjans->getTopNews();
 }
}

Çıktı: “AA haber ajansından gelen manşet haberler.” olacaktır. Yani AA haber ajansından gelen manşet haberleri listeliyor olacağız.

 

Birde haber detay göstereceğimiz sayfamızda yan tarafında manşetleri listeliyor olalım.

// haber detay sayfamizi temsil eden class
class NewsDetail
{
 private $haberAjans;
 public function __construct()
 {
// haber ajans class ini yukleyelim
$this->haberAjans = new AnadoluHaber();
 }
 
 public function getNewsDetail($newsId)
 {
 // haber detayini ajanstan alalim
 $this->haberAjans->getNewsDetail($newsId);
 }
 
 public function showTopNews()
 {
 // haber detay sayfamizin yan kisminda mansetleri gosterelim 
 $this->haberAjans->getTopNews();
 }
}

$detail = new NewsDetail();
// detay
$detail->getNewsDetail(1);
// Çıktı: "Anadolu H.A 1 nolu haber detayı"

// mansetler
$detail->showTopNews();Çıktı: "AA haber ajansından gelen manşet haberler."

Buraya kadar güzel anlaştığımız ajansa ait verileri uygulamamızda alıp gösterebiliyor işlerimizi görüyoruz. Yarın patron geldi artık CHA (Cihan Haber Ajansı) ile çalışmaya başlayacağız gerekli düzenleme ve çalışmalar yapılsın dedi. Evet bu demek oluyor AA ajansına ait yaptığımız çalışmalar geçersiz olacak. Buda demek oluyor ki AA class ını kullandığım her yerde bu değişiklik gerçekleşecek. İyi ama peki nerelerde kullanmıştık? Biz kocaman bir haber portalıyız hemen hemen her yer de kullanılıyor ama bu.Bunu kullanan tüm class ve alt class larda bu değişikliği nasıl yapacağız.Peki ne kadar vaktimiz var? Bu class ı geçen sene çalışan Ali abi yapmıştı ben içeriğini ve detay inceliklerini bilmiyorum ki nasıl çıkıcam bu işten? Gibi gibi bir ton sorular ve sorunlar oluşmaya başlayacaktır. Proje büyüdükçe bunların üstesinden gelmek zorlaşır ve hata yapmaya açıktır.

Diyelim ki yeni ajans yapısını da iyi kötü oluşturduk.

Class CihanHaber
{
 public function getTopNews($top = 5)
 {
 // cihan haber ajansından manset haberleri getir
 echo "Cihan haber ajansından gelen manşet haberler";
 }
 
 public function getNewsDetail($newsId)
 {
 // Cihan haber ajansında yer alan haber detayını getirir
 echo "Cihan H.A {$newsId} nolu haber detayı";
 }
}

Peki nerelerde değişiklik yapacağız şimdi? AA class ının olduğu her yerde. Yani ana sayfamızda ve haber detay sayfamızda tek tek bulup yeni class ımızla değiştireceğiz.

// anasayfamizi temsil eden class
class MainPage
{
 public function showTopNews()
 {
 // anasayfamizda ust slider kisminda mansetleri gosterelim
// $haberAjans = new AnadoluHaber();
 $haberAjans = new CihanHaber();
 $haberAjans->getTopNews();
 }
}
// haber detay sayfamizi temsil eden class
class NewsDetail
{
 private $haberAjans;
 public function __construct()
 {
// $this->haberAjans = new AnadoluHaber();
 $this->haberAjans = new CihanHaber();
 }
 
 public function getNewsDetail($newsId)
 {
 // haber detayini ajanstan alalim
 $this->haberAjans->getNewsDetail($newsId);
 }
 
 public function showTopNews()
 {
 // haber detay sayfamizin yan kisminda mansetleri gosterelim 
 $this->haberAjans->getTopNews();
 }
}

Projenizde bu sınıfı kullanan her yerde değişiklikleri yapmanız gerekir. Güzel değişiklikleri gerçekleştirdik oh be tamamdır diyoruz. Eskilerini açıklama satırı haline getirip kurtulduk. Peki ya yarın yine başka bir ajans ile anlaşılmak istenirse? Yine aynılarını yaparım vakitte alsa diyorsunuz. Bu böyle gider. Peki ya kurumunuz uygulamanızda köklü bir değişikliğe giderse. AB testleri gibi bir kısım kullanıcılara AA bir diğer kısım kullanıcılara CHA haberlerini göstermek durumları incelemek isterse. Nasıl hem AA hem CHA haberlerini gösterebilirim düşünmeye başlarsınız. Sonra yine pratik şekilde yol bulursunuz. Projeyi tekrar açarak 2 class ıda aynı şekilde farklı farklı çalıştırırım, her eklendiğinde aynı şekilde projeyi açar yeni kodları ekler çalıştırırım dersiniz.

$ajansAA = new AnadoluHaber();

$ajansCHA = new CihanHaber();

Her yeni eklendiğinde böyle uzar gider en son bir yerde daha çıkılmaz hal alabilir. İçlerinde geliştirmeler düzenlemeler yeni özellikler istenildikçe. Bu kaçınılmazdır. Kod tekrarları fazla yazılmış kodlar, geçen zamanlar, uygulama pratikliği, dinamikliği. OOP gücünden faydalanmayı bilmeliyiz. Bunun hem bize katkısı hemde işlerimize katkısı olacaktır.

Peki çözüm ? Dependency İnjection Nerede Ne Sağlar ?

İşte burada her özellik için(ajanslar) ayrı class lar yazılır. Ama bütünlüğü bozmamak için bu class lara kurallar getiririz. Yani bu class ta bu methodlar mutlaka olacak bunlarla çalışacağız ve çalıştıracağız. Buda eşittir İnterface demek. Her bir özellik sınıfımızda(Ajans) diğer sınıflar tarafından kullanılan çalıştırılacak ortak methodlar belirliyoruz. Sonra uygulamamız yükleme (veya ilgili yerde) aşamasında config (ayarlar) verinizden hangi özelliğin(ajansın) yüklenmesi gerektiğini görür ve ona göre uygulamaya o özelliği – yapıyı ekler. Böylece bu sınıfı kullanan her yerde isteğe göre seçilmiş olan yapının içeriği kullandırılmış olur. Yani ana sayfamızın class ı hangi haber ajansının yüklendiğini bilmez ilgilenmez sadece belirtilmiş methodu çağırır. Çağırdığı method yüklenen ilgili ajansa ait haberleri getirir. Böylece her sınıf sadece kendisi ile ilgilenmiş olup diğer sınıflara doğrudan bağlılığı kalmaz.

Uygulamamızı Dependency İnjection Şeklinde Yenileyelim

1) Öncelikle tüm özellik – yapılarımızda(Burada Ajans Class ları) ortak kulları belirleyelim. Yani interface arayüz tanımlayalım.

Interface IHaberAjans
{
 public function getTopNews($top);
 
 public function getNewsDetail($newsId);
}

Bu interface tüm haber ajanslarında implement olacak, böylece her ajansın yapısında getTopNews($top) şeklinde manşet haberlerini getiren ve getNewsDetail($newsId) şeklinde haber detaylarının getirildiği methodları barındırması zorunda olacak.

2) Yapılarımızı bu arayüzle güncelleyelim.

Class AnadoluHaber implements IHaberAjans
{
 public function getTopNews($top = 5)
 {
 // anadolu haber ajansından manset haberleri getir
 echo "Anadolu haber ajansından gelen manşet haberler";
 }
 
 public function getNewsDetail($newsId)
 {
 // anadolu haber ajansında yer alan haber detayını getirir
 echo "Anadolu H.A {$newsId} nolu haber detayı";
 }
}

Class CihanHaber implements IHaberAjans
{
 public function getTopNews($top = 5)
 {
 // cihan haber ajansından manset haberleri getir
 echo "Cihan haber ajansından gelen manşet haberler";
 }
 
 public function getNewsDetail($newsId)
 {
 // Cihan haber ajansında yer alan haber detayını getirir
 echo "Cihan H.A {$newsId} nolu haber detayı";
 }
}

3) Sınıfımızı kullanan diğer sınıflara yer verelim.

// anasayfamizi temsil eden class
class MainPage
{
 private $haberAjans;
 
 public function __construct($_newsAgency)
 {
 $this->haberAjans = $_newsAgency;
 }
 
 public function showTopNews()
 {
 // anasayfamizda ust slider kisminda mansetleri gosterelim
 $this->haberAjans->getTopNews();
 }
}

// haber detay sayfamizi temsil eden class
class NewsDetail
{
 private $haberAjans;
 public function __construct($_newsAgency)
 {
 $this->haberAjans = $_newsAgency;
 }
 
 public function getNewsDetail($newsId)
 {
 // haber detayini ajanstan alalim
 $this->haberAjans->getNewsDetail($newsId);
 }
 
 public function showTopNews()
 {
 // haber detay sayfamizin yan kisminda mansetleri gosterelim 
 $this->haberAjans->getTopNews();
 }
}

 

4) Config de hangi haber ajansını kullanacağımızı bildirelim.

$config = "AnadoluHaber";

Bu değer database den xml den veya projenizde bir değişkeniniz den sağlayabilirsiniz. Biz direk config deki isimden class türeteceğiz.

5) Ana sayfamız ve haber detay sayfamızdan bağımsız olarak config deki seçilen ajansa göre instance alacağız.

// Congig degiskeninde "AnadoluHaber" değeri vardı. Bu yüzden Anadolu Haber sınıfı yuklenecek
// Eger CihanHaber degeri verseydik CihanHaber class ı yuklenecekti
$haberAjans = new $config();

şimdi kullanan class lara enjekte edebiliriz.

// anasayfada gosterilecek manset haberleri haberAjansi degiskeninden alacak. Yani AnadoluHaber classı
$mainPage = new MainPage($haberAjans);
$mainPage->showTopNews();

Aynı şekilde bu classı kulanan haber detay sayfasına da enjekte edelim.

$detail = new NewsDetail($haberAjans);
$detail->getNewsDetail(1);
$detail->showTopNews();

Böylece anasayfa ve haber detay sayfası haber ajansı sınıfından bağımsız olarak çalışmaktadır. haber ajansı ayarlardan okunarak ilgili ajansı dahil eder ve gerekli yerlere enjekte eder. Bu sayede hangi ajans yüklenirse yüklensin bağımsız olarak methodlarımız çalışacaktır. AA ajansıda CHA ajansıda olsa manşet haberler çağrılacaktır.

Yani config değişkenini bu şekilde değiştirirseniz artık CHA ajansını kullanacaktır.

$config = "CihanHaber";

Peki ya eklenen ajans sınıfında bu method yoksa ne olacak?

İşte bu yüzden interface kullandık. Bu methodlar barındırılmasını zorunlu kıldık.

Dependency İnjection Sonuç: Dependency İnjection sayesinde artık her ajans değiştirdiğimizde uygulama kodlarını açıp eski methodları açıklama satırı haline getirmeyeceğiz veya kodlarda düzenelemeler yapıp tekrar uygulamayı hazır hale getirmeyeceğiz. Yapmamız gereken tek şey ayarlarımız da hangi ajansın çalışacağını seçmekten ibaret.

Uygulama çalışmaya devam ederken değişik yapabilme imkanı.

Bu sayede artık testler gerçekleştirebileceğiz çünkü sınıfların birbirleri ile arasında sıkı bir bağ yok hepsini bağımsız olarak çalıştırabiliyor olacağız. Testler yazılabilir.

Bu sayede modülerlik, yeni düzenlemeler hata yakalama, yeni yöntemlere geçişler, başka yerlerde kullanabilme gibi özellikler kazandırırız.

Dahası zaman, ve boşa gitmeyen emekler…

Kodların son hali ise:

<?php

Class AnadoluHaber implements IHaberAjans
{
 public function getTopNews($top = 5)
 {
 // anadolu haber ajansından manset haberleri getir
 echo "Anadolu haber ajansından gelen manşet haberler";
 }
 
 public function getNewsDetail($newsId)
 {
 // anadolu haber ajansında yer alan haber detayını getirir
 echo "Anadolu H.A {$newsId} nolu haber detayı";
 }
}

Class CihanHaber implements IHaberAjans
{
 public function getTopNews($top = 5)
 {
 // cihan haber ajansından manset haberleri getir
 echo "Cihan haber ajansından gelen manşet haberler";
 }
 
 public function getNewsDetail($newsId)
 {
 // Cihan haber ajansında yer alan haber detayını getirir
 echo "Cihan H.A {$newsId} nolu haber detayı";
 }
}

Interface IHaberAjans
{
 public function getTopNews($top);
 
 public function getNewsDetail($newsId);
}

// anasayfamizi temsil eden class
class MainPage
{
 private $haberAjans;
 
 public function __construct($_newsAgency)
 {
 $this->haberAjans = $_newsAgency;
 }
 
 public function showTopNews()
 {
 // anasayfamizda ust slider kisminda mansetleri gosterelim
 $this->haberAjans->getTopNews();
 }
}

// haber detay sayfamizi temsil eden class
class NewsDetail
{
 private $haberAjans;
 public function __construct($_newsAgency)
 {
 $this->haberAjans = $_newsAgency;
 }
 
 public function getNewsDetail($newsId)
 {
 // haber detayini ajanstan alalim
 $this->haberAjans->getNewsDetail($newsId);
 }
 
 public function showTopNews()
 {
 // haber detay sayfamizin yan kisminda mansetleri gosterelim 
 $this->haberAjans->getTopNews();
 }
}

$config = 'AnadoluHaber';
$config = 'CihanHaber';
$haberAjans = new $config();

$mainPage = new MainPage($haberAjans);
$mainPage->showTopNews();

$detail = new NewsDetail($haberAjans);
$detail->getNewsDetail(1);