Nesne Yönelimli Programlamaya Giriş

“OOP” terimi, kodu organize etme ve yapılandırma yöntemi olan nesne yönelimli programlamayı ifade eder. OOP, bir programı bir dizi komut ve fonksiyon yerine, birbirleriyle iletişim kuran nesneler topluluğu olarak görmemizi sağlar.

OOP'de “nesne”, verileri ve bu verilerle çalışan fonksiyonları içeren bir birimdir. Nesneler, nesneler için taslak veya şablon olarak anlayabileceğimiz “sınıflara” göre oluşturulur. Bir sınıfımız olduğunda, bu sınıfa göre oluşturulmuş belirli bir nesne olan “örneğini” (instance) oluşturabiliriz.

PHP'de basit bir sınıfı nasıl oluşturabileceğimizi görelim. Bir sınıf tanımlarken, “class” anahtar kelimesini, ardından sınıf adını ve ardından fonksiyonları (bunlara “metotlar” denir) ve sınıf değişkenlerini (bunlara “özellikler” veya İngilizce “property” denir) çevreleyen küme parantezlerini kullanırız:

class Araba
{
	function kornaCal()
	{
		echo 'Bip bip!';
	}
}

Bu örnekte, kornaCal adlı bir fonksiyon (veya “metot”) içeren Araba adında bir sınıf oluşturduk.

Her sınıf yalnızca bir ana görevi çözmelidir. Eğer bir sınıf çok fazla şey yapıyorsa, onu daha küçük, uzmanlaşmış sınıflara bölmek uygun olabilir.

Sınıfları genellikle ayrı dosyalarda saklarız, böylece kod düzenli olur ve içinde gezinmesi kolay olur. Dosya adı sınıf adıyla eşleşmelidir, bu nedenle Araba sınıfı için dosya adı Araba.php olacaktır.

Sınıfları adlandırırken, “PascalCase” kuralına uymak iyidir, bu da addaki her kelimenin büyük harfle başladığı ve aralarında alt çizgi veya başka ayırıcılar olmadığı anlamına gelir. Metotlar ve özellikler “camelCase” kuralını kullanır, yani küçük harfle başlarlar.

PHP'deki bazı metotların özel görevleri vardır ve __ (iki alt çizgi) önekiyle işaretlenirler. En önemli özel metotlardan biri, __construct olarak işaretlenen "yapıcı metot"tur (constructor). Yapıcı metot, bir sınıfın yeni bir örneğini oluşturduğunuzda otomatik olarak çağrılan bir metottur.

Yapıcı metodu genellikle nesnenin başlangıç durumunu ayarlamak için kullanırız. Örneğin, bir kişiyi temsil eden bir nesne oluşturduğunuzda, yaşını, adını veya diğer özelliklerini ayarlamak için yapıcı metodu kullanabilirsiniz.

PHP'de yapıcı metodu nasıl kullanacağımızı görelim:

class Kisi
{
	private $yas;

	function __construct($yas)
	{
		$this->yas = $yas;
	}

	function kacYasindasin()
	{
		return $this->yas;
	}
}

$kisi = new Kisi(25);
echo $kisi->kacYasindasin(); // Yazdırır: 25

Bu örnekte, Kisi sınıfının bir özelliği (değişkeni) $yas ve ayrıca bu özelliği ayarlayan bir yapıcı metodu vardır. kacYasindasin() metodu daha sonra kişinin yaşına erişim sağlar.

$this sözde değişkeni, nesnenin özelliklerine ve metotlarına erişmek için sınıf içinde kullanılır.

new anahtar kelimesi, bir sınıfın yeni bir örneğini oluşturmak için kullanılır. Yukarıdaki örnekte, 25 yaşında yeni bir kişi oluşturduk.

Ayrıca, nesne oluşturulurken belirtilmemişse, yapıcı metot parametreleri için varsayılan değerler ayarlayabilirsiniz. Örneğin:

class Kisi
{
	private $yas;

	function __construct($yas = 20)
	{
		$this->yas = $yas;
	}

	function kacYasindasin()
	{
		return $this->yas;
	}
}

$kisi = new Kisi;  // eğer hiçbir argüman geçmiyorsak, parantezler atlanabilir
echo $kisi->kacYasindasin(); // Yazdırır: 20

Bu örnekte, Kisi nesnesini oluştururken yaşı belirtmezseniz, varsayılan değer olan 20 kullanılacaktır.

Hoş olan şey, özelliğin yapıcı metot aracılığıyla başlatılmasıyla birlikte tanımının bu şekilde kısaltılıp basitleştirilebilmesidir:

