ile

Hiç Bilmeyenler İçin Nesne Yönelimli Programlamaya Giriş-5 — Sınıfı inşa etmek


Bu yazı daha önce yazdığım Hiç Bilmeyenler İçin Nesne Yönelimli Programlamaya Giriş-4 yazısının devamıdır. Önce onu okumanızı öneririm.

View story at Medium.com

Önceki yazılarda nesne yönelimli programlama mantığını, sınıflarımızı nasıl oluşturacağımızı, tek sorumluluk prensibini, bilgi gizlemeyi, sihirli yaratıcı metotları kavramaya çalıştık. Bir önceki yazıda sadece metot isimlerini içeren arayüzler yani interface’ler yazdık. Önümüzde iki yol var:

  1. Harala gürele kullanıcıyı düşünmeden sınıfların içlerini doldurmaya başlamak.
  2. Ya da bu sınıfları kullanacak uygulamayı yazmak. Yani bu sınıfları uygulamayı yazarken test etmek ve çıkabilecek hatalara önceden önlem almak. Biz arayüz yazdığımız için kullanıcının kullanacağı kodları zaten belirtmiş olduk.

PHP’de test konusuna bu yazıda basitçe değineceğim. Çünkü kodumuzu insanlar kullanacak. Daha sonra phpUnit konusuna geldiğimizde detaylıca anlatacağım da. Bu insanların neye ihtiyaçları olduğunu, neler yapabileceklerini az çok tahmin edebiliyorsak, sınıflarımızın içlerini bu kullanıcıların yapacakları hatalara karşı dayanıklı bir şekilde doldurabiliriz. Tabii ki hiçbir kod parçası doğduğu anda hatalara karşı bağışıklık kazanmış değildir. Ama biz önceden tahmin edemeyeceğimiz durumlara olabileceğini aklımızda tutarak kodumuzu yazarsak, daha sonra kafamızı beton duvarlara vurmayız.


Sıradan vatandaş sınıfımızı nasıl kullanır?

Sıradan vatandaş sınıfımızı arayüzlerine bakarak kullanır. Interface yani arayüz, yazdığımız sınıf için aynı zamanda bir kullanım kılavuzu işlevi de görür aslında. Bu nedenle sınıfımızı arayüze uygun hale getirmekle işe başlayabiliriz.

Şimdi Dictionary.php dosyamızın içindeki her şeyi silelim ve şu kodu ekleyelim:

<?php

namespace
MidoriKocak;

class Dictionary implements DictionaryInterface
{
private $title;
private $entries;

public function __construct(string $title)
{
$this->setTitle($title);
$this->entries = [];
}

public function setTitle(string $title)
{
if (($title != "") && (strlen($title) <= 70)) {
$this->title = $title;
}
}
}

Burada yaptığımız bir kaç değişiklik var. implements kelimesini kullanarak, sınıfımızın DictionaryInterface.php içinde tanımladığımız sözlük arayüzünü kullandığını belirttik. İkinci yaptığımız değişiklik ise __construct metodunun içinde setTitle metodunu çağırdık ki, boş başlık gibi girdilerle, VALIDATION yani doğrulama kurallarımız ezilmesin. Her durumda geçerli olsun.

Doğrulama

Set metotlarının hiçbir şey döndürmediğine dikkat ettiniz mi? Zorunlu olmasa da bu bir kuraldır. Peki bir kullanıcı yanlış başlık girdiğinde bundan nasıl haberimiz olacak? Ya da yanlış başlık kullanılarak sözlük yaratılmasının önüne nasıl geçeceğiz? İşte burada PHP’deki harika programlama yapısı olan try/catch yani deneme/yanılma yapısı devreye giriyor.

Dene ve Yanıl yani Try/Catch

Kodu sürekli hatalara karşı kontrol edeceğiz.

