ile

Hiç Bilmeyenler İçin Nesne Yönelimli Programlamaya Giriş-6— Girdi Sınıfı

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

Medium.com adresinde görüntüleyin

Bir önceki yazıda girdi sınıfımızı kullanan sözlük sınıfımızı yazdık. Deneme/Yanılma yani Try/Catch bloklarını kullanarak hatalara dayanıklı kod yazmayı ve kod tekrarlarını önlemekten bahsettik. Bu yazıda Entry yani girdi sınıfımızı oluşturacağız.

Sözlük girdisi

Dictionary.php sınıfımızın bulunduğu dizinde Entry.php adlı bir dosya oluşturalım. Artık bir sınıfın aşağı yukarı nasıl olması gerektiğini biliyorsunuz. İçi boş sınıfımızı şu şekilde yazalım.

<?php

namespace
MidoriKocak;


class Entry implements EntryInterface
{
public function __construct(){}
public function setKey(string $key){}
public function getKey(): string{}
public function setValues(array $values){}
public function getValues(): array{}
public function getValue(int $order): string{}
public function setValue(int $order, string $newValue){}
public function addValue(string $value){}
public function deleteValue(int $order){}
}

Her sınıfın __construct metodu olması gerektiğine dikkat edelim. Diğer metodlar daha önce EntryInterface.php içinde isimlerini tanımladığımız metodlar olacak. Tek tek metotlarımızın içlerini doldurmaya başlamadan önce, sınıfa özel private, yani sadece sınıfın içinden erişebileceğimiz değişkenleri sınıfımızın koduna eklemeye başlayalım.

__construct yani sihirli yaratma metodundan önce değişkenleri sınıfa şu şekilde ekleyelim:

<?php

namespace
MidoriKocak;


class Entry implements EntryInterface
{
private $key;
private $values;

public function __construct(string $key, string $value)
{
$this->setKey($key);
$this->values = [];
array_push($this->values, $value);
}

Yaptıklarımızı her zaman yaptığım gibi tek tek açıklayayım:

  1. Sınıfımıza private yani özel tipinde, $key ve $values adlı iki değişken ekledik.
  2. $key burada metin tipinde girdinin başlığını tutacak. Her başlıkta olduğu gibi, boş olmayacak ve 70 karakterden uzun olmayacak. Buna benzer kontrolü, Dictionary.php dosyasında setTitle metodunda yapmıştık. Aynı metodu alıp biraz değiştirerek burada kullanacağız.
  3. $values değişkeni, girdinin sahip olduğu açıklamaları tutan diziyi içerecek.
  4. __construct metodu içinde parametre olarak $key ve $value adlı iki değer tanımladık ki, girdi sınıfımızı, kolaylık açısından, örneğin $entry = new Entry(‘nesne’, ‘harika bişi’); diyerek yaratabilelim.
  5. __construct sınıfımızın içine baktığımızda, tıpkı Dictionary.php dosyasındaki __construct metodundaki setTitle metodunda olduğu gibi, setKey adlı metod ile $key değişkenini belirliyoruz ve bu sayede $key değişkeninin boş olmadığını, yani “” değerinde bir metin olmadığını ve 70 karakterden kısa olduğundan emin olarak nesnemizi yaratıyoruz.
  6. $this->values = [] diyerek boş bir dizi oluşturuyoruz.
  7. array_push metodu ile aldığımız $value değerini doğrudan $values dizisinin içerisine, sıra numarası olmadan ekliyoruz. Daha sonra addValue metodunun içini doldurduğumuzda bu satırı değiştirmeliyiz. Önemli: Uymamız gereken önemli bir kural olarak değişkenlere her zaman, o değişkenleri değiştiren ilgili set/get veya add metodları ile erişmeli veya manipüle etmeliyiz.

Getter ve Setter metodlarını bankadaki veznedar gibi düşünebilirsiniz.

Erişici ve Değiştirici yani Setter ve Getter metotları

Sınıfımızın metodlarının içlerini doldurmaya devam edelim:

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

public function getKey(): string
{
return $this->key;
}

setKey ve getKey metodları, daha önce Dictionary.php yani sözlük sınıfımızdaki setTitle ve getTitle metodlarında olduğu gibi, değişkeni kurallarımıza uyarak değiştirmemizi ve erişimi kontrol etmemizi sağlıyorlar. Burada getKey metodu doğrudan değişkeni döndürmekte. Fakat daha sonra, örneğin, erişimi bir şekilde kontrol etmek istersek (Örneğin sadece yazının yazarının erişebildiği bir yazı gibi), bu metodu değiştirebiliriz. Aklımızda bulunsun. Yine try/catch blokları ile hata denetimi yaptık.

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

public function getValues(): array
{
return $this->values;
}

Tıpkı setKey ve getKey metotlarında olduğu gibi gelen değerleri kontrol ettik. Burada gelen dizinin boş olmamasını kontrol ediyoruz. Fakat, dizinin tek boyutlu ve her elemanının metin tipinde olduğunu tek tek kontrol etmemiz gerekirdi. Şimdilik kolaylık açısından bunu daha sonraya bırakalım.

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

Daha önce Dictionary.php sınıfında, bir anahtarın dizide olup olmadığını kontrol eden ve hata döndüren isKeyInEntries($key) metodu yazmıştık. Burada da isOrderInValues metodu ile aynı şeyi yapıyoruz ki olmayan bir değere erişim sağlamaya çalışan bir gerizekalı programımızı çökertmesin.

public function setValue(int $order, string $newValue)
{
if ($this->isOrderInValues($order)) {
$this->values[$order] = $newValue;
}
}

public function getValue(int $order): string
{
if ($this->isOrderInValues($order)) {
return $this->values[$order];
}
return "";
}

public function addValue(string $value)
{
array_push($this->values, $value);
}

public function deleteValue(int $order)
{
if ($this->isOrderInValues($order)) {
unset($this->values[$order]);
}
}

Kalan metodlarımızı da tek tek açıklayalım:

  1. setValue: Sıra numarası ile açıklamayı düzenleyen metot. Bu metot aslında istediğimiz değerleri sıra numarası ile erişip düzenlememize yarıyor. Yine isOrderInValues metodu ile o sıra numarasında değer olup olmadığını kontrol ettik.
  2. getValue: Sıra numarası ile açıklamayı döndürüyor.
  3. addValue: Sıra numarası kullanmadan doğrudan $values değişkeninin yani açıklamalar dizisinin sonuna istediğimiz değeri ekliyor.
  4. deleteValue: Sıra numarası girerek açıklamayı silmemizi sağlıyor.

AddValue ve setValue metotlarında, açıklamanın zaten ekli olup olmadığını da kontrol edebilirdik, ancak bunu da şimdilik kolaylık açısından geçiyorum. Arama kısmına geldiğimizde, bu konuyu detaylı olarak anlatacağım.


Girdi yani Entry sınıfımızı tanımlamayı bitirdik. Gelelim kodumuzu kullanacak kodu yazmaya. Aynı dizinde daha önceden yazdığımız app.php isimli dosyayı şu şekilde değiştirelim:

<?php

require_once 'DictionaryInterface.php'
;
require_once 'Dictionary.php';
require_once 'EntryInterface.php';
require_once 'Entry.php';

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

$nesne = new MidoriKocakEntry('nesne', 'aklımızın dışındaki herşey');

$nesne->addValue('harika bişi');
$nesne->addValue('ingilizce object');

$şey = new MidoriKocakEntry('şey', 'ismi olmayan nesne');

$dictionary->addEntry($nesne);
$dictionary->addEntry($şey);

$entries = $dictionary->getEntries();

print_r($entries);

Burada print_r metodu, dizileri insanların okuyacağı şekilde ekrana basan metoddur. Eğer herhangi bir sorun olmazsa, web sunucumuzdan app.php adlı dosyayı çağırdığımız zaman şöyle bir sonuçlar karşılaşacağız:

Ne kadar çok chrome extension yüklemişim yahu.

Sonuç olarak elimizde doğru dürüst çalışan tertemiz iki sınıfımız oldu.

Hata mesajlarımız anlaşılır ve açık olmalı. Bu nedir yani?

Peki hata durumunda ne olacak? app.php dosyasını şu şekilde değiştirelim:

<?php

require_once 'DictionaryInterface.php'
;
require_once 'Dictionary.php';
require_once 'EntryInterface.php';
require_once 'Entry.php';

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

$nesne = new MidoriKocakEntry('nesne', 'aklımızın dışındaki herşey');

$nesne->addValue('harika bişi');
$nesne->addValue('ingilizce object');

$şey = new MidoriKocakEntry('', 'ismi olmayan nesne');

$dictionary->addEntry($nesne);
$dictionary->addEntry($şey);

$entries = $dictionary->getEntries();

print_r($entries);

$şey değişkeninde oluşturduğumuz entry nesnesinin başlık bilgisini girmeyi unuttuk. Sonuç ne olacak? Görelim:


Dikkat. Eğer uncaught error gibi bir hata gördüysek hata try/catch bloklarımızın dışında meydana gelmiş ve biz önlem alamamışız demektir. İnceleyelim. Burada sorun, key değeri boş olmasına rağmen, setKey metodu içinde istisnayı yakaladığımız için kodun çalışmaya devam etmiş olması. Bunu şimdilik setKey metodunu şu şekilde değiştirerek çözelim:

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

Sorunu şu anlık çözmek adına catch bloğu içinde kodu durduracak bir istisna fırlattık. Hata kodumuz şu şekilde değişti:


Gördüğümüz gibi kod çalışmaya devam etmedi ve hatanın olduğu yeri tamı tamına bize iletti.

Hata denetimi kimin sorumluluğunda?

Sorumluluk

Konuyu öğretmek açısından metotlarımızda try/catch bloklarını birlikte metotun içinde kullandık. Ancak, bu hatalara karşı önlem almak bizim sınıfımızın sorumluluğunda değil. Hatayı düzeltmek ve buna karşı önlem almak o kodu hatalı çalıştıran kişinin sorumluluğudur. Sınıf, sadece hatalı durumu belirtecek istisnayı fırlatır ve pisliğini temizle kardeşim der. Ayrıca, catch bloklarını tekrar tekrar metotlar içinde aynı şekilde kullandığımızı farkettiniz. Eğer bir yerde sık sık kod tekrarı oluyorsa, tekrarlanan kodun başka bir yere gitmesi ve oralardan erişilmesi gerekiyor. SOLID ilkelerinden henüz detaylıca bahsetmemiştim, ancak Single Responsibility Principle yani Tek Sorumluluk Prensibine göre her sınıfın iyi yapması gereken tek bir iş var. Bu tek sorumluluğa hata denetimi yapmak girebilir ama kodu kullanan kişinin hatasını temizlemek girmez. O zaman sınıfılarımızdaki try/catch bloklarını kaldıracak ve başka bir yere taşıyacağız. (Not: kitapta örnekleri baştan düzeltmek lazım.)

Yani tryb bloğu içindeki ifadeyi bırakalım, ve kalanını silelim. Örneğin setTitle metodu şöyleydi:

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

Bunu şu şekilde değiştirelim:

public function setKey(string $key)
{
if (($key != "") && (strlen($key) <= 70)) {
$this->key = $key;
} else {
throw new InvalidArgumentException('Wrong key title.');
}
}

Gördüğümüz gibi kod artık sadece hata durumunda istisna fırlatıyor, ama bu istisnanın pisliğinin temizlenmesi artık kullanıcının sorumluluğunda. Entry.php ve Dictionary.php içindeki tüm try/catch bloklarını bu şekilde kaldırlarım. Yani try bloğu içindeki kodu dışarı taşıyalım, ve try/catch bloğunu yukarıdaki örnekteki gibi silelim.

Dictionary.php dosyamızın son hali şu şekilde olsun:

<?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;
} else {
throw new InvalidArgumentException('Wrong title value.');
}
}

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

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

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

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

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

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]);
}
}
}