class Kisi
{
	function __construct(
		private $yas = 20,
	) {
	}
}

Tamamlamak gerekirse, yapıcı metotlara ek olarak, nesnelerin yıkıcı metotları (destructor) da olabilir (__destruct metodu), bunlar nesne bellekten serbest bırakılmadan önce çağrılır.

İsim Alanları

İsim alanları (veya İngilizce “namespaces”), ilgili sınıfları, fonksiyonları ve sabitleri organize etmemize ve gruplandırmamıza olanak tanırken aynı zamanda isim çakışmalarını önlememizi sağlar. Bunları bilgisayarınızdaki klasörler gibi düşünebilirsiniz; her klasör belirli bir projeye veya konuya ait dosyaları içerir.

İsim alanları özellikle büyük projelerde veya üçüncü taraf kütüphaneleri kullanırken sınıf adlarında çakışmaların meydana gelebileceği durumlarda kullanışlıdır.

Projenizde Araba adında bir sınıfınız olduğunu ve bunu Tasima adlı bir isim alanına yerleştirmek istediğinizi hayal edin. Bunu şu şekilde yaparsınız:

namespace Tasima;

class Araba
{
	function kornaCal()
	{
		echo 'Bip bip!';
	}
}

Araba sınıfını başka bir dosyada kullanmak isterseniz, sınıfın hangi isim alanından geldiğini belirtmeniz gerekir:

$araba = new Tasima\Araba;

Basitleştirmek için, dosyanın başında belirli bir isim alanından hangi sınıfı kullanmak istediğinizi belirtebilirsiniz, bu da tam yolu belirtme zorunluluğu olmadan örnekler oluşturmanıza olanak tanır:

use Tasima\Araba;

$araba = new Araba;

Kalıtım

Kalıtım, nesne yönelimli programlamanın bir aracıdır ve mevcut sınıflara dayanarak yeni sınıflar oluşturmaya, onların özelliklerini ve metotlarını devralmaya ve bunları ihtiyaca göre genişletmeye veya yeniden tanımlamaya olanak tanır. Kalıtım, kodun yeniden kullanılabilirliğini ve sınıfların hiyerarşisini sağlamaya olanak tanır.

Basitçe söylemek gerekirse, bir sınıfımız varsa ve ondan türetilmiş ancak birkaç değişiklikle başka bir sınıf oluşturmak istiyorsak, yeni sınıfı orijinal sınıftan “kalıtabiliriz”.

PHP'de kalıtımı extends anahtar kelimesi kullanarak gerçekleştiririz.

Kisi sınıfımız yaş bilgisini saklar. Kisi sınıfını genişleten ve eğitim alanı hakkında bilgi ekleyen başka bir Ogrenci sınıfımız olabilir.

Bir örneğe bakalım:

class Kisi
{
	private $yas;

	function __construct($yas)
	{
		$this->yas = $yas;
	}

	function bilgileriYazdir()
	{
		echo "Yaş: {$this->yas} yıl\n";
	}
}

class Ogrenci extends Kisi
{
	private $bolum;

	function __construct($yas, $bolum)
	{
		parent::__construct($yas);
		$this->bolum = $bolum;
	}

	function bilgileriYazdir()
	{
		parent::bilgileriYazdir();
		echo "Eğitim Alanı: {$this->bolum} \n";
	}
}

$ogrenci = new Ogrenci(20, 'Bilgisayar Bilimi');
$ogrenci->bilgileriYazdir();

Bu kod nasıl çalışır?

  • Kisi sınıfını genişletmek için extends anahtar kelimesini kullandık, bu da Ogrenci sınıfının Kisi sınıfından tüm metotları ve özellikleri devraldığı anlamına gelir.
  • parent:: anahtar kelimesi, üst sınıftan metotları çağırmamıza olanak tanır. Bu durumda, Ogrenci sınıfına kendi işlevselliğimizi eklemeden önce Kisi sınıfından yapıcı metodu çağırdık. Ve benzer şekilde, öğrenci hakkındaki bilgileri yazdırmadan önce atanın bilgileriYazdir() metodunu çağırdık.

Kalıtım, sınıflar arasında “bir” ilişkisi (“is-a” relationship) olduğu durumlar için tasarlanmıştır. Örneğin, Ogrenci bir Kisidir. Kedi bir hayvandır. Kodda bir nesne (örneğin, “Kisi”) beklediğimiz durumlarda, onun yerine kalıtılmış bir nesne (örneğin, “Ogrenci”) kullanma imkanı verir.

