PHP'de trait, birbiriyle kalıtım ilişkisi olmayan sınıflara aynı metotları ve özellikleri eklemenizi sağlayan bir kod paylaşım mekanizmasıdır. trait anahtar kelimesiyle tanımlanır, kendi başına örneklenemez ve sınıfın gövdesinde use TraitAdi; yazıldığında içindeki üyeler sınıfa kopyalanmış gibi davranır. PHP tekli kalıtım kullandığı için bir sınıf yalnızca bir üst sınıftan türeyebilir; trait bu sınırı, davranışı "yatay" olarak birden çok sınıfa dağıtarak hafifletir.

Bu yazıda trait tanımlamayı, öncelik sırasını, aynı adlı metotların çakışmasını insteadof ve as ile çözmeyi, görünürlük değiştirmeyi, trait içinde abstract ve static üyeleri, özellik ve sabit uyumluluğunu ve PHP 8.5 ile değişen bağlama sırasını çalışan örneklerle anlatıyoruz. Kalıtımın temellerini kalıtım, abstract ve interface yazısında, serinin tamamını PHP öğrenme rehberinde bulabilirsiniz.

İlk trait: tanımlamak ve kullanmak

İlk trait: tanımlamak ve kullanmak

Bir trait, sınıfa çok benzer biçimde yazılır: özellikleri, metotları, PHP 8.2'den beri sabitleri olabilir. Fark, onu new ile örnekleyememeniz ve bir tip olarak kullanamamanızdır. Trait'i bir sınıfa eklemek için sınıf gövdesinde use yazarsınız. Birden çok trait, virgülle ayrılarak tek bir use satırında listelenebilir.

php
<?php
trait Zamanlanabilir
{
    private ?string $guncellenme = null;

    public function dokun(string $tarih): void
    {
        $this->guncellenme = $tarih;
    }

    public function sonGuncelleme(): string
    {
        return $this->guncellenme ?? 'hiç';
    }
}

class Makale
{
    use Zamanlanabilir;

    public function __construct(public string $baslik) {}
}

class Urun
{
    use Zamanlanabilir;
}

$m = new Makale('Trait nedir?');
$m->dokun('2026-11-07');
echo $m->sonGuncelleme(), PHP_EOL;          // çıktı: 2026-11-07
echo (new Urun())->sonGuncelleme(), PHP_EOL; // çıktı: hiç
print_r(class_uses($m));
/*
Array
(
    [Zamanlanabilir] => Zamanlanabilir
)
*/

Makale ve Urun arasında hiçbir kalıtım ilişkisi yok, ama ikisi de aynı zaman damgası davranışını kazandı. Trait içindeki $this, trait'i kullanan sınıfın nesnesini gösterir; trait'teki private özellik de sınıfın kendi private özelliği gibi çalışır. Bir sınıfın hangi trait'leri kullandığını class_uses() ile öğrenebilirsiniz. Kılavuz, bu fonksiyonun üst sınıfların kullandığı trait'leri listeye dahil etmediğini belirtir.

Trait'in bir sözleşme (tip) sağlamadığını unutmayın. Aşağıdaki denemede parametre tipi olarak trait adı yazıldı; trait'i kullanan sınıfın nesnesi bile bu kontrolden geçemiyor. Ortak bir tip istiyorsanız bir interface tanımlayıp trait'i o interface'in hazır uygulaması olarak kullanmak yaygın bir yaklaşımdır.

php
<?php
trait Etiketli
{
    public function etiket(): string { return 'etiket'; }
}

class Dosya
{
    use Etiketli;
}

function goster(Etiketli $x): void
{
    echo $x->etiket(), PHP_EOL;
}

goster(new Dosya());
text
Fatal error: Uncaught TypeError: goster(): Argument #1 ($x) must be of type Etiketli, Dosya given, called in /yol/ornek.php on line 17 and defined in /yol/ornek.php:12
Stack trace:
#0 /yol/ornek.php(17): goster(Object(Dosya))
#1 {main}
  thrown in /yol/ornek.php on line 12

Öncelik sırası: sınıf, trait, üst sınıf

Aynı adlı bir metot hem sınıfta, hem trait'te, hem de üst sınıfta bulunabilir. Kılavuzun tanımladığı sıra şöyle: sınıfın kendi tanımladığı üye trait'ten gelen üyeyi ezer; trait'ten gelen üye ise üst sınıftan devralınan üyeyi ezer. Trait içindeki bir metot parent:: ile üst sınıftaki sürümü çağırabilir, çünkü trait kodu sanki sınıfın içine yazılmış gibi çalışır.

