Skip to content

10. Katmanlı Uygulamalar

10.1. Introduction

Image

Şimdi bir PHP uygulamasını katmanlara göre nasıl yapılandıracağımızı inceleyeceğiz:

Image

Bu şemada, sağdaki katmanı kullanma inisiyatifini sol katman üstleniyor. Katmanların rolleri şu şekildedir:

  • [1]: [dao] (Veri Erişim Nesneleri) olarak adlandırılan katman, harici veri depolarıyla (dosyalar, veritabanları, web hizmetleri…) olan veri alışverişini yönetir. Bu katman bazen, katmanın rolünü daha iyi tanımlayan bir terim olan [DAL] (Veri Erişim Katmanı) olarak da adlandırılır. Bu katman, [3] verilerini okuyabilir veya [2] verilerini yazabilir. [métier] ve [6] katmanları tarafından çağrılır ve bu katmanlara [7] sonuçlarını döndürür;
  • [5]: [métier] adlı bir katman, ihtiyaç duydukları tüm verilerle beslenen ‘iş’ prosedürlerini bir araya getirir. Verilerin nasıl elde edildiğine bağlı olmadığı için genellikle bir projenin en istikrarlı katmanıdır. Bu verilerin iki kaynağı vardır:
    • [9]: PHP komut dosyası tarafından sağlanan veriler;
    • [6,7]: [dao] katmanından istenen veriler.
  • [8]: Ana komut dosyası, tüm süreci yöneten ana unsurdur. Bir konsol uygulamasında bu komut dosyası:
    • [métier] ve [dao] katmanlarını oluşturur;
    • uygulamanın algoritmasını yürütür. Bu algoritma bir orkestra şefi gibidir: içinde hiçbir ‘iş’ kodu veya veri erişim kodu bulunmaz. Ana komut dosyası, yalnızca [métier] ve [9] katmanlarındaki prosedürleri çağırır. [dao] katmanını ve harici verileri tamamen göz ardı eder. [métier] katmanına [9] verilerini sağlayabilir. Bir konsol uygulamasında bu veriler, yapılandırma dosyalarından veya komut dosyasının kullanıcısından gelebilir. [métier] katmanından [10] sonuçlarını alır. Bazı sonuçları depolaması gerekebilir: bunun için yine [métier] katmanındaki prosedürleri kullanır; bu prosedürler, işi gerçekleştirecek olan [dao] katmanına yönelir;
    • sonuçlar arasında, ana komut dosyası istisnalar alabilir. Tüm katmanlardan gelen istisnaları yönetmek onun görevidir;

Bu katmanlara ayırma, uygulamanın geliştirilmesini kolaylaştırmak amacıyla yapılmıştır. Bunun için her katman bir arayüzü uygular. Şöyle varsayalım:

  • [dao] katmanı, [IDao] arayüzünü uygulayan [Dao] sınıfı tarafından uygulanmaktadır;
  • [métier] katmanı, [IMétier] arayüzünü uygulayan [Métier] sınıfı tarafından uygulanmaktadır;

Böylece, [métier] katmanındaki prosedürler, [Dao] sınıfı yerine [IDao] arayüzünü kullanacaktır. Bu sayede, [Métier] katmanına dokunmadan [Dao] katmanını geliştirmek mümkün olur. Diyelim ki sürüm 1'de [Dao1] katmanı bir veritabanındaki verileri kullanıyor. Bir güncelleme sonucunda, bu veriler artık sürüm 2'deki [Dao2] katmanında bir web hizmeti tarafından sağlanıyor. [Dao1] ve [Dao2] sınıflarının her ikisinin de aynı [IDao] arayüzünü uyguladığından ve aynı istisnaları tetiklediğinden emin olunmalıdır. Bu doğrulanırsa, [IDao] arayüzüyle çalışan [Métier] katmanı değişmeden kalacaktır;

Aynı mantık, [Métier] katmanı için de geçerlidir.

Sınıflar ve arayüzler kullanılarak bir uygulamaya bakalım:

Image

Uygulama, aşağıdaki katmanlı yapıyı uygulayacaktır:

Image

10.2. Katmanlar arasında alışverişi yapılan nesneler

Normalde katmanlar arasında çeşitli nesneler alışverişi yapılır. Burada ise şu sınıfı paylaşacaklar: [Personne.php]:


<?php

// bir kişi
class Personne {
  // kimlik
  private $id;

  // oluşturucu
  public function __construct(int $id) {
    $this->id = $id;
  }

  // toString
  public function __toString(): string {
    return "[Personne($this->id)]";
  }

}