Kalıtımın ana amacının kod tekrarını önlemek olmadığını anlamak önemlidir. Aksine, kalıtımın yanlış kullanımı karmaşık ve bakımı zor koda yol açabilir. Sınıflar arasında “bir” ilişkisi yoksa, kalıtım yerine kompozisyonu düşünmeliyiz.

Kisi ve Ogrenci sınıflarındaki bilgileriYazdir() metotlarının biraz farklı bilgiler yazdırdığına dikkat edin. Ve bu metodun başka uygulamalarını sağlayacak başka sınıflar (örneğin, Calisan) ekleyebiliriz. Farklı sınıfların nesnelerinin aynı metoda farklı şekillerde yanıt verme yeteneğine polimorfizm denir:

$kisiler = [
	new Kisi(30),
	new Ogrenci(20, 'Bilgisayar Bilimi'),
	new Calisan(45, 'Müdür'),
];

foreach ($kisiler as $kisi) {
	$kisi->bilgileriYazdir();
}

Kompozisyon

Kompozisyon, başka bir sınıfın özelliklerini ve metotlarını devralmak yerine, onun örneğini kendi sınıfımızda basitçe kullandığımız bir tekniktir. Bu, karmaşık kalıtım yapıları oluşturmaya gerek kalmadan birden fazla sınıfın işlevselliğini ve özelliklerini birleştirmemizi sağlar.

Bir örneğe bakalım. Bir Motor sınıfımız ve bir Araba sınıfımız var. “Araba bir Motordur” demek yerine, “Araba bir Motora sahiptir” diyoruz, bu tipik bir kompozisyon ilişkisidir.

class Motor
{
	function calistir()
	{
		echo 'Motor çalışıyor.';
	}
}

class Araba
{
	private $motor;

	function __construct()
	{
		$this->motor = new Motor;
	}

	function baslat()
	{
		$this->motor->calistir();
		echo 'Araba sürüşe hazır!';
	}
}

$araba = new Araba;
$araba->baslat();

Burada Araba, Motorun tüm özelliklerine ve metotlarına sahip değildir, ancak $motor özelliği aracılığıyla ona erişimi vardır.

Kompozisyonun avantajı, tasarımda daha fazla esneklik ve gelecekte daha iyi değişiklik yapma imkanıdır.

Görünürlük

PHP'de, bir sınıfın özellikleri, metotları ve sabitleri için “görünürlük” tanımlayabilirsiniz. Görünürlük, bu öğelere nereden erişebileceğinizi belirler.

  1. Public: Bir öğe public olarak işaretlenmişse, bu ona sınıf dışından bile her yerden erişebileceğiniz anlamına gelir.
  2. Protected: protected olarak işaretlenmiş bir öğeye yalnızca tanımlandığı sınıf içinden ve tüm alt sınıflarından (bu sınıftan kalıtım alan sınıflar) erişilebilir.
  3. Private: Bir öğe private ise, ona yalnızca tanımlandığı sınıfın içinden erişebilirsiniz.

Görünürlüğü belirtmezseniz, PHP onu otomatik olarak public olarak ayarlar.

Örnek bir koda bakalım:

class GorunurlukGosterimi
{
	public $publicOzellik = 'Public';
	protected $protectedOzellik = 'Protected';
	private $privateOzellik = 'Private';

	public function ozellikleriYazdir()
	{
		echo $this->publicOzellik;  // Çalışır
		echo $this->protectedOzellik; // Çalışır
		echo $this->privateOzellik; // Çalışır
	}
}

$nesne = new GorunurlukGosterimi;
$nesne->ozellikleriYazdir();
echo $nesne->publicOzellik;      // Çalışır
// echo $nesne->protectedOzellik;  // Hata verir
// echo $nesne->privateOzellik;  // Hata verir

Sınıf kalıtımı ile devam edelim:

class AltSinif extends GorunurlukGosterimi
{
	public function ozellikleriYazdir()
	{
		echo $this->publicOzellik;   // Çalışır
		echo $this->protectedOzellik;  // Çalışır
		// echo $this->privateOzellik;  // Hata verir
	}
}

Bu durumda, AltSinif sınıfındaki ozellikleriYazdir() metodu public ve protected özelliklere erişebilir, ancak üst sınıfın private özelliklerine erişemez.

Veriler ve metotlar mümkün olduğunca gizli tutulmalı ve yalnızca tanımlanmış bir arayüz aracılığıyla erişilebilir olmalıdır. Bu, kodun geri kalanını etkilemeden sınıfın dahili uygulamasını değiştirmenize olanak tanır.

final Anahtar Kelimesi