Örneğin set metotlarımızda try/Catch yani deneme yanılma blokları kullanarak, abuk subuk değerler geldiğinde kullanıcıyı bu durumdan haberdar edeceğiz ve bu duruma göre önlem alacağız ki kullanıcı programı çökertmesin. Şimdi setTitle metodumuzu şu şekilde değiştirelim:

public function setTitle(string $title)
{
try {
if (($title != "") && (strlen($title) <= 70)) {
$this->title = $title;
} else {
throw new InvalidArgumentException("Wrong title value.");
}
} catch (Exception $e) {
echo $e->getMessage();
}
}

Metodumuzu satır satır okuyalım.

  1. Try/Catch bloklarının dışında kod olmadığına dikkat edin.
  2. Try bloğunu açtığımızda, php yorumlayıcısından, burada olacak hataları, veya diğer bir değişle exception yani istisnai durumları gözetlemesini rica ediyoruz.
  3. if bloğundan sonra, else bloğu ekledik ki, hatalı bir girdi olduğunda throw diyerek, önceden tanımlı istisnalardan birini fırlatalım. Burada invalidArgument yani Hatalı parametre istisnası fırlattık. Bu sayede kodu kullanan kişi, coder, yani oraya hatalı bilgi giren gerizekalı, nerede hata yaptığını anlayacak ve önlemini alacak bi zahmet. İstisna içinde göstermek istediğimiz mesajı “Wrong title value.” diyerek belirttik.
  4. catch bloğu içerisinde Exception $e diyerek ne tipte hataya dikkat etmemiz gerektiğini tanımladık. echo $e->message ifadesiyle meydana gelen istisnanın mesajını ekrana bastık.

İstisna yani Excetion

Genellikle try/catch blokları bu şekilde yazılır. Ancak örneğin biz ekrana cart diye böyle birşey basarsak, bütün sitemizin ya da kullanıcıya döndürdüğümüz örneğin JSON formatındaki değerin yapısı bozulacak. (Not: JSON formatını daha sonra API bölümüne geldiğimizde detaylıca anlatacağım.) Daha sonra bu echo ifadesini değiştireceğiz, ama şimdilik böyle kalsın.

Tüm setter metotlarımızda try/catch mantığını kullanmalıyız. Bu sayede hatalı bir bilgi girişi olduğunda, nasıl bir hatayla karşılaştığımızı anlarız.

Aynı dizinde oluşturduğumuz app.php dosyasını şu şekilde değiştirelim.

<?php

require_once 'DictionaryInterface.php'
;
require_once 'Dictionary.php';

$dictionary = new MidoriKocakDictionary("Nesne Yönelimli Programlama Sözlüğü");

Daha sonra bu dosyada sınıfımızı kullanacak diğer kodları yazacağız. Dosyayı web sunucumuzdan çağıralım. Şöyle bir görüntü karşımıza gelecek:


Yani diyor ki, hani verdiğin sözler, vaadettiğin public metodlar nerde? O yüzden devam edelim ve DictionaryInterface.php dosyasında tanımladığımız arayüzdeki metotları Dictionary.php dosyası içinde tanımlamaya devam edelim. setTitle metodundan sonra şu metodları sınıfımıza ekleyelim. Korkmaya gerek yok, hepsini tek tek anlatacağım.

<?php

namespace
MidoriKocak;

