PHP'de görünürlük (visibility), bir sınıf üyesine kodun hangi bölümünden erişilebileceğini belirler. public üyeye her yerden, protected üyeye yalnızca sınıfın kendisinden ve kalıtım zincirindeki sınıflardan, private üyeye ise yalnızca onu tanımlayan sınıftan erişilir. readonly değiştiricisi bir özelliğin yalnızca bir kez atanıp sonra değiştirilememesini sağlar. PHP 8.4 ile gelen asimetrik görünürlük ise okuma ve yazma için ayrı kapsamlar tanımlamanıza izin verir: public private(set) yazılan bir özellik her yerden okunur ama yalnızca sınıfın içinden değiştirilebilir.

Bu yazıda üç görünürlük düzeyini özellik, metot ve sabitler üzerinde deniyor, readonly özellik ve readonly sınıfın kurallarını, PHP 8.4'teki asimetrik görünürlüğü ve PHP 8.5'te bunun static özelliklere genişletilmesini çalışan örneklerle anlatıyoruz. Sınıf temelleri için sınıf ve nesne yazısına, serinin tamamı için PHP öğrenme rehberine bakabilirsiniz.

Üç düzey: public, protected, private

Üç düzey: public, protected, private: Tanımlayan sınıfın içi, Alt sınıfın içi, Üst sınıfın içi (alt sınıfta tanımlı üye), Sınıf dışı kod