PHP'de, bir sınıfın, metodun veya sabitin kalıtılmasını veya üzerine yazılmasını önlemek istiyorsak final anahtar kelimesini kullanabiliriz. Bir sınıfı final olarak işaretlediğimizde, genişletilemez. Bir metodu final olarak işaretlediğimizde, alt sınıfta üzerine yazılamaz.

Belirli bir sınıfın veya metodun daha fazla değiştirilmeyeceğini bilmek, olası çakışmalardan endişe etmeden daha kolay değişiklikler yapmamızı sağlar. Örneğin, herhangi bir alt sınıfının zaten aynı ada sahip bir metodu olacağı ve bir çakışma olacağı endişesi olmadan yeni bir metot ekleyebiliriz. Veya metodun parametrelerini değiştirebiliriz, çünkü yine alt sınıftaki üzerine yazılmış metotla bir uyumsuzluğa neden olma riski yoktur.

final class FinalSinif
{
}

// Aşağıdaki kod hata verir, çünkü final bir sınıftan kalıtım alamayız.
class FinalSinifAltSinifi extends FinalSinif
{
}

Bu örnekte, final sınıf FinalSinif'tan kalıtım alma girişimi bir hata verecektir.

Statik Özellikler ve Metotlar

PHP'de bir sınıfın “statik” öğelerinden bahsettiğimizde, sınıfın kendisine ait olan ve bu sınıfın belirli bir örneğine ait olmayan metotları ve özellikleri kastederiz. Bu, onlara erişmek için sınıfın bir örneğini oluşturmanız gerekmediği anlamına gelir. Bunun yerine, onları doğrudan sınıf adı üzerinden çağırır veya erişirsiniz.

Statik öğeler sınıfa ait olduğundan ve örneklerine ait olmadığından, statik metotlar içinde $this sözde değişkenini kullanamayacağınızı unutmayın.

Statik özelliklerin kullanılması tuzaklarla dolu anlaşılmaz koda yol açar, bu yüzden onları asla kullanmamalısınız ve burada kullanım örneğini göstermeyeceğiz. Buna karşılık, statik metotlar kullanışlıdır. Kullanım örneği:

class HesapMakinesi
{
	public static function toplama($a, $b)
	{
		return $a + $b;
	}

	public static function cikarma($a, $b)
	{
		return $a - $b;
	}
}

// Sınıfın bir örneğini oluşturmadan statik metot kullanımı
echo HesapMakinesi::toplama(5, 3); // Sonuç: 8
echo HesapMakinesi::cikarma(5, 3); // Sonuç: 2

Bu örnekte, iki statik metot içeren HesapMakinesi sınıfını oluşturduk. Bu metotları, sınıfın bir örneğini oluşturmadan doğrudan :: operatörünü kullanarak çağırabiliriz. Statik metotlar, sınıfın belirli bir örneğinin durumuna bağlı olmayan işlemler için özellikle kullanışlıdır.

Sınıf Sabitleri

Sınıflar içinde sabitler tanımlama imkanımız vardır. Sabitler, programın çalışması sırasında asla değişmeyen değerlerdir. Değişkenlerin aksine, sabitin değeri her zaman aynı kalır.

class Araba
{
	public const TekerlekSayisi = 4;

	public function tekerlekSayisiniGoster(): int
	{
		echo self::TekerlekSayisi;
	}
}

echo Araba::TekerlekSayisi;  // Çıktı: 4

Bu örnekte, TekerlekSayisi sabitine sahip Araba sınıfımız var. Sınıf içindeki sabite erişmek istediğimizde, sınıf adı yerine self anahtar kelimesini kullanabiliriz.

Nesne Arayüzleri

Nesne arayüzleri, sınıflar için “sözleşmeler” gibi çalışır. Bir sınıf bir nesne arayüzünü uygulayacaksa, bu arayüzün tanımladığı tüm metotları içermelidir. Belirli sınıfların aynı “sözleşmeye” veya yapıya uymasını sağlamanın harika bir yoludur.

PHP'de arayüzler interface anahtar kelimesiyle tanımlanır. Arayüzde tanımlanan tüm metotlar public (public)'tir. Bir sınıf bir arayüzü uyguladığında, implements anahtar kelimesini kullanır.

interface Hayvan
{
	function sesCikar();
}

class Kedi implements Hayvan
{
	public function sesCikar()
	{
		echo 'Miyav';
	}
}

$kedi = new Kedi;
$kedi->sesCikar();

Bir sınıf bir arayüzü uygularsa ancak beklenen tüm metotlar içinde tanımlanmamışsa, PHP bir hata verir.

