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çin extends anahtar sözcüğünü kullandık, yani Student sınıfı tüm yöntemleri ve özellikleri Person 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 önce Person sınıfından kurucuyu çağırdık. Ve benzer şekilde, öğrenci bilgilerini listelemeden önce printInformation() 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.

  1. 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.
  2. 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.
  3. Ö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:

  1. Temel tipler: Bunlar int (tam sayılar), float (kayan noktalı sayılar), bool (boolean değerler), string (dizeler), array (diziler) ve null içerir.
  2. Sınıflar: Bir değerin belirli bir sınıfın örneği olmasını istediğimizde.
  3. 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.
  4. Karışık tipler: Bir değişkenin birden fazla izin verilen türe sahip olabileceğini belirtebiliriz.
  5. 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:

  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 ===: 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 BankingExceptiondan 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.

  1. İ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.
  2. 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.
  3. 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.
versiyon: 4.0