Nesne Yönelimli Programlamaya Giriş
“OOP” terimi, kodu organize etmenin ve yapılandırmanın bir yolu olan Nesne Yönelimli Programlama anlamına gelir. OOP, bir programı bir dizi komut ve işlev yerine birbirleriyle iletişim kuran nesneler topluluğu olarak görmemizi sağlar.
OOP'de “nesne”, veri ve bu veri üzerinde çalışan fonksiyonları içeren bir birimdir. Nesneler, nesneler için planlar veya şablonlar olarak anlaşılabilecek “sınıflar” temelinde oluşturulur. Bir sınıfa sahip olduğumuzda, o sınıftan yapılmış belirli bir nesne olan “örneğini” oluşturabiliriz.
PHP'de basit bir sınıfı nasıl oluşturabileceğimize bakalım. Bir sınıfı tanımlarken, “class” anahtar sözcüğünü, ardından sınıf adını ve ardından sınıfın işlevlerini (“yöntemler” olarak adlandırılır) ve sınıf değişkenlerini (“özellikler” veya “nitelikler” olarak adlandırılır) çevreleyen küme parantezlerini kullanırız:
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
Bu örnekte, honk
adında bir işlevi (veya “yöntemi”) olan Car
adında bir sınıf
oluşturduk.
Her sınıf sadece bir ana görevi çözmelidir. 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, kodu düzenli tutmak ve gezinmeyi kolaylaştırmak için genellikle ayrı dosyalarda saklanır. Dosya adı sınıf
adıyla eşleşmelidir, bu nedenle Car
sınıfı için dosya adı Car.php
olacaktır.
Sınıfları adlandırırken, “PascalCase” kuralını takip etmek iyidir, yani isimdeki her kelime büyük harfle başlar ve alt çizgi veya başka ayırıcılar yoktur. Yöntemler ve özellikler “camelCase” kuralını takip eder, yani küçük harfle başlarlar.
PHP'de bazı yöntemlerin özel rolleri vardır ve ön ekleri __
(iki alt çizgi) ile gösterilir. En önemli
özel yöntemlerden biri __construct
olarak etiketlenen "kurucu "dur. Yapıcı, bir sınıfın yeni bir örneğini
oluştururken otomatik olarak çağrılan bir yöntemdir.
Yapıcıyı genellikle bir nesnenin başlangıç durumunu ayarlamak için kullanırız. Örneğin, bir kişiyi temsil eden bir nesne oluştururken, yaşı, adı veya diğer nitelikleri ayarlamak için kurucuyu kullanabilirsiniz.
PHP'de bir yapıcının nasıl kullanılacağını görelim:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
Bu örnekte, Person
sınıfının bir özelliği (değişkeni) $age
ve bu özelliği ayarlayan bir
kurucusu vardır. Daha sonra howOldAreYou()
yöntemi kişinin yaşına erişim sağlar.
$this
sözde değişkeni, nesnenin özelliklerine ve yöntemlerine erişmek için sınıf içinde
kullanılır.
new
anahtar sözcüğü 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.
Bir nesne oluştururken belirtilmemişlerse, kurucu parametreleri için varsayılan değerler de ayarlayabilirsiniz. Örneğin:
class Person
{
private $age;
function __construct($age = 20)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person; // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
Bu örnekte, bir Person
nesnesi oluştururken bir yaş belirtmezseniz, varsayılan değer olan
20 kullanılacaktır.
Güzel olan şey, yapıcı aracılığıyla başlatılmasıyla birlikte özellik tanımının bu şekilde kısaltılabilmesi ve basitleştirilebilmesidir:
class Person
{
function __construct(
private $age = 20,
) {
}
}
Bütünlük için, kuruculara ek olarak, nesneler bellekten serbest bırakılmadan önce çağrılan yıkıcılara (yöntem
__destruct
) sahip olabilir.
İsim Alanları
Ad alanları, adlandırma çakışmalarını önlerken ilgili sınıfları, işlevleri ve sabitleri düzenlememize ve gruplandırmamıza olanak tanır. Bunları, her klasörün belirli bir proje veya konuyla ilgili dosyaları içerdiği bir bilgisayardaki klasörler gibi düşünebilirsiniz.
Ad alanları özellikle büyük projelerde veya sınıf adlandırma çakışmalarının ortaya çıkabileceği üçüncü taraf kütüphaneleri kullanırken kullanışlıdır.
Projenizde Car
adında bir sınıfınız olduğunu ve bunu Transport
adında bir ad alanına
yerleştirmek istediğinizi düşünün. Bunu şu şekilde yaparsınız:
namespace Transport;
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
Car
sınıfını başka bir dosyada kullanmak istiyorsanız, sınıfın hangi ad alanından geldiğini belirtmeniz
gerekir:
$car = new Transport\Car;
Basitleştirmek için, dosyanın başında belirli bir ad alanından hangi sınıfı kullanmak istediğinizi belirtebilir, böylece tam yolu belirtmeden örnekler oluşturabilirsiniz:
use Transport\Car;
$car = new Car;
Kalıtım
Kalıtım, mevcut sınıflara dayalı olarak yeni sınıfların oluşturulmasına, özelliklerinin ve yöntemlerinin miras alınmasına ve gerektiğinde genişletilmesine veya yeniden tanımlanmasına olanak tanıyan bir nesne yönelimli programlama aracıdır. Kalıtım, kodun yeniden kullanılabilirliğini ve sınıf hiyerarşisini sağlar.
Basitçe söylemek gerekirse, bir sınıfımız varsa ve ondan türetilmiş ancak bazı değişikliklerle başka bir sınıf oluşturmak istiyorsak, yeni sınıfı orijinal sınıftan “miras” alabiliriz.
PHP'de kalıtım extends
anahtar sözcüğü kullanılarak gerçekleştirilir.
Person
sınıfımız yaş bilgisini saklar. Person
'i genişleten ve çalışma alanı hakkında
bilgi ekleyen Student
adlı başka bir sınıfımız olabilir.
Bir örneğe bakalım:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function printInformation()
{
echo "Age: {$this->age} years\n";
}
}
class Student extends Person
{
private $fieldOfStudy;
function __construct($age, $fieldOfStudy)
{
parent::__construct($age);
$this->fieldOfStudy = $fieldOfStudy;
}
function printInformation()
{
parent::printInformation();
echo "Field of study: {$this->fieldOfStudy} \n";
}
}
$student = new Student(20, 'Computer Science');
$student->printInformation();
Bu kod nasıl çalışıyor?
Person
sınıfını genişletmek içinextends
anahtar sözcüğünü kullandık, yaniStudent
sınıfı tüm yöntemleri ve özellikleriPerson
sınıfından devralır.parent::
anahtar sözcüğü, üst sınıftaki yöntemleri çağırmamızı sağlar. Bu durumda,Student
sınıfına kendi işlevselliğimizi eklemeden öncePerson
sınıfından kurucuyu çağırdık. Ve benzer şekilde, öğrenci bilgilerini listelemeden önceprintInformation()
ata yöntemini çağırdık.
Kalıtım, sınıflar arasında “is a” ilişkisinin olduğu durumlar içindir. Örneğin, bir Student
bir
Person
. Bir kedi bir hayvandır. Kodda bir nesne (örn. “Kişi”) beklediğimiz durumlarda bunun yerine
türetilmiş bir nesne (örn. “Öğrenci”) kullanmamızı sağlar.
Kalıtımın birincil amacının kod tekrarını önlemek olmadığının farkına varmak çok önemlidir. Aksine, kalıtımın yanlış kullanımı karmaşık ve bakımı zor kodlara yol açabilir. Sınıflar arasında “is a” ilişkisi yoksa, kalıtım yerine bileşimi düşünmeliyiz.
Person
ve Student
sınıflarındaki printInformation()
yöntemlerinin biraz farklı
bilgiler verdiğine dikkat edin. Ve bu yöntemin başka uygulamalarını sağlayacak başka sınıflar ( Employee
gibi) ekleyebiliriz. Farklı sınıflardaki nesnelerin aynı yönteme farklı şekillerde yanıt verebilmesine çok
biçimlilik denir:
$people = [
new Person(30),
new Student(20, 'Computer Science'),
new Employee(45, 'Director'),
];
foreach ($people as $person) {
$person->printInformation();
}
Kompozisyon
Kompozisyon, başka bir sınıftan özellikleri ve yöntemleri miras almak yerine, sınıfımızda onun örneğini kullandığımız bir tekniktir. Bu, karmaşık kalıtım yapıları oluşturmadan birden fazla sınıfın işlevlerini ve özelliklerini birleştirmemizi sağlar.
Örneğin, bir Engine
sınıfımız ve bir Car
sınıfımız var. “Bir araba bir motordur” demek
yerine, tipik bir bileşim ilişkisi olan “Bir arabanın motoru vardır” deriz.
class Engine
{
function start()
{
echo 'Engine is running.';
}
}
class Car
{
private $engine;
function __construct()
{
$this->engine = new Engine;
}
function start()
{
$this->engine->start();
echo 'The car is ready to drive!';
}
}
$car = new Car;
$car->start();
Burada, Car
, Engine
'un tüm özelliklerine ve yöntemlerine sahip değildir, ancak
$engine
özelliği aracılığıyla bunlara erişebilir.
Kompozisyonun avantajı, daha fazla tasarım esnekliği ve gelecekteki değişiklikler için daha iyi uyarlanabilirliktir.
Görünürlük
PHP'de sınıf özellikleri, yöntemleri ve sabitleri için “görünürlük” tanımlayabilirsiniz. Görünürlük, bu öğelere nereden erişebileceğinizi belirler.
- Public: Eğer bir eleman
public
olarak işaretlenmişse, bu ona her yerden, hatta sınıf dışından bile erişebileceğiniz anlamına gelir. - Korumalı:
protected
olarak işaretlenmiş bir öğeye yalnızca sınıf ve tüm torunları (ondan miras alan sınıflar) içinde erişilebilir. - Özel: Bir eleman
private
ise, ona yalnızca tanımlandığı sınıf içinden erişebilirsiniz.
Görünürlük belirtmezseniz, PHP bunu otomatik olarak public
olarak ayarlayacaktır.
Örnek bir koda bakalım:
class VisibilityExample
{
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
echo $this->privateProperty; // Works
}
}
$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty; // Works
// echo $object->protectedProperty; // Throws an error
// echo $object->privateProperty; // Throws an error
Sınıf kalıtımına devam ediyoruz:
class ChildClass extends VisibilityExample
{
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
// echo $this->privateProperty; // Throws an error
}
}
Bu durumda, ChildClass
adresindeki printProperties()
yöntemi public ve protected özelliklere
erişebilir ancak ana sınıfın private özelliklerine erişemez.
Veri ve yöntemler mümkün olduğunca gizli olmalı ve sadece tanımlanmış bir arayüz üzerinden erişilebilir olmalıdır. Bu, kodun geri kalanını etkilemeden sınıfın dahili uygulamasını değiştirmenize olanak tanır.
Son Anahtar Kelime
PHP'de, bir sınıfın, yöntemin veya sabitin miras alınmasını veya geçersiz kılınmasını önlemek istiyorsak
final
anahtar sözcüğünü kullanabiliriz. Bir sınıf final
olarak işaretlendiğinde
genişletilemez. Bir yöntem final
olarak işaretlendiğinde, bir alt sınıfta geçersiz kılınamaz.
Belirli bir sınıf veya yöntemin artık değiştirilmeyeceğinin farkında olmak, olası çakışmalar konusunda endişelenmeden daha kolay değişiklik yapmamızı sağlar. Örneğin, bir alt sınıfın zaten aynı isimde bir metoda sahip olmasından ve bunun bir çakışmaya yol açmasından korkmadan yeni bir metot ekleyebiliriz. Ya da bir yöntemin parametrelerini, yine alttaki bir yöntemde geçersiz kılınmış bir yöntemle tutarsızlığa neden olma riski olmadan değiştirebiliriz.
final class FinalClass
{
}
// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
Bu örnekte, FinalClass
nihai sınıfından miras almaya çalışmak bir hatayla sonuçlanacaktır.
Statik Özellikler ve Yöntemler
PHP'de bir sınıfın “statik” öğelerinden bahsettiğimizde, sınıfın belirli bir örneğine değil, sınıfın kendisine ait olan yöntemleri ve özellikleri kastediyoruz. Bu, onlara erişmek için sınıfın bir örneğini oluşturmanız gerekmediği anlamına gelir. Bunun yerine, bunları doğrudan sınıf adı üzerinden çağırır veya bunlara erişirsiniz.
Statik öğeler örneklere değil sınıfa ait olduğundan, statik yöntemlerin içinde $this
sözde değişkenini
kullanamayacağınızı unutmayın.
Statik özelliklerin kullanılması tuzaklarla dolu karmaşık kodlara yol açar, bu nedenle bunları asla kullanmamalısınız ve burada bir örnek göstermeyeceğiz. Öte yandan, statik yöntemler kullanışlıdır. İşte bir örnek:
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
public static function subtract($a, $b)
{
return $a - $b;
}
}
// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
Bu örnekte, iki statik yöntemi olan bir Calculator
sınıfı oluşturduk. Bu yöntemleri, ::
operatörünü kullanarak sınıfın bir örneğini oluşturmadan doğrudan çağırabiliriz. Statik yöntemler özellikle belirli
bir sınıf örneğinin durumuna bağlı olmayan işlemler için kullanışlıdır.
Sınıf Sabitleri
Sınıflar içinde sabitler tanımlama seçeneğimiz vardır. Sabitler, programın yürütülmesi sırasında asla değişmeyen değerlerdir. Değişkenlerin aksine, bir sabitin değeri aynı kalır.
class Car
{
public const NumberOfWheels = 4;
public function displayNumberOfWheels(): int
{
echo self::NumberOfWheels;
}
}
echo Car::NumberOfWheels; // Output: 4
Bu örnekte, NumberOfWheels
sabitine sahip bir Car
sınıfımız var. Sınıf içindeki sabite
erişirken, sınıf adı yerine self
anahtar sözcüğünü kullanabiliriz.
Nesne Arayüzleri
Nesne arayüzleri sınıflar için “sözleşme” görevi görür. Bir sınıf bir nesne arayüzünü uygulayacaksa, arayüzün tanımladığı tüm yöntemleri içermelidir. Bu, belirli sınıfların aynı “sözleşmeye” veya yapıya bağlı kalmasını sağlamanın harika bir yoludur.
PHP'de arayüzler interface
anahtar sözcüğü kullanılarak tanımlanır. Bir arayüzde tanımlanan tüm
yöntemler public'tir (public
). Bir sınıf bir arayüzü uyguladığında, implements
anahtar
sözcüğünü kullanır.
interface Animal
{
function makeSound();
}
class Cat implements Animal
{
public function makeSound()
{
echo 'Meow';
}
}
$cat = new Cat;
$cat->makeSound();
Eğer bir sınıf bir arayüzü uyguluyor, ancak beklenen tüm yöntemler tanımlanmamışsa, PHP bir hata verir.
Bir sınıf aynı anda birden fazla arayüzü uygulayabilir; bu, bir sınıfın yalnızca bir sınıftan miras alabildiği kalıtımdan farklıdır:
interface Guardian
{
function guardHouse();
}
class Dog implements Animal, Guardian
{
public function makeSound()
{
echo 'Bark';
}
public function guardHouse()
{
echo 'Dog diligently guards the house';
}
}
Soyut Sınıflar
Soyut sınıflar diğer sınıflar için temel şablon görevi görür, ancak örneklerini doğrudan oluşturamazsınız. Tam yöntemler ve tanımlanmış bir içeriği olmayan soyut yöntemlerin bir karışımını içerirler. Soyut sınıflardan miras alan sınıflar, üst sınıftaki tüm soyut yöntemler için tanımlar sağlamalıdır.
Soyut bir sınıf tanımlamak için abstract
anahtar sözcüğünü kullanırız.
abstract class AbstractClass
{
public function regularMethod()
{
echo 'This is a regular method';
}
abstract public function abstractMethod();
}
class Child extends AbstractClass
{
public function abstractMethod()
{
echo 'This is the implementation of the abstract method';
}
}
$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
Bu örnekte, bir normal ve bir soyut yöntemi olan soyut bir sınıfımız var. Ardından, AbstractClass
adresinden miras alan ve soyut yöntem için bir uygulama sağlayan bir Child
sınıfımız var.
Arayüzler ve soyut sınıflar nasıl farklıdır? Soyut sınıflar hem soyut hem de somut yöntemler içerebilirken, arayüzler yalnızca sınıfın hangi yöntemleri uygulaması gerektiğini tanımlar, ancak hiçbir uygulama sağlamaz. Bir sınıf yalnızca bir soyut sınıftan miras alabilir, ancak herhangi bir sayıda arayüzü uygulayabilir.
Tip Kontrolü
Programlamada, birlikte çalıştığımız verilerin doğru türde olduğundan emin olmak çok önemlidir. PHP'de bu güvenceyi sağlayan araçlarımız vardır. Verilerin doğru türde olduğunu doğrulamaya “tür denetimi” denir.
PHP'de karşılaşabileceğimiz türler:
- Temel tipler: Bunlar
int
(tam sayılar),float
(kayan noktalı sayılar),bool
(boolean değerler),string
(dizeler),array
(diziler) venull
içerir. - Sınıflar: Bir değerin belirli bir sınıfın örneği olmasını istediğimizde.
- Arayüzler: Bir sınıfın uygulaması gereken bir dizi yöntemi tanımlar. Bir arayüzü karşılayan bir değer bu yöntemlere sahip olmalıdır.
- Karışık tipler: Bir değişkenin birden fazla izin verilen türe sahip olabileceğini belirtebiliriz.
- Void: Bu özel tip, bir fonksiyon veya metodun herhangi bir değer döndürmediğini belirtir.
Türleri dahil etmek için kodu nasıl değiştireceğimizi görelim:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function printAge(): void
{
echo "This person is {$this->age} years old.";
}
}
/**
* A function that accepts a Person object and prints the person's age.
*/
function printPersonAge(Person $person): void
{
$person->printAge();
}
Bu şekilde, kodumuzun doğru türdeki verileri beklediğinden ve bunlarla çalıştığından emin olarak olası hataları önlememize yardımcı oluruz.
Bazı türler PHP'de doğrudan yazılamaz. Bu durumda, PHP kodunu belgelemek için standart biçim olan phpDoc yorumunda
listelenirler, /**
ile başlar ve */
ile biter. Sınıfların, yöntemlerin vb. açıklamalarını
eklemenize olanak tanır. Ayrıca @var
, @param
ve @return
ek açıklamalarını kullanarak
karmaşık türleri listelemenizi sağlar. Bu tipler daha sonra statik kod analiz araçları tarafından kullanılır, ancak
PHP'nin kendisi tarafından kontrol edilmez.
class Registry
{
/** @var array<Person> indicates that it's an array of Person objects */
private array $persons = [];
public function addPerson(Person $person): void
{
$this->persons[] = $person;
}
}
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
===
: Nesnenin aynı örneği olup olmadığını kontrol eder.
class Car
{
public string $brand;
public function __construct(string $brand)
{
$this->brand = $brand;
}
}
$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;
var_dump($car1 == $car2); // true, because they have the same value
var_dump($car1 === $car2); // false, because they are not the same instance
var_dump($car1 === $car3); // true, because $car3 is the same instance as $car1
instanceof
Operatör
instanceof
operatörü, belirli bir nesnenin belirli bir sınıfın örneği olup olmadığını, bu sınıfın
soyundan gelip gelmediğini veya belirli bir arayüzü uygulayıp uygulamadığını belirlemenizi sağlar.
Bir Person
sınıfımız ve Person
sınıfının soyundan gelen başka bir Student
sınıfımız olduğunu düşünün:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
}
class Student extends Person
{
private string $major;
public function __construct(int $age, string $major)
{
parent::__construct($age);
$this->major = $major;
}
}
$student = new Student(20, 'Computer Science');
// Check if $student is an instance of the Student class
var_dump($student instanceof Student); // Output: bool(true)
// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person); // Output: bool(true)
Çıktılardan, $student
nesnesinin hem Student
hem de Person
sınıflarının bir
örneği olarak kabul edildiği açıktır.
Akıcı Arayüzler
“Akıcı Arayüz”, OOP'de yöntemlerin tek bir çağrıda bir araya getirilmesini sağlayan bir tekniktir. Bu genellikle kodu basitleştirir ve netleştirir.
Akıcı bir arayüzün temel unsuru, zincirdeki her yöntemin geçerli nesneye bir referans döndürmesidir. Bu, yöntemin
sonunda return $this;
kullanılarak gerçekleştirilir. Bu programlama tarzı genellikle bir nesnenin özelliklerinin
değerlerini ayarlayan “setter” adı verilen yöntemlerle ilişkilendirilir.
E-posta göndermek için akıcı bir arayüzün nasıl görünebileceğini görelim:
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()
yöntemleri ilgili değerleri
(gönderen, alıcı, mesaj içeriği) ayarlamak için kullanılır. Bu değerlerin her birini ayarladıktan sonra, yöntemler
geçerli nesneyi ($email
) döndürür, böylece ondan sonra başka bir yöntemi zincirlememize izin verir. Son
olarak, e-postayı gerçekten gönderen send()
yöntemini çağırıyoruz.
Akıcı arayüzler sayesinde sezgisel ve kolay okunabilir kodlar yazabiliyoruz.
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 gerekiyorsa, sınıfta özel bir __clone()
yöntemi
tanımlayabiliriz. Nesne klonlandığında bu yöntem otomatik olarak çağrılır.
class Sheep
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __clone()
{
$this->name = 'Clone of ' . $this->name;
}
}
$original = new Sheep('Dolly');
echo $original->name . "\n"; // Outputs: Dolly
$clone = clone $original;
echo $clone->name . "\n"; // Outputs: Clone of Dolly
Bu örnekte, bir özelliği $name
olan bir Sheep
sınıfımız var. Bu sınıfın bir örneğini
klonladığımızda, __clone()
yöntemi klonlanan koyunun adının “Clone of” ön ekini almasını sağlar.
Özellikler
PHP'deki özellikler, sınıflar arasında yöntemlerin, özelliklerin ve sabitlerin paylaşılmasını sağlayan ve kod tekrarını önleyen bir araçtır. Bunları, bir özelliğin içeriğinin sınıflara “yapıştırıldığı” bir “kopyala ve yapıştır” mekanizması (Ctrl-C ve Ctrl-V) olarak düşünebilirsiniz. Bu, karmaşık sınıf hiyerarşileri oluşturmak zorunda kalmadan kodu yeniden kullanmanıza olanak tanır.
PHP'de özelliklerin nasıl kullanılacağına dair basit bir örneğe göz atalım:
trait Honking
{
public function honk()
{
echo 'Beep beep!';
}
}
class Car
{
use Honking;
}
class Truck
{
use Honking;
}
$car = new Car;
$car->honk(); // Outputs 'Beep beep!'
$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
Bu örnekte, bir yöntem içeren Honking
adlı bir özelliğimiz var honk()
. Sonra iki sınıfımız
var: Car
ve Truck
, her ikisi de Honking
özelliğini kullanıyor. Sonuç olarak, her iki
sınıf da honk()
yöntemine “sahiptir” ve bu yöntemi her iki sınıfın nesneleri üzerinde
çağırabiliriz.
Özellikler, sınıflar arasında kolay ve verimli bir şekilde kod paylaşımı yapmanızı sağlar. Kalıtım hiyerarşisine
girmezler, yani $car instanceof Honking
, false
döndürür.
İstisnalar
OOP'deki istisnalar, kodumuzdaki hataları ve beklenmedik durumları zarif bir şekilde ele almamızı sağlar. Bunlar, bir hata veya olağandışı durum hakkında bilgi taşıyan nesnelerdir.
PHP'de, tüm istisnalar için temel teşkil eden yerleşik bir Exception
sınıfımız vardır. Bunun, hata
mesajı, hatanın oluştuğu dosya ve satır gibi istisna hakkında daha fazla bilgi almamızı sağlayan çeşitli yöntemleri
vardır.
Kodda bir hata oluştuğunda, throw
anahtar sözcüğünü kullanarak istisnayı “atabiliriz”.
function division(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Division by zero!');
}
return $a / $b;
}
division()
fonksiyonu ikinci argüman olarak null aldığında, 'Division by zero!'
hata mesajını
içeren bir istisna fırlatır. İstisna fırlatıldığında programın çökmesini önlemek için, istisnayı
try/catch
bloğuna hapsediyoruz:
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
}
İstisna fırlatabilen kod bir blok try
içine sarılır. İstisna atılırsa, kod yürütmesi istisnayı ele
alabileceğimiz (örneğin, bir hata mesajı yazabileceğimiz) bir bloğa catch
taşınır.
try
ve catch
bloklarından sonra, istisna atılsa da atılmasa da her zaman çalıştırılan isteğe
bağlı bir finally
bloğu ekleyebiliriz ( try
veya catch
bloğunda return
,
break
veya continue
kullansak bile):
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
} finally {
// Code that is always executed whether the exception has been thrown or not
}
Exception sınıfından miras alan kendi istisna sınıflarımızı (hiyerarşi) da oluşturabiliriz. Örnek olarak, para yatırma ve çekme işlemlerine izin veren basit bir bankacılık uygulaması düşünün:
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}
class BankAccount
{
private int $balance = 0;
private int $dailyLimit = 1000;
public function deposit(int $amount): int
{
$this->balance += $amount;
return $this->balance;
}
public function withdraw(int $amount): int
{
if ($amount > $this->balance) {
throw new InsufficientFundsException('Not enough funds in the account.');
}
if ($amount > $this->dailyLimit) {
throw new ExceededLimitException('Daily withdrawal limit exceeded.');
}
$this->balance -= $amount;
return $this->balance;
}
}
Farklı türde istisnalar bekliyorsanız, tek bir try
bloğu için birden fazla catch
bloğu
belirtilebilir.
$account = new BankAccount;
$account->deposit(500);
try {
$account->withdraw(1500);
} catch (ExceededLimitException $e) {
echo $e->getMessage();
} catch (InsufficientFundsException $e) {
echo $e->getMessage();
} catch (BankingException $e) {
echo 'An error occurred during the operation.';
}
Bu örnekte, catch
bloklarının sırasına dikkat etmek önemlidir. Tüm istisnalar
BankingException
dan miras alındığından, bu bloğu ilk olarak kullansaydık, kod sonraki catch
bloklarına ulaşmadan tüm istisnalar bu blokta yakalanırdı. Bu nedenle, daha spesifik istisnaların (yani diğerlerinden miras
kalanların) catch
bloğu sıralamasında ana istisnalardan daha üstte olması önemlidir.
Yinelemeler
PHP'de, foreach
döngüsünü kullanarak, bir dizi içinde döngü yapar gibi nesneler arasında döngü
yapabilirsiniz. Bunun çalışması için nesnenin özel bir arayüz uygulaması gerekir.
İlk seçenek, current()
mevcut değeri döndüren, key()
anahtarı döndüren, next()
bir sonraki değere geçen, rewind()
başa geçen ve valid()
henüz sona gelip gelmediğimizi kontrol
eden yöntemlere sahip Iterator
arayüzünü uygulamaktır.
Diğer seçenek ise sadece bir metodu olan IteratorAggregate
arayüzünü uygulamaktır
getIterator()
. Bu, çapraz geçişi sağlayacak bir yer tutucu nesne döndürür ya da anahtarları ve değerleri
sırayla döndürmek için yield
kullanan özel bir işlev olan bir üreteç olabilir:
class Person
{
public function __construct(
public int $age,
) {
}
}
class Registry implements IteratorAggregate
{
private array $people = [];
public function addPerson(Person $person): void
{
$this->people[] = $person;
}
public function getIterator(): Generator
{
foreach ($this->people as $person) {
yield $person;
}
}
}
$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));
foreach ($list as $person) {
echo "Age: {$person->age} years\n";
}
En İyi Uygulamalar
Nesne yönelimli programlamanın temel ilkelerini öğrendikten sonra, OOP'deki en iyi uygulamalara odaklanmak çok önemlidir. Bunlar yalnızca işlevsel değil aynı zamanda okunabilir, anlaşılabilir ve bakımı kolay kodlar yazmanıza yardımcı olacaktır.
- İlgilerin Ayrılması: Her sınıfın açıkça tanımlanmış bir sorumluluğu olmalı ve yalnızca bir birincil görevi ele almalıdır. Bir sınıf çok fazla şey yapıyorsa, onu daha küçük, uzmanlaşmış sınıflara bölmek uygun olabilir.
- Kapsülleme: Veri ve yöntemler mümkün olduğunca gizli olmalı ve yalnızca tanımlanmış bir arayüz aracılığıyla erişilebilir olmalıdır. Bu, kodun geri kalanını etkilemeden bir sınıfın dahili uygulamasını değiştirmenize olanak tanır.
- Dependency Injection: Bağımlılıkları doğrudan bir sınıfın içinde oluşturmak yerine, onları dışarıdan “enjekte etmelisiniz”. Bu prensibi daha iyi anlamak için Bağımlılık Enjeksiyonu bölümünü okumanızı tavsiye ederiz.