Bir sınıf aynı anda birden fazla arayüzü uygulayabilir, bu da bir sınıfın yalnızca bir sınıftan kalıtım alabileceği kalıtımdan farklıdır:

interface Bekci
{
	function eviKoru();
}

class Kopek implements Hayvan, Bekci
{
	public function sesCikar()
	{
		echo 'Hav';
	}

	public function eviKoru()
	{
		echo 'Köpek evi dikkatlice koruyor';
	}
}

Soyut Sınıflar

Soyut sınıflar, diğer sınıflar için temel şablonlar olarak hizmet eder, ancak doğrudan örneklerini oluşturamazsınız. Tamamlanmış metotların ve içeriği tanımlanmamış soyut metotların bir kombinasyonunu içerirler. Soyut sınıflardan kalıtım alan sınıflar, atadan gelen tüm soyut metotlar için tanımlamalar sağlamalıdır.

Soyut bir sınıf tanımlamak için abstract anahtar kelimesini kullanırız.

abstract class SoyutSinif
{
	public function normalMetot()
	{
		echo 'Bu normal bir metottur';
	}

	abstract public function soyutMetot();
}

class AltSinif extends SoyutSinif
{
	public function soyutMetot()
	{
		echo 'Bu soyut metodun uygulamasıdır';
	}
}

$ornek = new AltSinif;
$ornek->normalMetot();
$ornek->soyutMetot();

Bu örnekte, bir normal ve bir soyut metot içeren soyut bir sınıfımız var. Ardından, SoyutSinif'tan kalıtım alan ve soyut metot için bir uygulama sağlayan AltSinif sınıfımız var.

Arayüzler ve soyut sınıflar aslında nasıl farklılık gösterir? Soyut sınıflar hem soyut hem de somut metotlar içerebilirken, arayüzler yalnızca bir sınıfın hangi metotları uygulaması gerektiğini tanımlar ancak herhangi bir uygulama sağlamaz. Bir sınıf yalnızca bir soyut sınıftan kalıtım alabilir, ancak istediği sayıda arayüzü uygulayabilir.

Tip Kontrolü

Programlamada, çalıştığımız verilerin doğru türde olduğundan emin olmak çok önemlidir. PHP'de bunu sağlayan araçlarımız vardır. Verilerin doğru türe sahip olup olmadığını doğrulamaya “tip kontrolü” denir.

PHP'de karşılaşabileceğimiz tipler:

  1. Temel tipler: int (tam sayılar), float (ondalıklı sayılar), bool (mantıksal değerler), string (karakter dizileri), array (diziler) ve null içerir.
  2. Sınıflar: Değerin belirli bir sınıfın örneği olmasını istiyorsak.
  3. Arayüzler: Bir sınıfın uygulaması gereken metotlar kümesini tanımlar. Arayüzü karşılayan bir değerin bu metotlara sahip olması gerekir.
  4. Karışık tipler: Bir değişkenin birden fazla izin verilen türe sahip olabileceğini belirleyebiliriz.
  5. Void: Bu özel tip, bir fonksiyonun veya metodun herhangi bir değer döndürmediğini belirtir.

Tipleri içerecek şekilde kodu nasıl düzenleyeceğimizi görelim:

class Kisi
{
	private int $yas;

	public function __construct(int $yas)
	{
		$this->yas = $yas;
	}

	public function yasiYazdir(): void
	{
		echo "Bu kişi {$this->yas} yaşında.";
	}
}

/**
 * Kisi sınıfından bir nesne alan ve kişinin yaşını yazdıran fonksiyon.
 */
function kisininYasiniYazdir(Kisi $kisi): void
{
	$kisi->yasiYazdir();
}

Bu şekilde, kodumuzun doğru türde veriler beklediğini ve bunlarla çalıştığını sağladık, bu da potansiyel hataları önlememize yardımcı olur.

Bazı tipler PHP'de doğrudan yazılamaz. Bu durumda, /** ile başlayan ve */ ile biten PHP kodunu belgelemek için standart bir format olan phpDoc yorumunda belirtilirler. Sınıfların, metotların vb. açıklamalarını eklemeye olanak tanır. Ayrıca @var, @param ve @return gibi anotasyonlar kullanarak karmaşık tipleri belirtmeye de olanak tanır. Bu tipler daha sonra statik kod analizi araçları tarafından kullanılır, ancak PHP'nin kendisi bunları kontrol etmez.

class Liste
{
	/** @var array<Kisi>  bu gösterim, Kisi nesnelerinden oluşan bir dizi olduğunu söyler */
	private array $kisiler = [];

