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çinextends
anahtar kelimesini kullandık, bu daOgrenci
sınıfınınKisi
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 önceKisi
sınıfından yapıcı metodu çağırdık. Ve benzer şekilde, öğrenci hakkındaki bilgileri yazdırmadan önce atanınbilgileriYazdir()
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 Kisi
dir. 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
, Motor
un 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.
- Public: Bir öğe
public
olarak işaretlenmişse, bu ona sınıf dışından bile her yerden erişebileceğiniz anlamına gelir. - 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. - 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:
- Temel tipler:
int
(tam sayılar),float
(ondalıklı sayılar),bool
(mantıksal değerler),string
(karakter dizileri),array
(diziler) venull
içerir. - Sınıflar: Değerin belirli bir sınıfın örneği olmasını istiyorsak.
- 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.
- Karışık tipler: Bir değişkenin birden fazla izin verilen türe sahip olabileceğini belirleyebiliriz.
- 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:
- 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. - 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.
- 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.
- 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.
- 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.