Açıklamalar

  • 6. satır: Kişinin tek bir özniteliği vardır, o da [$id] kimlik numarasıdır. Bunun bir veri ambarında tek bir kişiyi tanımladığını varsayacağız;
  • 9-11. satırlar: Kimlik numarasıyla birlikte bir [Personne] türünü oluşturmaya yarayan oluşturucu;
  • satır 14-16: kişinin kimlik numarasını görüntüleyen [__toString] yöntemi;

10.3. [dao] Katmanı

[dao, 1] katmanı tarafından uygulanan [IDao] arayüzü şöyledir: [IDao.php]:


<?php


// katman [dao] ------------------------
interface IDao {

  // harici bir depodan bir kişinin alınması
  // kişinin kimlik bilgisi verilir
  public function get(int $id): Personne;

  // bir kişinin harici depoya kaydedilmesi
  public function save(Personne $p): void;
}

Bu arayüz, aşağıdaki [Dao1] sınıfı tarafından uygulanmaktadır: [Dao1.php]:


<?php

class Dao1 implements IDao {

  // bir kişinin harici depoya kaydedilmesi
  public function save(Personne $p): void {
    print "[Dao1] : Sauvegarde de la personne $p en base de données [locale]\n";
  }

  // harici bir depodan bir kişinin alınması
  public function get(int $id): Personne {
    print "[Dao1] : Récupération de la personne d'identité ($id) en base de données [locale]\n";
    return new Personne($id);
  }

}

Açıklamalar

  • Sınıf, bir veri ambarıyla etkileşime girmiyor. Yalnızca kodun işleyişini takip etmek için mesajlar görüntüleniyor (7. ve 12. satırlar);
  • 13. satır: Yönteme parametre olarak geçirilen kimliğe sahip bir kişi doğru şekilde döndürülür (11. satır);

Ayrıca, aşağıdaki [Dao2] sınıfı ile [IDao] arayüzünü de uyguluyoruz: [Dao2.php]:


<?php

class Dao2 implements IDao {

  // bir kişinin harici depoya kaydedilmesi
  public function save(Personne $p): void {
    print "[Dao2] : Sauvegarde de la personne $p en base de données [distante]\n";
  }

  // harici depodan bir kişinin alınması
  public function get(int $id): Personne {
    print "[Dao2] : Récupération de la personne d'identité ($id) en base de données [distante]\n";
    return new Personne($id);
  }

}

[Dao2] sınıfı, görüntülenen mesajları değiştirmemiz dışında [Dao1] sınıfına benzerdir.

10.4. [métier] katmanı

[métier, 2] katmanı, aşağıdaki [IMétier] arayüzünü sunar: [IMetier.php]:


<?php

// [métier] katmanı ------------------------
interface IMétier extends IDao {

  // kimlik numarasıyla tanımlanan bir kişinin verilerinin işlenmesi
  public function doSomething(Personne $p): void;
}

Açıklamalar

  • [IMétier] arayüzü, [IDao] arayüzünü genişletir. Bu kesinlikle zorunlu değildir. Burada bunu yapıyoruz çünkü örnek basittir;
  • 7. satır: [doSomething] yöntemi, [Métier] katmanına özgüdür;

[IMétier] arayüzü, aşağıdaki [Métier] sınıfı tarafından uygulanacaktır: [Métier.php]:


<?php

class Métier implements IMétier {
  // [dao] katmanı
  private $dao;

  // getter / setter
  public function setDao(IDao $dao): void {
    $this->dao = $dao;
  }

  public function getDao(): IDao {
    return $this->dao;
  }

  // bir kişiyi işliyoruz
  public function doSomething(Personne $p): void {
    // işleme
    print "Métier : Traitement métier de la personne $p\n";
  }

  // kişinin yedeklenmesi
  public function save(Personne $p): void {
    print "[Métier] : Sauvegarde de la personne $p\n";
    // [dao] katmanından yedekleme yapılmasını istenir
    $this->dao->save($p);
  }

  public function get(int $id): Personne {
    print "[Métier] : récupération de la personne d'identifiant $id\n";
    // kişinin [dao] katmanına aktarılması isteniyor
    $personne = $this->dao->get($id);
    return $personne;
  }

}