	public function kisiEkle(Kisi $kisi): void
	{
		$this->kisiler[] = $kisi;
	}
}

Karşılaştırma ve Kimlik

PHP'de nesneleri iki şekilde karşılaştırabilirsiniz:

  1. Değer karşılaştırması ==: Nesnelerin aynı sınıftan olup olmadığını ve özelliklerinde aynı değerlere sahip olup olmadığını kontrol eder.
  2. Kimlik ===: Aynı nesne örneği olup olmadığını kontrol eder.
class Araba
{
	public string $marka;

	public function __construct(string $marka)
	{
		$this->marka = $marka;
	}
}

$araba1 = new Araba('Skoda');
$araba2 = new Araba('Skoda');
$araba3 = $araba1;

var_dump($araba1 == $araba2);   // true, çünkü aynı değere sahipler
var_dump($araba1 === $araba2);  // false, çünkü aynı örnek değiller
var_dump($araba1 === $araba3);  // true, çünkü $araba3, $araba1 ile aynı örnektir

instanceof Operatörü

instanceof operatörü, belirli bir nesnenin belirli bir sınıfın örneği, bu sınıfın bir alt sınıfının örneği olup olmadığını veya belirli bir arayüzü uygulayıp uygulamadığını belirlemeye olanak tanır.

Kisi sınıfımız ve Kisi sınıfının bir alt sınıfı olan başka bir Ogrenci sınıfımız olduğunu hayal edelim:

class Kisi
{
	private int $yas;

	public function __construct(int $yas)
	{
		$this->yas = $yas;
	}
}

class Ogrenci extends Kisi
{
	private string $bolum;

	public function __construct(int $yas, string $bolum)
	{
		parent::__construct($yas);
		$this->bolum = $bolum;
	}
}

$ogrenci = new Ogrenci(20, 'Bilgisayar Bilimi');

// $ogrenci'nin Ogrenci sınıfının bir örneği olup olmadığını kontrol etme
var_dump($ogrenci instanceof Ogrenci);  // Çıktı: bool(true)

// $ogrenci'nin Kisi sınıfının bir örneği olup olmadığını kontrol etme (çünkü Ogrenci, Kisi'nin alt sınıfıdır)
var_dump($ogrenci instanceof Kisi);     // Çıktı: bool(true)

Çıktılardan, $ogrenci nesnesinin aynı anda her iki sınıfın – Ogrenci ve Kisi – örneği olarak kabul edildiği açıktır.

Akıcı Arayüzler (Fluent Interfaces)

“Akıcı Arayüz” (İngilizce “Fluent Interface”), OOP'de metotları tek bir çağrıda birbirine zincirlemeyi sağlayan bir tekniktir. Bu genellikle kodu basitleştirir ve daha okunabilir hale getirir.

Akıcı arayüzün anahtar öğesi, zincirdeki her metodun mevcut nesneye bir referans döndürmesidir. Bunu, metodun sonunda return $this; kullanarak başarırız. Bu programlama tarzı genellikle nesnenin özelliklerinin değerlerini ayarlayan “setter” olarak adlandırılan metotlarla ilişkilidir.

E-posta gönderme örneğinde akıcı bir arayüzün nasıl görünebileceğini gösterelim:

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}

Bu örnekte, setFrom(), setRecipient() ve setMessage() metotları ilgili değerleri (gönderen, alıcı, mesaj içeriği) ayarlamaya hizmet eder. Bu değerlerin her biri ayarlandıktan sonra, metotlar bize mevcut nesneyi ($email) döndürür, bu da bir sonraki metodu ona zincirlememizi sağlar. Son olarak, e-postayı gerçekten gönderen send() metodunu çağırırız.

Akıcı arayüzler sayesinde, sezgisel ve kolay okunabilir kod yazabiliriz.

clone ile Kopyalama

PHP'de clone operatörünü kullanarak bir nesnenin kopyasını oluşturabiliriz. Bu şekilde, aynı içeriğe sahip yeni bir örnek elde ederiz.

Bir nesneyi kopyalarken bazı özelliklerini değiştirmemiz gerekirse, sınıfta özel bir __clone() metodu tanımlayabiliriz. Bu metot, nesne klonlandığında otomatik olarak çağrılır.

class Koyun
{
	public string $isim;

	public function __construct(string $isim)
	{
		$this->isim = $isim;
	}

	public function __clone()
	{
		$this->isim = 'Klon ' . $this->isim;
	}
}

$orijinal = new Koyun('Dolly');
echo $orijinal->isim . "\n";  // Yazdırır: Dolly