Görünürlük, özellik, metot ve (PHP 7.1'den beri) sınıf sabiti tanımının başına yazılır. Görünürlük belirtmeden tanımlanan özellik, metot ve sabitler public sayılır. Aşağıdaki tablo kimin neye erişebildiğini özetliyor; sonra her satırı bir örnekle doğruluyoruz.

Erişim yeripublicprotectedprivate
Tanımlayan sınıfın içiEvetEvetEvet
Alt sınıfın içiEvetEvetHayır
Üst sınıfın içi (alt sınıfta tanımlı üye)EvetEvetHayır
Sınıf dışı kodEvetHayırHayır
Aynı sınıftan başka bir nesneEvetEvetEvet
php
<?php
class Hesap
{
    public string $sahip = 'Zeynep';
    protected float $limit = 5000;
    private string $pin = '1234';

    public function ozet(): string
    {
        return "$this->sahip / $this->limit / " . str_repeat('*', strlen($this->pin));
    }
}

$h = new Hesap();
echo $h->sahip, PHP_EOL;  // çıktı: Zeynep
echo $h->ozet(), PHP_EOL; // çıktı: Zeynep / 5000 / ****
echo $h->pin;
text
Zeynep
Zeynep / 5000 / ****

Fatal error: Uncaught Error: Cannot access private property Hesap::$pin in /yol/ornek.php:17
Stack trace:
#0 {main}
  thrown in /yol/ornek.php on line 17

Sınıfın kendi metodu ozet() üç özelliğe de erişebildi; dışarıdan ise yalnızca public olan $sahip okunabildi. Private bir özelliği dışarıdan okumaya çalışmak bir Error fırlatır. Protected özellikte de mesaj benzerdir, yalnızca "private" yerine "protected" yazar.

protected ve private farkı kalıtımda ortaya çıkar

İki düzey arasındaki asıl fark alt sınıflarda görünür. Protected üye alt sınıfın içinden sanki onun kendi üyesiymiş gibi kullanılabilir. Private üye ise alt sınıf için yoktur: alt sınıf onu okuyamaz, değiştiremez, hatta aynı adla yeni bir üye tanımlarsa bu, üst sınıftakiyle hiçbir bağı olmayan ayrı bir üye olur.

php
<?php
class Hesap
{
    protected float $limit = 5000;
    private string $pin = '1234';
}

class KurumsalHesap extends Hesap
{
    public function limitArtir(float $ek): float
    {
        $this->limit += $ek; // protected: alt sınıftan erişilebilir
        return $this->limit;
    }

    public function pinVarMi(): bool
    {
        return isset($this->pin); // private: alt sınıf göremez
    }
}

$k = new KurumsalHesap();
echo $k->limitArtir(2500), PHP_EOL; // çıktı: 7500
var_dump($k->pinVarMi());           // çıktı: bool(false)

pinVarMi() false döndürdü, çünkü alt sınıfın kapsamından bakıldığında $pin diye bir özellik görünmüyor. Metotlarda da aynı mantık geçerli ve bu, beklenmedik sonuçlar doğurabilir. Üst sınıftaki bir public metot $this->adim() ile private bir metodu çağırıyorsa, alt sınıf aynı adla bir private metot yazsa bile üst sınıfın kendi private metodu çalışır. Public metotlar ise normal şekilde ezilir.

php
<?php
class Ana
{
    public function calistir(): void
    {
        $this->adim();
        $this->kanca();
    }

    private function adim(): void { echo "Ana::adim\n"; }
    public function kanca(): void { echo "Ana::kanca\n"; }
}

class Alt extends Ana
{
    private function adim(): void { echo "Alt::adim\n"; }
    public function kanca(): void { echo "Alt::kanca\n"; }
}

(new Alt())->calistir();
/*
Ana::adim
Alt::kanca
*/

Alt sınıfların bir adımı özelleştirmesini istiyorsanız o metodu protected yapın. Kalıtımda görünürlük yalnızca genişletilebilir: üst sınıfta protected olan bir metot alt sınıfta public yapılabilir, ama public bir üye private yapılamaz. Kurucular bu kuralın istisnasıdır. Kalıtım kurallarının geri kalanı kalıtım yazısında.

Aynı sınıftan başka nesnelerin private üyeleri

Görünürlük nesne bazında değil, sınıf bazında çalışır. Bir metot, aynı sınıftan başka bir nesnenin private ve protected üyelerine de erişebilir. Kılavuz bunu, sınıfın içindeki kodun uygulama ayrıntılarını zaten bildiği gerekçesiyle açıklar. Bu özellik özellikle iki değeri karşılaştıran veya birleştiren metotlarda işe yarar.

php
<?php
class Para
{
    public function __construct(private int $kurus) {}

    public function topla(Para $diger): Para
    {
        return new Para($this->kurus + $diger->kurus); // diğer nesnenin private alanı
    }

    public function yaz(): string
    {
        return number_format($this->kurus / 100, 2, ',', '.') . ' TL';
    }
}

echo (new Para(1250))->topla(new Para(99975))->yaz(), PHP_EOL; // çıktı: 1.012,25 TL

Örnekte tutar, ondalıklı sayı yerine kuruş cinsinden tamsayı olarak tutuluyor; kayan noktalı sayıların hassasiyet sınırları için veri tipleri yazısına göz atabilirsiniz. Türkçe binlik ve ondalık ayırıcıyla biçimlendirme için sprintf ve number_format yazısına bakabilirsiniz.

Sınıf sabitlerinde görünürlük

Sınıf sabitlerinde görünürlük

PHP 7.1'den beri sınıf sabitleri de public, protected veya private olabilir. Varsayılan görünürlük public'tir. Private bir sabit, örneğin bir sınıfın iç hesaplarında kullandığı bir eşik değeri, dışarıya sızmadan sınıfın içinde self:: ile kullanılabilir. PHP 8.3 ile interface sabitlerinin görünürlüğü daha sıkı denetleniyor: interface'te public olan bir sabiti uygulayan sınıfta protected yapmak artık ölümcül hata veriyor. Sabitlerin ayrıntıları için static ve sınıf sabitleri yazısına bakabilirsiniz.

readonly özellikler

PHP 8.1 ile gelen readonly değiştiricisi, bir özelliğin ilk atamadan sonra değiştirilmesini engeller. Değeri yeniden atamak, artırmak, diziye eleman eklemek, unset() ile silmek veya referans almak gibi her türlü değişiklik bir Error fırlatır. Kılavuz birkaç kısıtı özellikle belirtir: readonly yalnızca tipli özelliklere uygulanabilir (tipsiz bir readonly özellik için mixed kullanılabilir), static readonly özellik desteklenmez ve readonly özelliğe tanım satırında varsayılan değer verilemez, çünkü bu bir sabitten farksız olurdu.

php
<?php
class Siparis
{
    public function __construct(
        public readonly int $no,
        public readonly array $kalemler,
    ) {}
}

$s = new Siparis(1001, ['kalem', 'defter']);
echo $s->no, PHP_EOL; // çıktı: 1001

try {
    $s->no = 1002;
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL; // çıktı: Cannot modify readonly property Siparis::$no
}

try {
    $s->kalemler[] = 'silgi';
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL; // çıktı: Cannot indirectly modify readonly property Siparis::$kalemler
}

İkinci mesajdaki "indirectly" kelimesine dikkat edin: diziye eleman eklemek bir atama gibi görünmese de özelliğin değerini değiştirdiği için dolaylı değişiklik sayılır ve aynı şekilde reddedilir. $s->no++, $s->no += 1 veya özelliği referansla bir fonksiyona geçirmek de kılavuzun listelediği yasak işlemler arasındadır.

Readonly bir özellik yalnızca bir kez ve yalnızca bildirildiği kapsamdan başlatılabilir. Sınıf dışındaki kod, henüz başlatılmamış bir readonly özelliğe de değer atayamaz. Öte yandan readonly yalnızca özelliğin kendisini korur; özellikte bir nesne saklıyorsanız o nesnenin içi değiştirilebilir. Kılavuz bunu "iç değişebilirlik" (interior mutability) olarak adlandırır.

php
<?php
class Kutu
{
    public readonly string $etiket;

    public function __construct(public readonly stdClass $icerik) {}
}

$k = new Kutu(new stdClass());
$k->icerik->adet = 3;           // izinli: nesnenin içi değişebilir
echo $k->icerik->adet, PHP_EOL; // çıktı: 3

try {
    $k->etiket = 'dışarıdan';
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL; // çıktı: Cannot modify protected(set) readonly property Kutu::$etiket from global scope
}

Hata mesajında geçen protected(set) ifadesi tesadüf değil; bir sonraki paragrafta anlatılan PHP 8.4 değişikliğinin sonucudur. Mesaj, özelliğe yalnızca sınıfın ve alt sınıfların yazabileceğini, genel kapsamın (global scope) ise yazamayacağını söylüyor.

PHP 8.4 ile readonly özelliklerin yazma kapsamı değişti. Önceden readonly özellik örtük olarak private(set) davranıyordu, yani yalnızca tanımlandığı sınıf onu başlatabiliyordu. PHP 8.4 itibarıyla örtük olarak protected(set) davranıyor ve alt sınıflar da başlatabiliyor. Aşağıdaki kod PHP 8.4 ve sonrasında çalışır.

php
<?php
class Varlik
{
    public readonly int $id;
}

class Kullanici extends Varlik
{
    public function __construct(int $id)
    {
        $this->id = $id; // PHP 8.4+: readonly örtük olarak protected(set)
    }
}

echo (new Kullanici(7))->id, PHP_EOL; // çıktı: 7

Readonly bir özelliğin değerini değiştirilmiş bir kopya üretmek için PHP 8.3'ten beri __clone() içinde yeniden başlatabilirsiniz; PHP 8.5 ise clone() fonksiyonuna özellik listesi verme imkânı getirdi. Bu konuları clone ve nesne karşılaştırma yazısında anlatıyoruz.

readonly sınıflar

PHP 8.2'den beri bütün sınıfı readonly olarak işaretleyebilirsiniz. Bu, bildirilen her özelliğe readonly ekler ve dinamik özellik oluşturmayı tamamen engeller; #[\AllowDynamicProperties] ile bu kısıt kaldırılamaz. Readonly özelliklerin kuralları burada da geçerli olduğu için readonly bir sınıf tipsiz veya static özellik bildiremez. Readonly bir sınıftan yalnızca yine readonly olan bir sınıf türetilebilir. Değer nesneleri (koordinat, para tutarı, tarih aralığı gibi) için doğal bir seçimdir.

php
<?php
readonly class Koordinat
{
    public function __construct(
        public float $enlem,
        public float $boylam,
    ) {}

    public function kaydir(float $d): static
    {
        return new static($this->enlem + $d, $this->boylam + $d);
    }
}

$a = new Koordinat(41.0, 29.0);
$b = $a->kaydir(0.5);
echo "$a->enlem,$a->boylam | $b->enlem,$b->boylam", PHP_EOL; // çıktı: 41,29 | 41.5,29.5

try {
    $a->rakim = 100;
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL; // çıktı: Cannot create dynamic property Koordinat::$rakim
}

kaydir() mevcut nesneyi değiştirmek yerine yeni bir nesne döndürüyor. Değişmez (immutable) nesnelerle çalışırken bu "değiştir ve yenisini döndür" kalıbı en yaygın yaklaşımdır.

Asimetrik görünürlük: public private(set)

PHP 8.4 ile bir özelliğin okuma (get) ve yazma (set) görünürlüğünü ayrı ayrı belirleyebiliyorsunuz. İlk değiştirici okuma kapsamını, parantezli ikinci değiştirici yazma kapsamını belirler. Yazma görünürlüğü okuma görünürlüğüyle aynı veya ondan daha dar olmalıdır: public protected(set) ve protected private(set) geçerlidir, protected public(set) sözdizimi hatasıdır. Özellik public ise ana görünürlüğü yazmayabilirsiniz; private(set) string $ad ile public private(set) string $ad aynı anlama gelir.

Bu özellik, eskiden "private özellik ve public getter metodu" ikilisiyle çözdüğünüz durumu tek satıra indirir. Değeri herkes okuyabilir, ama değişiklik yalnızca sınıfın kendi metotlarından, yani sınıfın kurallarından geçerek yapılabilir. Readonly'den farkı, değerin sınıf içinden istenildiği kadar değiştirilebilmesidir.

php
<?php
class Kitap
{
    public function __construct(
        public private(set) string $baslik,
        public protected(set) int $stok = 0,
    ) {}

    public function yenidenAdlandir(string $yeni): void
    {
        $this->baslik = $yeni; // sınıf içinden yazmak serbest
    }
}

class IndirimliKitap extends Kitap
{
    public function stokEkle(int $n): void
    {
        $this->stok += $n; // protected(set): alt sınıf yazabilir
    }
}

$k = new IndirimliKitap('Sefiller', 3);
$k->stokEkle(2);
$k->yenidenAdlandir('Les Misérables');
echo $k->baslik, ' / ', $k->stok, PHP_EOL; // çıktı: Les Misérables / 5

try {
    $k->baslik = 'Başka';
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL; // çıktı: Cannot modify private(set) property Kitap::$baslik from global scope
}

Kılavuz asimetrik görünürlükle ilgili birkaç ince kuralı listeler. Yalnızca tipli özellikler ayrı bir set görünürlüğüne sahip olabilir. private(set) bir özellik otomatik olarak final sayılır ve alt sınıfta yeniden bildirilemez. Bir özelliğe referans almak, değeri değiştirmeye kapı açtığı için get değil set görünürlüğüne tabidir; dizi özelliğine eleman eklemek de aynı nedenle set kuralına uyar. Son olarak parantezin içinde boşluk bırakılamaz: private( set ) yazımı ayrıştırma hatası verir.

Alt sınıf, final olmayan bir özelliği yeniden bildirirken ana görünürlüğü veya set görünürlüğünü genişletebilir, daraltamaz. Private bir özelliği alt sınıfta yeniden bildirmenin üst sınıftaki özelliği değiştirmediğini, farklı bir iç ada sahip yeni bir özellik oluşturduğunu da unutmayın.

PHP 8.5: static özelliklerde asimetrik görünürlük

PHP 8.4'te asimetrik görünürlük yalnızca nesne özelliklerinde kullanılabiliyordu. PHP 8.5 sürüm notlarına göre aynı söz dizimi artık static özelliklerde de çalışıyor. Bir sayaç veya önbellek istatistiği gibi herkesin okuması ama yalnızca sınıfın güncellemesi gereken static değerler için uygundur. Bu örnek PHP 8.5 veya sonrasını gerektirir.

php
<?php
class Sayac
{
    public private(set) static int $cagri = 0;

    public static function isle(): void
    {
        self::$cagri++;
    }
}

Sayac::isle();
Sayac::isle();
echo Sayac::$cagri, PHP_EOL; // çıktı: 2

try {
    Sayac::$cagri = 0;
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL; // çıktı: Cannot modify private(set) property Sayac::$cagri from global scope
}

Uygulamalı örnek: kuralları sınıfın koruduğu bir üyelik

Aşağıdaki sınıf bu yazıdaki araçları birlikte kullanıyor. Üye numarası readonly, çünkü hiç değişmemeli. Puan herkes tarafından okunabilmeli ama yalnızca sınıfın kurallarıyla artmalı; bu yüzden private(set). Seviye hesabı bir uygulama ayrıntısı olduğu için private bir metotta ve dışarıya açılmıyor. Seviye eşikleri de private bir sabitte duruyor. Bu örnek asimetrik görünürlük kullandığı için PHP 8.4 veya sonrasını gerektirir.

php
<?php
class Uyelik
{
    private const array ESIKLER = ['Altın' => 1000, 'Gümüş' => 500, 'Bronz' => 0];

    public private(set) int $puan = 0;

    public function __construct(public readonly int $uyeNo) {}

    public function alisveris(float $tutar): void
    {
        $this->puan += intdiv((int) $tutar, 10);
    }

    public function seviye(): string
    {
        return $this->esikBul();
    }

    private function esikBul(): string
    {
        foreach (self::ESIKLER as $ad => $alt) {
            if ($this->puan >= $alt) {
                return $ad;
            }
        }
        return 'Yok';
    }
}

$u = new Uyelik(501);
$u->alisveris(3250);
$u->alisveris(2100.90);
echo $u->uyeNo, ': ', $u->puan, ' puan, ', $u->seviye(), PHP_EOL; // çıktı: 501: 535 puan, Gümüş

foreach (['puan', 'uyeNo'] as $alan) {
    try {
        $u->$alan = 99999;
    } catch (Error $e) {
        echo $e->getMessage(), PHP_EOL;
    }
}
/*
Cannot modify private(set) property Uyelik::$puan from global scope
Cannot modify readonly property Uyelik::$uyeNo
*/

Puanı artırmanın tek yolu alisveris() metodu; dışarıdan $u->puan = 99999 yazmak reddediliyor. Üye numarası ise sınıfın içinden bile değiştirilemez. Bu yapı sayesinde puan kuralını değiştirmek istediğinizde yalnızca tek bir metoda dokunursunuz. Özelliğe okunurken veya yazılırken ek mantık çalıştırmak isterseniz PHP 8.4 ile gelen property hooks bir sonraki adımdır.

Hangi aracı ne zaman kullanmalı?

Hangi aracı ne zaman kullanmalı? Herkes okusun ve yazsın, Yalnızca sınıf ve alt sınıflar erişsin, Yalnızca bu sınıf erişsin, Bir kez atansın, sonra hiç değişmesin
İhtiyaçÖnerilen yazımGereken sürüm
Herkes okusun ve yazsınpublicTümü
Yalnızca sınıf ve alt sınıflar erişsinprotectedTümü
Yalnızca bu sınıf erişsinprivateTümü
Bir kez atansın, sonra hiç değişmesinpublic readonly8.1+
Tüm özellikler değişmez olsunreadonly class8.2+
Herkes okusun, yalnızca sınıf yazsınpublic private(set)8.4+
Herkes okusun, sınıf ve alt sınıflar yazsınpublic protected(set)8.4+
Static özellikte okuma ve yazma ayrımıpublic private(set) static8.5+

Sık yapılan hatalar

  • Her şeyi public yapmak: Dışarıdan değiştirilen özellikler, sınıfın kurallarını (ör. bakiyenin negatif olmaması) atlatmanın kolay bir yolunu açar. Varsayılan olarak private başlayıp gerektiğinde genişletmek daha güvenlidir.
  • Alt sınıfın kullanacağı üyeyi private yapmak: Alt sınıf private üyeyi göremez; isset() false döner veya "Undefined property" uyarısı alırsınız. Bu durumda protected kullanın.
  • readonly özelliğe varsayılan değer vermek: "cannot have default value" hatası alırsınız. Değeri kurucuda atayın veya sabit kullanın.
  • readonly'yi derin değişmezlik sanmak: Readonly özellikte saklanan nesnenin içi değiştirilebilir.
  • Asimetrik görünürlüğü tipsiz özellikte kullanmak: Ayrı set görünürlüğü yalnızca tipli özelliklerde geçerlidir.
  • private(set) özelliği alt sınıfta yeniden bildirmek: Bu özellikler örtük olarak final'dır.

Sık Sorulan Sorular

Görünürlük yazmazsam ne olur?

Görünürlük belirtilmeden tanımlanan özellik, metot ve sınıf sabitleri public kabul edilir. Okunabilirlik için yine de her üyeye açıkça görünürlük yazmanız önerilir.

readonly ile public private(set) arasındaki fark nedir?

Readonly özellik bir kez başlatıldıktan sonra sınıfın içinden bile değiştirilemez. public private(set) özellik ise dışarıdan değiştirilemez ama sınıfın içinden istenildiği kadar güncellenebilir.

Readonly bir sınıftan türetme yapabilir miyim?

Evet, ama yalnızca alt sınıf da readonly olarak işaretlenirse. Kılavuz, readonly bir sınıfın ancak ve ancak alt sınıfı da readonly ise genişletilebileceğini belirtir. Aynı şekilde normal bir özelliği alt sınıfta readonly olarak yeniden bildirmek de yasaktır.

Readonly özelliği unset() ile sıfırlayabilir miyim?

Başlatıldıktan sonra hayır. Kılavuza göre readonly bir özellik yalnızca henüz başlatılmamışken ve bildirildiği kapsamdan unset() edilebilir.

Interface içinde protected veya private metot tanımlayabilir miyim?

Hayır. Kılavuza göre interface'te bildirilen bütün metotlar public olmak zorundadır, çünkü interface bir sınıfın dış dünyaya verdiği sözü tanımlar. Alt sınıflara açık ama dışarıya kapalı bir zorunluluk istiyorsanız abstract sınıfta abstract protected metot kullanabilirsiniz.

Private bir özelliği test etmek için dışarıdan okuyabilir miyim?

Normal kodla hayır; görünürlük kuralları buna izin vermez. Test ihtiyacı çoğu zaman sınıfın public davranışını test ederek veya değeri okuyan bir metot ekleyerek karşılanır. Okuma serbest olsun istiyorsanız PHP 8.4 ile public private(set) iyi bir seçenektir.

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 · Sınıf ve nesne · Kalıtım, abstract ve interface · Property hooks · clone ve nesne karşılaştırma · PHP 8.4 yenilikler