php
<?php
class Temel
{
    public function selam(): string { return 'Temel'; }
    public function veda(): string { return 'Temel veda'; }
}

trait SelamTrait
{
    public function selam(): string { return 'Trait, sonra ' . parent::selam(); }
    public function veda(): string { return 'Trait veda'; }
}

class Cocuk extends Temel
{
    use SelamTrait;

    public function veda(): string { return 'Sınıf veda'; }
}

$c = new Cocuk();
echo $c->selam(), PHP_EOL; // çıktı: Trait, sonra Temel
echo $c->veda(), PHP_EOL;  // çıktı: Sınıf veda

selam() çağrısında trait'teki sürüm üst sınıftakini ezdi, ama parent::selam() ile onu da çalıştırdı. veda() için ise sınıfın kendi metodu trait'tekinin önüne geçti. Bu sıralama metotlar için PHP 8.5'te de aynıdır.

Çakışma: iki trait aynı metodu getirirse

İki trait aynı adla bir metot sağlıyorsa ve siz hangisinin kullanılacağını söylemezseniz PHP ölümcül hata verir. Bu hata, sınıf tanımlanırken oluşur; yani metodu hiç çağırmasanız bile betik çalışmaz.

php
<?php
trait Loglayan
{
    public function yaz(): string { return 'log'; }
}

trait Yazdiran
{
    public function yaz(): string { return 'yazıcı'; }
}

class Rapor
{
    use Loglayan, Yazdiran;
}
text
Fatal error: Trait method Yazdiran::yaz has not been applied as Rapor::yaz, because of collision with Loglayan::yaz in /yol/ornek.php on line 12
Stack trace:
#0 {main}

Mesaj, PHP'nin Yazdiran trait'indeki yaz() metodunu sınıfa ekleyemediğini, çünkü aynı adın Loglayan tarafından zaten doldurulduğunu söylüyor. PHP burada sessizce birini seçmez; bu tasarım, çoklu kalıtımda görülen "hangi metot çalıştı?" belirsizliğini baştan engellemek içindir. Kararı sınıfı yazan kişi açıkça vermek zorundadır.

Çözüm için use satırının sonuna bir süslü parantez bloğu açarsınız. insteadof operatörü, çakışan metotlardan hangisinin kullanılacağını seçer ve diğerini dışlar. as operatörü ise bir metoda ek bir takma ad (alias) verir. Kılavuzun özellikle vurguladığı nokta: as metodu yeniden adlandırmaz, yalnızca ikinci bir ad ekler; dışlanan metoda bu ek adla erişmeye devam edebilirsiniz.

php
<?php
trait Loglayan
{
    public function yaz(): string { return 'log dosyasına yazıldı'; }
}

trait Yazdiran
{
    public function yaz(): string { return 'yazıcıya gönderildi'; }
}

class Rapor
{
    use Loglayan, Yazdiran {
        Yazdiran::yaz insteadof Loglayan;
        Loglayan::yaz as logaYaz;
    }
}

$r = new Rapor();
echo $r->yaz(), PHP_EOL;     // çıktı: yazıcıya gönderildi
echo $r->logaYaz(), PHP_EOL; // çıktı: log dosyasına yazıldı

Burada yaz() adı Yazdiran trait'inin sürümüne ait. Loglayan trait'inin sürümü insteadof ile dışlandı, ama as logaYaz sayesinde ikinci bir adla sınıfta kalmaya devam ediyor. Takma ad vermeden yalnızca insteadof kullansaydınız dışlanan sürüme hiçbir şekilde ulaşamazdınız.

as ile görünürlüğü değiştirmek

as ile görünürlüğü değiştirmek

as operatörünün ikinci görevi, trait'ten gelen bir metodun görünürlüğünü kullanan sınıf içinde değiştirmektir. Trait'te public yazılmış bir yardımcı metodu sınıfınızın dış arayüzünde göstermek istemiyorsanız as protected veya as private yazabilirsiniz. Görünürlükle birlikte takma ad da verebilirsiniz; bu durumda yalnızca takma adın görünürlüğü değişir, asıl ad olduğu gibi kalır.

php
<?php
trait Yardimci
{
    public function temizle(string $s): string
    {
        return trim(strip_tags($s));
    }
}

class Form
{
    use Yardimci {
        temizle as protected;
    }