class Dictionary implements DictionaryInterface
{
private $title;
private $entries;

public function __construct(string $title)
{
$this->setTitle($title);
$this->entries = [];
}

public function setTitle(string $title)
{
try {
if (($title != "") && (strlen($title) <= 70)) {
$this->title = $title;
} else {
throw new InvalidArgumentException('Wrong title value.');
}
} catch (Exception $e) {
echo $e->getMessage();
}
}

public function getTitle(): string
{
return $this->title;
}

public function getEntries(): array
{
$entries = [];
foreach ($this->entries as $entry) {
$entries[$entry->getKey()] = $entry->getValues();
}
return $entries;
}

public function setEntries(array $entries)
{
try {
if (!empty($entries)) {
$this->entries = $entries;
} else {
throw new InvalidArgumentException('Array cannot be empty.');
}
} catch (Exception $e) {
echo $e->getMessage();
}
}

public function addEntry(EntryInterface $entry)
{
$key = $entry->getKey();
$this->entries[$key] = $entry;
}

public function getEntry(string $key): EntryInterface
{
try {
if (array_key_exists($key, $this->entries)) {
return $this->entries[$key];
} else {
throw new OutOfBoundsException('Cannot find entry in dictionary');
}
} catch (Exception $e) {
echo $e->getMessage();
}
}

public function deleteEntry(string $key)
{
try {
if (array_key_exists($key, $this->entries)) {
unset($this->entries[$key]);
} else {
throw new OutOfBoundsException('Cannot find entry in dictionary');
}
} catch (Exception $e) {
echo $e->getMessage();
}
}

}

Şimdi içlerini doldurduğumuz metotlara tek tek gözatalım.

  1. getTitle: Pek açıklayacak birşey yok. Doğrudan sözlüğün başlığını metin tipinde döndürecek.
  2. addEntry: Sözlük sınıfındaki girdiler dizisine istediğimiz başlığı ekleyecek. Burada girdi başlığının boş olup olmamasıyla, ya da girdinin açıklaması olup olmamasıyla artık ilgilenmiyoruz. Bu konular Dictionary yani sözlük sınıfımızın sorumluluğunda değil. Çünkü bunları tek bir sınıfta halletmeye kalksaydık, sözlük sınıfı devasa bir Tanrı nesnesi (God Object) olurdu, ve tek başına her boku yapmaya çalışırdı. Bu metodun içinde $entry değişkeninde girdi Arayüzü sınıfını tanımlayan (implement eden) bir girdi nesnesi geleceğinden emin olduğumuz için, gelen nesnenin doğru dürüst verilere sahip olup olmadığını doğrulamamıza gerek yok. Zaten PHP yorumlayıcısı type hinting yani tip ipucu, yani metodun parametresinin tipini EntryInterface olarak tanımladığımız için, saçma sapan bir değere izin vermeyecek. Bu yüzden tekrar try/catch bloğunda bu değeri oluşabilecek hatalara karşı kontrol etmek için doğrulama yapmamıza gerek yok. Burada daha önce arayüzü açıklarken dediğimiz gibi, set yerine add yani ekle ifadesini kullandık. Çünkü set dediğimiz zaman, sınıfa ait tek bir değişkeni değiştirmeye çalıştığımız anlaşılıyor.
  3. setEntries: Yine burada da try/catch bloğu ile gönderdiğimiz dizinin boş olup olmadığını kontrol ettik. Aslında tek tek dizinin her elemanının EntryInterface tipinde olup olmadığını da kontrol etmemiz gerekiyordu. Ancak bu metodu daha sonra, hızlıca dosyadan sınıfı oluşturmak için kullanacağımız için bu kontrolü yapmayı veriyi kaydetme kısmına bırakıyorum.
  4. getEntries: EntryInterface tipinde objelere sahip olan sınıfın $entries dizisini döndürecek olan metot. Burada kolaylık açısından, diziyi metin içeren elemanlar olarak döndürelim.
  5. getEntry: Burada try/catch bloğu ile doğrulama yaptık çünkü sorulan key yani girdi başlığı değerinin dizi içerisinde olup olmadığını kontrol etmek istiyoruz. İstisna olarak OutOfBoundsException fırlattık. Bu istisna bir dizide olmayan değere işaret ettiğimizi gösteriyor.
  6. deleteEntry: Tıpkı getEntry metodunda olduğu gibi $key değerini hatalara karşı kontrol ettik. Kod tekrarı yaptığımıza dikkat edin.

Sözlük programımızı yazmaya devam ediyoruz.