Entry.php dosyamızın son hali de şu şekilde:

<?php

namespace
MidoriKocak;


class Entry implements EntryInterface
{
private $key;
private $values;

public function __construct(string $key, string $value)
{
$this->setKey($key);
$this->values = [];
array_push($this->values, $value);
}

public function setKey(string $key)
{
if (($key != "") && (strlen($key) <= 70)) {
$this->key = $key;
} else {
throw new InvalidArgumentException('Wrong key title.');
}
}

public function getKey(): string
{
return $this->key;
}

public function setValues(array $values)
{
if (!empty($values)) {
$this->values = $values;
} else {
throw new InvalidArgumentException('Array cannot be empty.');
}
}

public function getValues(): array
{
return $this->values;
}

private function isOrderInValues($order)
{
if (!array_key_exists($order, $this->values)) {
throw new OutOfBoundsException('Cannot find entry in dictionary');
} else {
return true;
}
}

public function setValue(int $order, string $newValue)
{
if ($this->isOrderInValues($order)) {
$this->values[$order] = $newValue;
}
}

public function getValue(int $order): string
{
if ($this->isOrderInValues($order)) {
return $this->values[$order];
}
return "";
}

public function addValue(string $value)
{
array_push($this->values, $value);
}

public function deleteValue(int $order)
{
if ($this->isOrderInValues($order)) {
unset($this->values[$order]);
}
}
}

Şimdi app.php dosyamızı da şu şekilde değiştirelim:

<?php

require_once 'DictionaryInterface.php'
;
require_once 'Dictionary.php';
require_once 'EntryInterface.php';
require_once 'Entry.php';

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

$nesne = new MidoriKocakEntry('nesne', 'aklımızın dışındaki herşey');

$nesne->addValue('harika bişi');
$nesne->addValue('ingilizce object');

$şey = new MidoriKocakEntry('', 'ismi olmayan nesne');

$dictionary->addEntry($nesne);
$dictionary->addEntry($şey);

$entries = $dictionary->getEntries();

print_r($entries);
} catch (Exception $e) {
echo 'Error on line '.$e->getLine().' in '.$e->getFile()
.': <b>'.$e->getMessage();
}

Gördüğümüz gibi tüm kullanıcı kodumuzu try/catch bloğu içinde aldık. Artık elimizde tek bir try/catch bloğu var. Üstelik catch bloğu içinde hata mesajımızı da değiştirdik. Bu sayede hatanın olduğu dosyayı, satırı ve mesajı aynı anda görebileceğiz. Eğer bunu metotlar içinde sürekli tekrarlanan try/catch blokları içinde yapsaydık aynı kodu sürekli yapıştırmamız gerekecekti. Unutmayın, eğer bir yerde kod tekrarı varsa, kötü tasarım vardır.


Şimdi hata kodumuzun nasıl göründüğüne bakalım:


Güzel görünüyor değil mi? Bu sayede kodumuzun neresinde hata olduğunu rahatça anlayıp değiştirebiliyoruz. Tek catch bloğu kullandığımız için, kodumuz ilk hatada duracak, ve kodun kalan kısmı da hatalı bir şekilde çalışmaya devam etmeyecek.

Şimdilik bu kadar. Bu yazıda entry yani girdi sınıfımızı tanımladık ve try/catch bloklarımızın kullanımını düzelttik. Bir sonraki yazıda uygulamamızı tamamlamaya devam edeceğiz.

Bir sonraki yazıya şuradan ulaşabilirsiniz:

Medium.com adresinde görüntüleyin


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/midorikocak.pdf 
Websitem:
http://mtkocak.net

Yorumla

Yorum