$klon = clone $orijinal;
echo $klon->isim . "\n";      // Yazdırır: Klon Dolly

Bu örnekte, bir $isim özelliğine sahip Koyun sınıfımız var. Bu sınıfın bir örneğini klonladığımızda, __clone() metodu klonlanmış koyunun adının “Klon” önekini almasını sağlar.

Traitler

PHP'deki traitler, sınıflar arasında metotları, özellikleri ve sabitleri paylaşmayı ve kod tekrarını önlemeyi sağlayan bir araçtır. Bunları, trait içeriğinin sınıflara “yapıştırıldığı” bir “kopyala ve yapıştır” (Ctrl-C ve Ctrl-V) mekanizması olarak düşünebilirsiniz. Bu, karmaşık sınıf hiyerarşileri oluşturmaya gerek kalmadan kodu yeniden kullanmanıza olanak tanır.

PHP'de traitleri nasıl kullanacağımıza dair basit bir örnek görelim:

trait KornaCalma
{
	public function kornaCal()
	{
		echo 'Bip bip!';
	}
}

class Araba
{
	use KornaCalma;
}

class Kamyon
{
	use KornaCalma;
}

$araba = new Araba;
$araba->kornaCal(); // 'Bip bip!' yazdırır

$kamyon = new Kamyon;
$kamyon->kornaCal(); // Ayrıca 'Bip bip!' yazdırır

Bu örnekte, bir kornaCal() metodu içeren KornaCalma adlı bir traitimiz var. Ardından, her ikisi de KornaCalma traitini kullanan iki sınıfımız var: Araba ve Kamyon. Bu sayede her iki sınıf da kornaCal() metoduna “sahiptir” ve onu her iki sınıfın nesnelerinde de çağırabiliriz.

Traitler, sınıflar arasında kodu kolayca ve verimli bir şekilde paylaşmanıza olanak tanır. Ancak kalıtım hiyerarşisine girmezler, yani $araba instanceof KornaCalma ifadesi false döndürür.

İstisnalar

OOP'deki istisnalar, kodumuzdaki hataları ve beklenmedik durumları zarif bir şekilde ele almamızı sağlar. Bunlar, hata veya olağandışı durum hakkında bilgi taşıyan nesnelerdir.

PHP'de, tüm istisnalar için temel görevi gören yerleşik bir Exception sınıfımız vardır. Bu sınıf, hata mesajı, hatanın oluştuğu dosya ve satır gibi istisna hakkında daha fazla bilgi edinmemizi sağlayan birkaç metoda sahiptir.

Kodda bir hata oluştuğunda, throw anahtar kelimesini kullanarak bir istisna “fırlatabiliriz”.

function bolme(float $a, float $b): float
{
	if ($b === 0) {
		throw new Exception('Sıfıra bölme!');
	}
	return $a / $b;
}

bolme() fonksiyonu ikinci argüman olarak sıfır aldığında, 'Sıfıra bölme!' hata mesajıyla bir istisna fırlatır. İstisna fırlatıldığında programın çökmesini önlemek için, onu bir try/catch bloğunda yakalarız:

try {
	echo bolme(10, 0);
} catch (Exception $e) {
	echo 'İstisna yakalandı: '. $e->getMessage();
}

İstisna fırlatabilecek kod try bloğuna sarılır. Eğer bir istisna fırlatılırsa, kodun yürütülmesi catch bloğuna taşınır, burada istisnayı işleyebiliriz (örneğin, hata mesajını yazdırabiliriz).

try ve catch bloklarından sonra, isteğe bağlı bir finally bloğu ekleyebiliriz, bu blok istisna fırlatılmış olsun ya da olmasın her zaman yürütülür (hatta try veya catch bloğunda return, break veya continue deyimi kullansak bile):

try {
	echo bolme(10, 0);
} catch (Exception $e) {
	echo 'İstisna yakalandı: '. $e->getMessage();
} finally {
	// İstisna fırlatılmış olsun ya da olmasın her zaman yürütülecek kod
}

Ayrıca Exception sınıfından kalıtım alan kendi istisna sınıflarımızı (hiyerarşisini) oluşturabiliriz. Örnek olarak, para yatırma ve çekme işlemlerine izin veren basit bir bankacılık uygulamasını düşünelim:

class BankaIstisnasi extends Exception {}
class YetersizBakiyeIstisnasi extends BankaIstisnasi {}
class LimitAsimiIstisnasi extends BankaIstisnasi {}

class BankaHesabi
{
	private int $bakiye = 0;
	private int $gunlukLimit = 1000;