Kod tekrarlarını önlemek

Şahsen yapılan kod tekrarlarına uyuz olan bir insan olduğum ve kopyalayıp yapıştırılan kod öbekleri, kodumuzu saçma bi şekilde şişmanlatacağından tekrarlanan kodu ayrı bir metot haline getireceğim.

Şimdi en sondaki getEntry ve deleteEntry metotlarını silelim ve şu kodu ekleyelim:

private function isKeyInEntries($key) :bool
{
try {
if (!array_key_exists($key, $this->entries)) {
throw new OutOfBoundsException('Cannot find entry in dictionary');
} else {
return true;
}
} catch (Exception $e) {
echo $e->getMessage();
}
return false;
}

public function getEntry(string $key): EntryInterface
{
if ($this->isKeyInEntries($key)) {
return $this->entries[$key];
}
}

public function deleteEntry(string $key)
{
if ($this->isKeyInEntries($key)) {
unset($this->entries[$key]);
}
}

Gördüğünüz gibi isKeyInEntries adında bir metod yazdık. :bool diyerek kesinlikle bool yani true false, yani doğru yanlış tipinde bir değişken döndürmesi gerektiğini belirttik. Metodun sonuna return false koyduk ki, başta belirttiğimiz return sözüne uyalım. Eğer bunu buraya koymazsak, kullandığımız ide veya phpmd, phpcs gibi kod kontrol araçları, neden metod kodunda birşey döndürmüyorsun diyebilir. if bloğu içinde return ifadesi olması yeterli değil. Kod her durumda eğer birşey döndürmeyi taahüt ettiyse o değer tipinde bir değişkeni döndürmeli. (Not: Null Object Pattern kısmında bu konuyu detaylıca anlatacağım.)

isBilmemne şeklinde yazilan metodlar genellikle kontrol metodlarıdır ve true veya false döndürürler. Bu try/catch bloğunda yaptığımız kontrol işini bizim için yapacak olan metot. private diyerek bu metodun sadece sınıf içerisinden erişilebileceğini belirttik. Bu sayede programı kullanan herhangi bir kimsenin bu metodun varlığından haberi olmayacak. Bu şekilde metotlar içinde kullandığınız, zırt pırt tekrar eden kod bloklarını ayrı bir private metot haline getirirsek, sınıflarımızı önemli ölçüde fit bir hale getirmiş oluruz.

Şimdi örneğin hatalı bir durumda ne olacağını görmek için, app.php dosyamızı şu şekilde değiştirelim:

<?php

require_once 'DictionaryInterface.php'
;
require_once 'Dictionary.php';

$dictionary = new MidoriKocakDictionary("");
$dictionary->setTitle("Doğru başlık");
$dictionary->getTitle();

app.php dosyasını web sunucumuzdan çağırdığımızda şöyle bir şey karşımıza çıkmalı:

Yakalanmış istisna durumu

Gördüğümüz gibi, kodumuz hatalı girdiyi yakaladı ve php yorumlayıcısı kodun kalan kısmını çalıştırmadı. Bu sayede ne çeşit bir hatayla karşılaştığımızı anladık. Daha sonra bu hata mesajını da özelleştirmeyi göstereceğim.

Şimdilik bu kadar. Bir sonraki yazıda Girdi yani Entry sınıfımızı tanımlayacağız. Sevgiyle kalın.

Bir sonraki yazıya şuradan ulaşabilirsiniz:

View story at Medium.com


Projelerle PHP 7

Ben Mutlu Koçak, Bilgisayar Mühendisiyim, ZCPE Sertifikasına sahibim ve “Hiç Bilmeyenler İçin İnternet Programlamaya Giriş — PHP 7” adlı kitabın yazarıyım. Kitabım: https://www.seckin.com.tr/kitap/911934237
Özgeçmişim:
http://represent.io/mtkocak.pdf 
Websitem:
http://mynameismidori.com

Yorumla

Yorum