    public function isle(string $girdi): string
    {
        return $this->temizle($girdi);
    }
}

$f = new Form();
echo $f->isle('  <b>Merhaba</b> '), PHP_EOL; // çıktı: Merhaba
$f->temizle('x');
text
Merhaba

Fatal error: Uncaught Error: Call to protected method Form::temizle() from global scope in /yol/ornek.php:24
Stack trace:
#0 {main}
  thrown in /yol/ornek.php on line 24

PHP 8.3 ile as operatörü final değiştiricisini de kabul ediyor: use OrtakTrait { metot as final; } yazarsanız bu sınıftan türeyen alt sınıflar metodu ezemez. Trait'i kullanan sınıfın kendisi ise metodu ezmeye devam edebilir. Görünürlük kurallarının tamamı için public, private, protected ve readonly yazısına bakabilirsiniz.

Trait içinde abstract ve static üyeler

Bir trait, kullanan sınıftan bazı şeyleri talep edebilir. Bunun yolu trait içinde abstract metot bildirmektir: trait'i kullanan sınıf o metodu tanımlamazsa abstract metot eksik kaldığı için hata alır. PHP 8.0'dan beri trait içindeki abstract metotlar public, protected veya private olabilir ve sınıftaki somut metodun imzası, normal imza uyumluluğu kurallarına uymak zorundadır.

Trait'ler static metot, static özellik ve metot içinde static değişken de tanımlayabilir. Önemli bir ayrıntı: trait'i kullanan her sınıf, trait'teki static özelliğin kendi kopyasını alır. Aşağıdaki örnekte Bilet ve Kupon aynı trait'i kullanıyor ama sayaçları ayrı ilerliyor.

php
<?php
trait Sayilabilir
{
    private static int $adet = 0;

    public static function olustur(): static
    {
        static::$adet++;
        return new static();
    }

    public static function adet(): int
    {
        return static::$adet;
    }

    abstract public function etiket(): string;
}

class Bilet
{
    use Sayilabilir;

    public function etiket(): string { return 'bilet'; }
}

class Kupon
{
    use Sayilabilir;

    public function etiket(): string { return 'kupon'; }
}

Bilet::olustur();
Bilet::olustur();
echo Kupon::olustur()->etiket(), PHP_EOL;  // çıktı: kupon
echo Bilet::adet(), ' ', Kupon::adet(), PHP_EOL; // çıktı: 2 1

Kılavuz iki sürüm notu daha veriyor. PHP 8.1'den beri static bir metodu veya özelliği doğrudan trait üzerinden çağırmak (Sayilabilir::adet()) kullanımdan kaldırılmıştır; bu üyelere yalnızca trait'i kullanan sınıf üzerinden erişilmelidir. PHP 8.3'ten itibaren ise bir alt sınıf, üst sınıfının zaten kullandığı bir trait'i tekrar use ederse static özellik alt sınıf için ayrı bir kopya sayılır; daha önce aynı hiyerarşide paylaşılıyordu. static:: ile self:: arasındaki farkı static ve late static binding yazısında anlatıyoruz.

php
<?php
trait Arac
{
    public static function surum(): string
    {
        return '1.0';
    }
}

echo Arac::surum(), PHP_EOL;
text
Deprecated: Calling static trait method Arac::surum is deprecated, it should only be called on a class using the trait in /yol/ornek.php on line 10
1.0

Trait içinde özellik ve sabit

Trait'te tanımlanan bir özellik, kullanan sınıfa eklenir. Sınıf aynı adla bir özellik tanımlamak isterse iki tanımın tamamen uyumlu olması gerekir: aynı görünürlük, aynı tip, aynı readonly durumu ve aynı başlangıç değeri. Küçük bir fark, örneğin başlangıç değerinin 10 yerine 20 olması bile ölümcül hataya yol açar. PHP 8.2'den beri trait'ler sabit de tanımlayabilir ve aynı kural sabitler için de geçerlidir: görünürlük, değer ve final durumu birebir aynı olmalıdır.

php
<?php
trait Varsayilanlar
{
    public const SURUM = 2;
    public int $limit = 10;
}

class Liste
{
    use Varsayilanlar;

    public int $limit = 10; // uyumlu: görünürlük, tip ve başlangıç değeri aynı
}

$l = new Liste();
echo Liste::SURUM, ' ', $l->limit, PHP_EOL; // çıktı: 2 10
php
<?php
trait Varsayilanlar
{
    public int $limit = 10;
}

class Liste
{
    use Varsayilanlar;

