
PHP'de Kalıtım, Arayüz ve Trait: Kod Paylaşmanın Üç Yolu
Kalıtım, arayüz ve trait sık sık birbirinin yerine kullanılır. Üçü de kod tekrarını azaltır, ancak çözdükleri sorun aynı değildir. Kalıtım bir tür ilişkisi kurar, arayüz bir sözleşme tanımlar, trait ise yalnızca kod kopyalamanın dille desteklenmiş biçimidir. Yanlış mekanizma seçildiğinde ortaya çıkan bağımlılık, ilerleyen aylarda değişimi engelleyen bir yapıya dönüşür.
Bu bölümde üç aracın sözdizimi, davranış farkları ve seçim ölçütleri ele alınıyor.
Kalıtım
Kalıtım, alt sınıfın üst sınıfın tüm görünür üyelerini devralmasını sağlar. Üst sınıfın metodu alt sınıfta yeniden tanımlanabilir; asıl davranışa parent anahtar sözcüğüyle ulaşılır.
abstract class Bildirim
{
public function __construct(protected readonly string $alici) {}
abstract public function gonder(string $mesaj): void; // alt sınıf yazmak zorunda
protected function kayitTut(string $mesaj): void
{
error_log(sprintf('[%s] %s', $this->alici, $mesaj));
}
}
final class EpostaBildirimi extends Bildirim
{
public function gonder(string $mesaj): void
{
mail($this->alici, 'Bildirim', $mesaj);
$this->kayitTut($mesaj);
}
}Soyut sınıf doğrudan örneklenemez ve içinde gövdesi olmayan metotlar bulundurabilir. Ortak davranışın bir bölümü paylaşılacak, geri kalanı alt sınıfa bırakılacaksa uygun araçtır. Genişletilmesi istenmeyen sınıflar final ile işaretlenir; bu, ileride yapılacak değişikliklerde beklenmedik alt sınıf davranışlarıyla uğraşmayı önler.
Arayüz
Arayüz yalnızca sözleşmeyi tanımlar: hangi metotların hangi imzayla bulunacağını söyler, nasıl çalışacağını söylemez. Bir sınıf birden fazla arayüz uygulayabilir. Bu yüzden bağımlılıklar somut sınıflara değil arayüzlere verildiğinde, uygulama değiştirmek çağrı noktalarına dokunmadan mümkün olur.
interface OdemeSaglayici
{
public function tahsilEt(int $kurus, string $kart): string; // işlem numarası döner
}
final class SanalPos implements OdemeSaglayici
{
public function tahsilEt(int $kurus, string $kart): string { /* ... */ }
}
final class TestSaglayici implements OdemeSaglayici
{
public function tahsilEt(int $kurus, string $kart): string
{
return 'TEST-' . bin2hex(random_bytes(4));
}
}
final class SiparisServisi
{
public function __construct(private readonly OdemeSaglayici $odeme) {}
}Yukarıdaki yapıda sipariş servisi hangi sağlayıcının kullanıldığını bilmez. Testlerde gerçek sağlayıcı yerine sahte bir uygulama verilebilir; üretimde sağlayıcı değiştiğinde servis kodu aynı kalır. Arayüzler sabit değerler de içerebilir, ancak özellik tanımlayamaz.
Trait
Trait, birden fazla sınıfa aynı metotları eklemenin yoludur. Kalıtımdan farkı, bir tür ilişkisi kurmamasıdır: trait kullanan sınıflar birbirinin yerine geçemez. Derleme aşamasında metotlar sınıfın içine kopyalanır.
trait ZamanDamgasi
{
private ?DateTimeImmutable $olusturma = null;
public function damgala(): void
{
$this->olusturma ??= new DateTimeImmutable('now', new DateTimeZone('UTC'));
}
}
trait Gunlukleyici
{
public function yaz(string $mesaj): void
{
error_log(static::class . ': ' . $mesaj);
}
}
final class Fatura
{
use ZamanDamgasi, Gunlukleyici;
}İki trait aynı adda metot getirirse çakışma oluşur ve bu durum açıkça çözülmelidir. Çözüm sözdizimi işe yarar, ancak sık ihtiyaç duyuluyorsa tasarımın gözden geçirilmesi gerekir.
final class Rapor
{
use Gunlukleyici, DosyaYazici {
Gunlukleyici::yaz insteadof DosyaYazici; // çakışmada bu kazanır
DosyaYazici::yaz as dosyayaYaz; // diğeri yeni adla kullanılır
}
}Trait durum taşıdığında sorun büyür: sınıfın hangi özelliklere sahip olduğu tek bir dosyaya bakarak anlaşılamaz. Trait kullanımı, davranışın gerçekten birbirinden bağımsız sınıflarda tekrarlandığı ve ortak bir üst sınıfın anlamsız olduğu durumlarla sınırlı tutulmalıdır.
Geç statik bağlama
self, yazıldığı sınıfı gösterir; static ise çağrının yapıldığı sınıfı. Fark, üst sınıfta yazılan bir üretici metodun alt sınıftan çağrılmasında ortaya çıkar.
abstract class Model
{
public static function olustur(array $veri): static
{
return new static($veri); // alt sınıfın örneği üretilir
}
public static function yanlis(array $veri): self
{
return new self($veri); // her zaman Model üretmeye çalışır
}
}Soyut sınıf mı arayüz mü
İkisi arasındaki seçim, paylaşılacak şeyin ne olduğuna bakılarak yapılır. Paylaşılan yalnızca metot adları ve imzalarsa arayüz yeterlidir. Paylaşılan bir de ortak davranışsa soyut sınıf düşünülür, ancak bu tercih alt sınıfların tek bir üst sınıfa bağlanması anlamına gelir.
Pratikte yaygın olan düzen, arayüzü sözleşme olarak tanımlayıp ortak davranışı ayrı bir nesneye taşımaktır. Böylece hem sözleşme belirgin kalır hem de kalıtım zinciri büyümez. Soyut sınıf, ortak davranışın gerçekten alt sınıfın iç işleyişine ait olduğu dar durumlarda kullanılır.
protected görünürlüğü de dikkatli kullanılmalıdır. Bu düzey, üyeyi alt sınıflara açtığı için kalıtım zincirinin tamamıyla bir sözleşme kurar; ileride değiştirilmesi, dışarı açık bir metodu değiştirmek kadar zordur.
Metot imzalarında uyumluluk
Alt sınıf, üst sınıfın metodunu yeniden tanımlarken imzayı daraltamaz. Kural şudur: parametreler genişleyebilir, dönüş tipi daralabilir. Bunun nedeni, alt sınıf örneğinin üst sınıf beklenen her yerde kullanılabilmesi gerektiğidir.
abstract class Depo
{
abstract public function bul(int $id): ?Model;
}
final class UrunDeposu extends Depo
{
public function bul(int $id): ?Urun // dönüş tipi daraldı, geçerli
{
// ...
}
}Bu kural yalnızca dilin denetlediği bir sözdizimi meselesi değildir. Alt sınıf, üst sınıfın vaat ettiğinden daha katı koşullar öne sürerse, çağıran kod beklenmedik biçimde hata alır. Örneğin üst sınıfın metodu boş liste için çalışıyorken alt sınıfın istisna fırlatması, imza aynı kalsa bile sözleşmenin ihlalidir.
Hangisi ne zaman
- Alt tür gerçekten üst türün bir çeşidiyse ve her yerde onun yerine geçebiliyorsa kalıtım uygundur.
- Yalnızca ortak bir sözleşme gerekiyorsa arayüz kullanılır; uygulamalar birbirinden tamamen bağımsız kalır.
- Aynı yardımcı davranış, aralarında tür ilişkisi olmayan sınıflarda tekrar ediyorsa trait düşünülebilir.
- Davranış çalışma zamanında değişebiliyorsa hiçbiri uygun değildir; bağımlılık nesne olarak içeri verilir.
Kalıtımın en sık yapılan yanlış kullanımı, kod paylaşmak için tür ilişkisi uydurmaktır. Bir sınıf yalnızca üst sınıftaki iki metodu kullanmak için genişletiliyorsa, o iki metot ayrı bir nesneye taşınıp bileşimle kullanılmalıdır.
// kalıtım yerine bileşim
final class SiparisRaporu
{
public function __construct(
private readonly Bicimleyici $bicim,
private readonly Gunluk $gunluk,
) {}
public function uret(array $satirlar): string
{
$this->gunluk->bilgi('Rapor üretiliyor');
return $this->bicim->tabloya($satirlar);
}
}Bileşimde bağımlılıklar dışarıdan verildiği için değiştirilmesi kolaydır ve sınıfın neye ihtiyaç duyduğu yapıcı metoda bakılarak görülür. Kalıtımda ise bağımlılık üst sınıfın içinde gizlidir.
Kontrol listesi
- Kalıtım yalnızca gerçek bir tür ilişkisi varsa kullanılıyor.
- Genişletilmesi planlanmayan sınıflar
finalile işaretli. - Bağımlılıklar somut sınıflara değil arayüzlere veriliyor.
- Trait kullanımı durum taşımayan yardımcı davranışlarla sınırlı.
- Üretici metotlarda
selfyerinestatictercih ediliyor. - Kalıtım zinciri iki düzeyi geçmiyor.
İlgili yazılar
- Sınıf, nesne ve kapsülleme
- Kalıtım yerine bileşim ve erken soyutlamanın maliyeti
- Modüller arası sınırları belirlemek
PHP eğitim serisi
Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.
Temeller
- Kurulum ve temel sözdizimi
- Döngüler ve akış kontrolü
- Diziler
- Fonksiyonlar
- String fonksiyonları
- Süper globaller
- Form işleme
- Dosya işlemleri
Dilin ayrıntıları
Nesne yönelimli PHP
- Sınıf ve nesne
- Kalıtım, arayüz ve trait (bu yazı)
- Statik üyeler, sabitler ve enum
- Namespace, Composer ve autoload
Veri ve güvenlik
- PDO ile veritabanı işlemleri
- Oturum yönetimi ve kimlik doğrulama
- Güvenli çıktı ve XSS
- JSON ile veri değişimi
Dış dünya ve üretim
Yorumlar
İlk yorumu sen yap.
İlgili Yazılar

PHP Uygulamasını Üretime Almak: Yapılandırma ve Güvenlik
Geliştirme makinesinde çalışan kod ile üretimde güvenle çalışan kod arasındaki fark yapılandırmadır. Ayarlar, izinler, dağıtım akışı ve son kontroller.

PHP ile REST API Yazmak: Yönlendirme ve HTTP Durum Kodları
Bir API'nin kalitesi çoğunlukla ayrıntılarda belli olur: doğru durum kodu, tutarlı hata biçimi ve öngörülebilir adresler. Saf PHP ile uçtan uca kurulum.

PHPUnit ile Birim Test Yazmak: Kurulum, Sahte Nesneler ve Kapsam
Testin amacı hata bulmak değil, değişikliği güvenle yapabilmektir. Kurulum, ilk test, sahte nesneler ve test edilebilir kodun özellikleri.