	public function yatir(int $miktar): int
	{
		$this->bakiye += $miktar;
		return $this->bakiye;
	}

	public function cek(int $miktar): int
	{
		if ($miktar > $this->bakiye) {
			throw new YetersizBakiyeIstisnasi('Hesapta yeterli bakiye yok.');
		}

		if ($miktar > $this->gunlukLimit) {
			throw new LimitAsimiIstisnasi('Günlük para çekme limiti aşıldı.');
		}

		$this->bakiye -= $miktar;
		return $this->bakiye;
	}
}

Bir try bloğu için, farklı türde istisnalar bekliyorsanız birden fazla catch bloğu belirleyebilirsiniz.

$hesap = new BankaHesabi;
$hesap->yatir(500);

try {
	$hesap->cek(1500);
} catch (LimitAsimiIstisnasi $e) {
	echo $e->getMessage();
} catch (YetersizBakiyeIstisnasi $e) {
	echo $e->getMessage();
} catch (BankaIstisnasi $e) {
	echo 'İşlem gerçekleştirilirken bir hata oluştu.';
}

Bu örnekte, catch bloklarının sırasına dikkat etmek önemlidir. Çünkü tüm istisnalar BankaIstisnasi'ndan kalıtım alır, eğer bu bloğu ilk sıraya koysaydık, tüm istisnalar kod sonraki catch bloklarına ulaşamadan bu blokta yakalanırdı. Bu nedenle, daha spesifik istisnaları (yani diğerlerinden kalıtım alanları) catch bloğunda üst sınıflarından daha önce sıralamak önemlidir.

Yineleme (Iteration)

PHP'de, dizilerde dolaştığınız gibi foreach döngüsünü kullanarak nesneler üzerinde de dolaşabilirsiniz. Bunun çalışması için nesnenin özel bir arayüz uygulaması gerekir.

İlk seçenek, mevcut değeri döndüren current(), anahtarı döndüren key(), bir sonraki değere geçen next(), başa dönen rewind() ve henüz sonda olup olmadığımızı kontrol eden valid() metotlarına sahip Iterator arayüzünü uygulamaktır.

İkinci seçenek, yalnızca tek bir getIterator() metoduna sahip olan IteratorAggregate arayüzünü uygulamaktır. Bu metot ya dolaşımı sağlayacak bir yer tutucu nesne döndürür ya da anahtarları ve değerleri kademeli olarak döndürmek için yield kullanılan özel bir fonksiyon olan bir jeneratör (generator) temsil edebilir:

class Kisi
{
	public function __construct(
		public int $yas,
	) {
	}
}

class Liste implements IteratorAggregate
{
	private array $kisiler = [];

	public function kisiEkle(Kisi $kisi): void
	{
		$this->kisiler[] = $kisi;
	}

	public function getIterator(): Generator
	{
		foreach ($this->kisiler as $kisi) {
			yield $kisi;
		}
	}
}

$liste = new Liste;
$liste->kisiEkle(new Kisi(30));
$liste->kisiEkle(new Kisi(25));

foreach ($liste as $kisi) {
	echo "Yaş: {$kisi->yas} yıl \n";
}

İyi Uygulamalar (Best practices)

Nesne yönelimli programlamanın temel prensiplerini öğrendikten sonra, OOP'deki iyi uygulamalara odaklanmak önemlidir. Bunlar, yalnızca işlevsel değil, aynı zamanda okunabilir, anlaşılır ve bakımı kolay kod yazmanıza yardımcı olacaktır.

  1. Sorumlulukların Ayrılması (Separation of Concerns): Her sınıfın açıkça tanımlanmış bir sorumluluğu olmalı ve yalnızca bir ana görevi çözmelidir. Eğer bir sınıf çok fazla şey yapıyorsa, onu daha küçük, uzmanlaşmış sınıflara bölmek uygun olabilir.
  2. Kapsülleme (Encapsulation): Veriler ve metotlar mümkün olduğunca gizli tutulmalı ve yalnızca tanımlanmış bir arayüz aracılığıyla erişilebilir olmalıdır. Bu, kodun geri kalanını etkilemeden sınıfın dahili uygulamasını değiştirmenize olanak tanır.
  3. Bağımlılık Enjeksiyonu (Dependency Injection): Bağımlılıkları doğrudan sınıf içinde oluşturmak yerine, onları dışarıdan “enjekte etmelisiniz”. Bu prensibi daha derinlemesine anlamak için Bağımlılık Enjeksiyonu bölümlerini öneririz.
versiyon: 4.0