    public int $limit = 20; // başlangıç değeri farklı
}
text
Fatal error: Liste and Varsayilanlar define the same property ($limit) in the composition of Liste. However, the definition differs and is considered incompatible. Class was composed in /yol/ornek.php on line 7
Stack trace:
#0 {main}

Bu kuralın pratik sonucu şudur: trait'teki bir özelliğin değerini sınıfta "ezerek" değiştiremezsiniz. Farklı bir başlangıç değeri gerekiyorsa değeri kurucuda atayın ya da trait'te bir abstract metotla sınıftan isteyin.

Trait'lerden trait oluşturmak

Sınıflar gibi trait'ler de başka trait'leri use edebilir. Böylece küçük, tek işe odaklanmış trait'leri birleştirip daha büyük bir paket oluşturabilirsiniz. Çakışma çözümü ve takma ad kuralları burada da aynen geçerlidir.

php
<?php
trait Selam
{
    public function selam(): string { return 'Merhaba'; }
}

trait Dunya
{
    public function dunya(): string { return 'Dünya'; }
}

trait SelamDunya
{
    use Selam, Dunya;
}

class Karsilama
{
    use SelamDunya;
}

$k = new Karsilama();
echo $k->selam(), ' ', $k->dunya(), '!', PHP_EOL; // çıktı: Merhaba Dünya!
print_r(class_uses($k));
/*
Array
(
    [SelamDunya] => SelamDunya
)
*/

class_uses() bu çalıştırmada yalnızca sınıfın doğrudan kullandığı SelamDunya trait'ini döndürdü; onun içindeki Selam ve Dunya listede yer almadı. Metotların ikisi de sınıfta kullanılabilir olsa da hangi trait'ten geldiklerini bu fonksiyonun çıktısından doğrudan göremezsiniz.

Trait metodunu ezip yine de çağırmak

Öncelik kuralı gereği sınıfta aynı adla bir metot yazdığınızda trait'teki sürüm görünmez olur. Trait sürümünü tamamen atmak değil de onu sarmalamak istiyorsanız as ile ona ek bir ad verin ve kendi metodunuzdan bu ad üzerinden çağırın. Takma adı protected veya private yaparak dış arayüzü de temiz tutabilirsiniz. Trait içinde parent:: kullanmanın tersine, bu yöntem sınıfın üst sınıfı olmasa da çalışır.

php
<?php
trait Kaydedici
{
    public function kaydet(string $veri): string
    {
        return "kaydedildi: $veri";
    }
}

class DenetimliKayit
{
    use Kaydedici {
        kaydet as private traitKaydet;
    }

    public function kaydet(string $veri): string
    {
        return '[denetim] ' . $this->traitKaydet(strtoupper($veri));
    }
}

echo (new DenetimliKayit())->kaydet('fatura'), PHP_EOL; // çıktı: [denetim] kaydedildi: FATURA

Uygulamalı örnek: interface ve trait birlikte

Trait'in tip oluşturmama eksikliğini kapatmanın en temiz yolu, sözleşmeyi bir interface'e, hazır uygulamayı bir trait'e koymaktır. Aşağıdaki örnekte Etiketlenebilir interface'i fonksiyonların beklediği tipi tanımlıyor. EtiketTrait ise bu sözleşmenin ortak kodunu taşıyor ve gereken tek bilgiyi, yani etiketin önekini, abstract bir metotla kullanan sınıftan istiyor. Böylece yeni bir sınıf iki satırla hem tipi hem davranışı kazanıyor, fonksiyon da yalnızca interface'e bağımlı kalıyor.

php
<?php
interface Etiketlenebilir
{
    public function etiketEkle(string $etiket): void;
    public function etiketler(): array;
}

trait EtiketTrait
{
    private array $etiketler = [];

    abstract protected function onek(): string;

    public function etiketEkle(string $etiket): void
    {
        $this->etiketler[] = $this->onek() . mb_strtolower($etiket);
    }

    public function etiketler(): array
    {
        return $this->etiketler;
    }
}

class Yazi implements Etiketlenebilir
{
    use EtiketTrait;

    protected function onek(): string { return '#'; }
}

function listele(Etiketlenebilir $nesne): string
{
    return implode(' ', $nesne->etiketler());
}

$y = new Yazi();
$y->etiketEkle('PHP');
$y->etiketEkle('Trait');
echo listele($y), PHP_EOL; // çıktı: #php #trait