Açıklamalar

  • 5. satır: [métier] katmanı, [dao] katmanındaki yöntemleri kullanabilmek için bu katmana bir referans içermelidir;
  • 8-10. satırlar: [setDao] yöntemi, [métier] katmanına [dao] katmanının referansını atamayı sağlar. Parametrenin türünün [IDao] olduğu unutulmamalıdır. Böylece [métier] katmanı, [IDao] arayüzünü uygulayan tüm sınıflarla çalışabilecektir. [Dao1] katmanından [Dao2] katmanına geçildiğinde ve her ikisi de [IDao] arayüzünü uyguluyorsa, [métier] katmanının yeniden yazılmasına gerek yoktur;
  • 17-20. satırlar: [IMetier::doSomething]'in uygulanması;
  • 23-27. satırlar: [IMetier::save]'in uygulaması. Bu yöntem, bir kişiyi veri ambarına kaydetmelidir. [métier] katmanı bunu yapamaz. Bu kaydı gerçekleştirmek için [dao] katmanına başvurur;
  • 29-34. satırlar: [IMetier::get] yönteminin uygulaması. Bu yöntem, parametre olarak kendisine aktarılan kimlik numarasına sahip kişiyi veri ambarından almalıdır. [métier] katmanı bunu yapamaz. Bu işi yapmak için [dao] katmanına başvurur (satır 32);

Sonuç

[métier] katmanı, veri ambarında depolanan verilere erişmesi gerektiğinde, bu verilere erişmek için oluşturulmuş olan [dao] katmanını kullanmalıdır.

10.5. Ana komut dosyası

Bu uygulama için iki ana komut dosyası yazacağız. Birincisi olan [main1.php], [Dao1] katmanını kullanırken, ikincisi olan [main2.php] ise [Dao2] katmanını kullanacaktır. Bunun, [Métier] katmanının kodunu hiçbir şekilde etkilemediğini göstermek istiyoruz.

[main1.php] komut dosyası şu şekildedir:


<?php

// fonksiyon parametrelerine sıkı sıkıya uyulması
declare (strict_types=1);

// sınıf ve arayüzlerin dahil edilmesi
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao1.php";
require_once __DIR__."/Métier.php";

// test ----------------
// katmanların oluşturulması
$dao1 = new Dao1();
$métier = new Métier();
$métier->setDao($dao1);
// katmanın kullanımı [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);

Yorumlar

  • Birkaç noktayı hatırlatalım: [main1.php] komut dosyası, uygulamanın orkestra şefi gibidir. Uygulamanın katman yapısını oluşturur (15-17. satırlar) ve ardından [métier] katmanıyla iletişim kurmaya başlar (19-21. satırlar). Katmanlı yapı şu şekildedir:

Image

Bu şemaya göre, [main1.php] komut dosyası yalnızca [métier] katmanına hitap etmelidir. Teorik olarak mümkün olsa bile, [dao] katmanına hitap etmemelidir.

Çalıştırma sonuçları şöyledir:


[Métier] : récupération de la personne d'identifiant 4
[Dao1] : Récupération de la personne d'identité (4) en base de données [locale]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao1] : Sauvegarde de la personne [Personne(4)] en base de données [locale]

Yorumlar

  • kodun 10. satırı, sonuçların 1. ve 2. satırlarının yazılmasına neden olmuştur;
  • kodun 20. satırı, sonuçların 3. satırının yazdırılmasına neden oldu;
  • kodun 21. satırı, sonuçların 4. ve 5. satırlarının yazdırılmasına neden oldu;

[main2.php] komut dosyası şu şekildedir:


<?php

// fonksiyon parametrelerine sıkı uyma
declare (strict_types=1);

// sınıf ve arayüzlerin dahil edilmesi
require_once __DIR__."/Personne.php";
require_once __DIR__."/IDao.php";
require_once __DIR__."/IMetier.php";
require_once __DIR__."/Dao2.php";
require_once __DIR__."/Métier.php";

// test ----------------
// katmanların oluşturulması
$dao2 = new Dao2();
$métier = new Métier();
$métier->setDao($dao2);
// katmanın kullanımı [métier]
$personne = $métier->get(4);
$métier->doSomething($personne);
$métier->save($personne);

Yorumlar

  • 36-38. satırlar: Katmanlı yapı artık [Dao2] katmanını kullanıyor;

Çalıştırma sonuçları şöyledir:


[Métier] : récupération de la personne d'identifiant 4
[Dao2] : Récupération de la personne d'identité (4) en base de données [distante]
[Métier] : Traitement métier de la personne [Personne(4)]
[Métier] : Sauvegarde de la personne [Personne(4)]
[Dao2] : Sauvegarde de la personne [Personne(4)] en base de données [distante]

[Dao1] katmanı yerel bir veritabanına erişimi simüle ederken, [Dao2] katmanı uzak bir veritabanına erişimi simüle eder. Bu iki katman [IDao] arayüzüne uyduğu sürece, [Métier] katmanının kodunda herhangi bir değişiklik yapılmasına gerek kalmadığı görülmektedir.

Az önce öğrendiklerimizi, ana temamız olan vergi hesaplama alıştırmasına uygulayacağız.