Burada listele() fonksiyonu trait'ten habersiz; yalnızca interface'i tanıyor. Yarın etiketleri veritabanında tutan farklı bir sınıf yazarsanız trait'i kullanmadan da aynı interface'i uygulayabilir ve fonksiyona geçirebilirsiniz. mb_strtolower() çok baytlı karakterleri doğru işlediği için Türkçe etiketlerde strtolower() yerine tercih edildi; ayrıntısı mbstring ve UTF-8 yazısında.

Trait ne zaman doğru seçim?

Trait, aynı birkaç metodun ilgisiz sınıflarda kopyalanıp yapıştırıldığını fark ettiğinizde işe yarar: zaman damgası, olay kaydı, basit önbellek yardımcıları gibi. Ancak trait bir tip oluşturmaz ve kullanan sınıfın içine görünmez biçimde kod ekler. Çok sayıda trait kullanan bir sınıfta bir metodun nereden geldiğini bulmak zorlaşabilir. Davranışın bir sözleşme olarak görünmesi gerekiyorsa interface, ortak durum ve kod paylaşan bir hiyerarşi gerekiyorsa abstract sınıf daha açık bir tasarım sunar. Karşılaştırma tablosu kalıtım yazısında yer alıyor.

Sık yapılan hatalar

Sık yapılan hatalar: Çakışmayı çözmeden iki trait kullanmak, as operatörünü yeniden adlandırma sanmak, Trait'i tip olarak kullanmak, Static üyeyi trait üzerinden çağırmak
  • Çakışmayı çözmeden iki trait kullanmak: Aynı adlı metot getiren iki trait, insteadof yazılmadan kullanılırsa sınıf tanımlanamaz.
  • as operatörünü yeniden adlandırma sanmak: as yalnızca ek bir ad verir; asıl adın kaldırılması için insteadof gerekir.
  • Trait'i tip olarak kullanmak: Trait adları parametre veya dönüş tipi olarak bir sözleşme sağlamaz; interface kullanın.
  • Static üyeyi trait üzerinden çağırmak: PHP 8.1'den beri kullanımdan kaldırılmış bir yazımdır; trait'i kullanan sınıfın adını kullanın.
  • Trait özelliğini farklı değerle yeniden tanımlamak: Görünürlük, tip, readonly veya başlangıç değeri farklıysa ölümcül hata alırsınız.

Sık Sorulan Sorular

Trait ile abstract sınıf arasındaki temel fark nedir?

Bir sınıf yalnızca tek bir abstract sınıftan türeyebilir ve o sınıf bir tip olarak kullanılabilir. Trait ise birden çok sınıfa, aralarında kalıtım ilişkisi olmadan eklenebilir ama bir tip oluşturmaz.

Trait içindeki private metoda sınıftan erişebilir miyim?

Evet. Trait'in üyeleri sınıfa eklendiği için trait'teki private metotlar, trait'i kullanan sınıfın kendi private metotları gibi çalışır ve sınıfın içinden çağrılabilir.

Bir sınıf kaç trait kullanabilir?

Kılavuz bir sınır belirtmez; use satırında virgülle istediğiniz kadar trait listeleyebilirsiniz. Aynı adlı metotlar çakışırsa her çakışmayı insteadof ile tek tek çözmeniz gerekir. Çok sayıda trait, metotların kaynağını izlemeyi zorlaştırdığı için okunabilirliği de gözetin.

Trait içinde sabit tanımlayabilir miyim?

PHP 8.2 ve sonrasında evet. Kılavuzun örneğinde trait'teki sabit, trait'i kullanan sınıf ya da onun bir nesnesi üzerinden ($nesne::SABIT) okunuyor. Sınıf aynı adla bir sabit tanımlarsa görünürlüğü, değeri ve final durumu trait'teki tanımla birebir aynı olmalıdır.

PHP 8.5'teki trait bağlama değişikliği kodumu bozar mı?

Metot çözümlemesi değişmedi. Etkilenen durum, trait ile üst sınıfın aynı adlı özellik veya sabit tanımlamasıdır; eski sürümlerde ölümcül hata veren bu birleşim artık trait'in tanımını kullanıyor. Ayrıntı için kılavuzun PHP 8.5 uyumsuz değişiklikler sayfasına ve PHP 8.5 yenilikler yazısına bakabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: PHP öğrenme rehberi · Kalıtım, abstract ve interface · Sınıf ve nesne · static ve late static binding · Görünürlük ve readonly · PHP 8.5 yenilikler