PHP'de sınıf (class), birbirine ait verileri ve bu veriler üzerinde çalışan fonksiyonları tek bir yapı altında toplayan bir şablondur. Sınıfın içindeki değişkenlere özellik (property), fonksiyonlara metot (method) denir. Bu şablondan new anahtar kelimesiyle üretilen her somut kopya bir nesnedir (object). Metotların içinde çağrıyı yapan nesneye $this sözde değişkeniyle ulaşırsınız; nesne oluşturulurken çalışacak hazırlık kodunu ise __construct() metoduna yazarsınız.
Bu yazıda bir sınıfı sıfırdan tanımlıyor, özellik ve metot ekliyor, kurucu metodu (constructor) ve PHP 8.0 ile gelen constructor promotion kısaltmasını, PHP 8.4 ile parantezsiz hâle gelen new zincirlemesini ve en sık karşılaşılan hataları çalışan örneklerle anlatıyoruz. Tüm örnekler PHP 8.5.12 ile denendi. Seriye baştan başlamak isterseniz PHP öğrenme rehberine göz atabilirsiniz.
Sınıf tanımlamak: class, özellik ve metot

Bir sınıf class kelimesiyle başlar, ardından sınıf adı ve süslü parantezler içinde gövde gelir. Sınıf adı bir harf ya da alt çizgiyle başlamalı, devamında harf, rakam veya alt çizgi içerebilir ve PHP'nin ayrılmış kelimelerinden biri olamaz. Kılavuz, PHP 8.4 itibarıyla yalnızca tek bir alt çizgiden (_) oluşan sınıf adını kullanımdan kaldırılmış (deprecated) olarak işaretliyor. Gelenek olarak sınıf adları büyük harfle başlar: Urun, Siparis, BankaHesabi.
Gövdenin içinde üç tür üye bulunabilir: sabitler, özellikler ve metotlar. Özellik tanımlarken en az bir değiştirici (modifier) yazmanız gerekir; en yaygını görünürlük belirten public, protected veya private kelimesidir. PHP 7.4'ten beri özelliğe tip de verebilirsiniz. Başlangıç değeri verilecekse bu değer sabit bir ifade olmalıdır; fonksiyon çağrısı veya başka bir değişken başlangıç değeri olarak yazılamaz.
<?php
class Urun
{
public string $ad = 'İsimsiz';
public float $fiyat = 0.0;
public function etiket(): string
{
return $this->ad . ' - ' . $this->fiyat . ' TL';
}
}
$klavye = new Urun();
$klavye->ad = 'Klavye';
$klavye->fiyat = 750.5;
echo $klavye->etiket(), PHP_EOL; // çıktı: Klavye - 750.5 TL
$bos = new Urun;
echo $bos->etiket(), PHP_EOL; // çıktı: İsimsiz - 0 TLÖrnekte dikkat edilecek birkaç nokta var. Nesnenin özelliğine ve metoduna nesne operatörü -> ile erişilir: $klavye->ad bir özelliği okur, $klavye->etiket() bir metodu çağırır. Özellik adının başına ikinci bir $ yazılmaz; $klavye->$ad yazarsanız PHP, $ad değişkeninin değerini özellik adı olarak kullanmaya çalışır. Kurucuya argüman geçmiyorsanız new Urun gibi parantezleri atlayabilirsiniz, ama okunabilirlik için çoğu ekip parantezli yazımı tercih eder.
Metot içindeki $this, o an metodu çağıran nesneyi gösterir. Aynı sınıftan iki nesne oluşturduğunuzda her birinin kendi özellik değerleri vardır; $bos nesnesi $klavye üzerindeki değişikliklerden etkilenmez. Özelliklerle metotlar ayrı ad alanlarında yaşadığı için bir sınıfta hem $bar adında bir özellik hem de bar() adında bir metot bulunabilir; hangisinin kullanılacağına yazımın parantezli olup olmamasına göre karar verilir.
Tipli özellikler ve başlatılmamış değer hatası
Tip bildirimli (typed) bir özelliğe başlangıç değeri vermezseniz özellik "başlatılmamış" (uninitialized) durumda kalır. Bu, değerin null olduğu anlamına gelmez: okumaya kalktığınızda PHP bir Error fırlatır. Tipi ?string gibi nullable olsa bile durum aynıdır; null değerini kendiniz atamadıkça özellik başlatılmış sayılmaz.
<?php
class Siparis
{
public int $no;
public ?string $not = null;
}
$s = new Siparis();
var_dump($s->not); // çıktı: NULL
echo $s->no;NULL
Fatal error: Uncaught Error: Typed property Siparis::$no must not be accessed before initialization in /yol/ornek.php:10
Stack trace:
#0 {main}
thrown in /yol/ornek.php on line 10Bu hatayı çözmenin iki yolu var: özelliğe tanım satırında bir başlangıç değeri vermek ya da değeri kurucu metotta atamak. Hangi değerin zorunlu olduğunu nesne oluşturulurken netleştirmek genellikle daha sağlam bir tasarımdır, çünkü yarım kurulmuş bir nesne dolaşıma hiç girmez. Tiplerin ayrıntısı ve declare(strict_types=1) etkisi için tip bildirimleri yazısına bakabilirsiniz.
Kurucu metot: __construct()
__construct(), PHP'nin sihirli metotlarından biridir ve her new çağrısında, nesne oluşturulduktan hemen sonra otomatik çalışır. Sıradan bir metot gibi istediğiniz sayıda parametre alabilir; parametreler tipli, zorunlu veya varsayılan değerli olabilir. new ifadesinde sınıf adından sonra parantez içine yazdığınız argümanlar doğrudan kurucuya gider. PHP 8.0'dan beri isimli argümanlar (named arguments) kurucuda da çalışır.
<?php
class Nokta
{
private int $x;
private int $y;
public function __construct(int $x, int $y = 0)
{
$this->x = $x;
$this->y = $y;
}
public function yaz(): string
{
return "($this->x, $this->y)";
}
}
echo (new Nokta(4, 5))->yaz(), PHP_EOL; // çıktı: (4, 5)
echo (new Nokta(4))->yaz(), PHP_EOL; // çıktı: (4, 0)
echo (new Nokta(y: 7, x: 2))->yaz(), PHP_EOL; // çıktı: (2, 7)PHP sınıf başına yalnızca bir kurucu destekler; Java veya C# gibi aynı adla farklı parametre listelerine sahip birden çok kurucu yazamazsınız. Kılavuzun önerdiği yöntem, farklı girdilerden nesne üretmek için fromJson(), fromArray() gibi statik "oluşturucu" metotlar yazmaktır. Bu kalıbı ve new static() kullanımını static ve late static binding yazısında ayrıntılı gösteriyoruz.
Eski PHP sürümlerinde sınıfla aynı adı taşıyan metot da kurucu sayılıyordu. PHP 8.0 itibarıyla bu kural tamamen kalktı; artık sınıfla aynı adlı bir metodun hiçbir özel anlamı yoktur. Yeni kodda her zaman __construct() kullanın.
Constructor promotion: özellik ve parametreyi tek satırda tanımlamak

Kurucuların büyük kısmı, aldığı parametreleri aynı adlı özelliklere atamaktan başka bir şey yapmaz. PHP 8.0 ile gelen constructor property promotion, bu tekrarı ortadan kaldırır: kurucu parametresinin önüne bir görünürlük (veya readonly gibi başka bir) değiştirici yazdığınızda PHP o parametreyi hem bir özellik hem de bir argüman olarak yorumlar ve değeri otomatik atar. Kurucunun gövdesi boş kalabilir ya da ek işlemler içerebilir; gövdedeki satırlar atamalar yapıldıktan sonra çalışır.
<?php
class Musteri
{
public function __construct(
public readonly int $id,
public string $ad,
private string $eposta = '',
) {
$this->ad = trim($this->ad);
}
public function eposta(): string
{
return $this->eposta === '' ? '(yok)' : $this->eposta;
}
}
$m = new Musteri(42, ' Ayşe Yılmaz ');
var_dump($m->id); // çıktı: int(42)
var_dump($m->ad); // çıktı: string(13) "Ayşe Yılmaz"
echo $m->eposta(), PHP_EOL; // çıktı: (yok)Örnekteki $id hem yükseltilmiş hem de readonly: değeri kurucuda bir kez atanır ve sonra değiştirilemez. $ad ise normal bir public özellik olduğu için kurucu gövdesinde trim() ile temizlenebiliyor. Bu sıra önemlidir: gövdeye geldiğinizde promotion atamaları çoktan yapılmıştır, bu yüzden $this->ad zaten argümanın değerini taşır.
Promotion ile normal parametreleri istediğiniz sırada karıştırabilirsiniz; sınıfı kullanan kod açısından hiçbir şey değişmez. Bilmeniz gereken birkaç kural var. Birincisi, özellikler callable tipinde olamadığı için yükseltilen (promoted) parametre de callable tipli olamaz. İkincisi, parametredeki varsayılan değer yalnızca argümana kopyalanır, özelliğe kopyalanmaz. Üçüncüsü, parametreye koyduğunuz bir attribute hem özelliğe hem argümana yansır. Son olarak PHP 8.5 ile promotion, final özellikler için de kullanılabilir hâle geldi. readonly ve asimetrik görünürlük seçeneklerini public, private, protected ve readonly yazısında ele alıyoruz.
Nesneler değer değil, tanıtıcıyla taşınır
Yeni başlayanların en çok şaşırdığı davranış şudur: bir nesneyi başka bir değişkene atadığınızda veya bir fonksiyona argüman olarak verdiğinizde nesnenin kopyası oluşmaz. Yeni değişken aynı nesneye işaret eden bir tanıtıcı (handle) taşır. Dolayısıyla fonksiyon içinde nesnenin özelliğini değiştirirseniz bu değişiklik dışarıdan da görünür. Gerçekten bağımsız bir kopya istiyorsanız clone kullanmanız gerekir.
<?php
class Sayac
{
public int $deger = 0;
}
function artir(Sayac $s): void
{
$s->deger++;
}
$a = new Sayac();
$b = $a; // kopya değil, aynı nesne
artir($b);
echo $a->deger, PHP_EOL; // çıktı: 1
$c = clone $a; // ayrı bir kopya
$c->deger = 10;
echo $a->deger, ' ', $c->deger, PHP_EOL; // çıktı: 1 10
var_dump($a === $b, $a === $c);
/*
bool(true)
bool(false)
*/Bu davranış dizilerden farklıdır: bir diziyi atadığınızda PHP onu kopyalar gibi davranır, nesnede ise aynı nesne paylaşılır. Sığ ve derin kopya farkı, __clone() ve nesnelerde == ile === karşılaştırması için clone ve nesne karşılaştırma yazısına, & ile kurulan gerçek referanslar için PHP referansları yazısına bakabilirsiniz.
PHP 8.4: new ifadesini parantezsiz zincirlemek
Bir nesneyi oluşturup hemen metodunu çağırmak için eskiden new ifadesini parantez içine almanız gerekiyordu: (new Sepet())->ekle(...). PHP 8.4 sürüm notlarına göre kurucu argümanı alan new ifadeleri artık doğrudan zincirlenebiliyor; dış parantezleri yazmak zorunda değilsiniz. Bu yazım PHP 8.4 veya sonrasını gerektirir; daha eski sürümlerde parantezli yazımı kullanmanız gerekir. Birçok sunucu hâlâ eski sürümleri çalıştırdığı için kodunuzun nerede çalışacağını göz önünde bulundurun.
<?php
class Sepet
{
private array $kalemler = [];
public function ekle(string $ad, float $tutar): static
{
$this->kalemler[$ad] = $tutar;
return $this;
}
public function toplam(): float
{
return array_sum($this->kalemler);
}
}
// Her sürümde çalışan yazım
echo (new Sepet())->ekle('Kalem', 12.5)->ekle('Defter', 40)->toplam(), PHP_EOL; // çıktı: 52.5
// PHP 8.4 ve sonrası: dış parantez gerekmez
echo new Sepet()->ekle('Silgi', 7.5)->toplam(), PHP_EOL; // çıktı: 7.5ekle() metodunun $this döndürmesi, akıcı arayüz (fluent interface) denen zincirleme yazımı mümkün kılıyor. Dönüş tipi olarak static yazmak, alt sınıflarda da doğru tipin döndüğünü belirtir. PHP 8.4 ile gelen diğer yenilikleri PHP 8.4 yenilikler yazısında topluca bulabilirsiniz.
Yıkıcı metot: __destruct()
__destruct(), bir nesneye işaret eden başka hiçbir başvuru kalmadığında ya da betik kapanırken çağrılır. Dosya veya bağlantı kapatmak gibi temizlik işleri için kullanılabilir. Kurucuda olduğu gibi, alt sınıf kendi yıkıcısını tanımlarsa üst sınıfın yıkıcısı kendiliğinden çalışmaz; parent::__destruct() ile açıkça çağırmanız gerekir. Kılavuz ayrıca betik kapanışında çağrılan bir yıkıcının içinden istisna fırlatmanın ölümcül hataya yol açtığını belirtir.
<?php
class Gunluk
{
public function __construct(private string $ad)
{
echo "{$this->ad} açıldı\n";
}
public function __destruct()
{
echo "{$this->ad} kapandı\n";
}
}
$g = new Gunluk('rapor.log');
$g = null; // son başvuru gitti, yıkıcı hemen çalışır
echo "Betik devam ediyor\n";
/*
rapor.log açıldı
rapor.log kapandı
Betik devam ediyor
*/Uygulamalı örnek: küçük bir banka hesabı sınıfı
Şimdiye kadar gördüklerimizi bir araya getirelim. Aşağıdaki sınıf, sahibini promotion ile alıyor, bakiyeyi dışarıdan doğrudan değiştirilemeyen bir private özellikte tutuyor ve geçersiz işlemleri istisnayla reddediyor. Metotlar $this döndürdüğü için işlemler zincirlenebiliyor. Bakiye için hem $bakiye özelliği hem de bakiye() metodu var; yukarıda anlattığımız ayrı ad alanı kuralı sayesinde çakışmıyorlar.
<?php
class BankaHesabi
{
private float $bakiye = 0;
public function __construct(public readonly string $sahip) {}
public function yatir(float $tutar): static
{
if ($tutar <= 0) {
throw new InvalidArgumentException('Tutar pozitif olmalı');
}
$this->bakiye += $tutar;
return $this;
}
public function cek(float $tutar): static
{
if ($tutar > $this->bakiye) {
throw new RuntimeException('Yetersiz bakiye');
}
$this->bakiye -= $tutar;
return $this;
}
public function bakiye(): float
{
return $this->bakiye;
}
}
$h = new BankaHesabi('Mehmet');
$h->yatir(500)->cek(120);
echo $h->sahip, ': ', $h->bakiye(), ' TL', PHP_EOL; // çıktı: Mehmet: 380 TL
try {
$h->cek(1000);
} catch (RuntimeException $e) {
echo 'Hata: ', $e->getMessage(), PHP_EOL; // çıktı: Hata: Yetersiz bakiye
}
$h->bakiye = 1000000;Fatal error: Uncaught Error: Cannot access private property BankaHesabi::$bakiye in /yol/ornek.php:42
Stack trace:
#0 {main}
thrown in /yol/ornek.php on line 42Son satır, kapsüllemenin (encapsulation) neden işe yaradığını gösteriyor: bakiye yalnızca sınıfın kendi kuralları üzerinden değişebiliyor. İstisna fırlatma ve yakalamanın ayrıntıları için try catch ve exception yazısına bakabilirsiniz.
Sınıf adını dinamik kullanmak, instanceof ve ::class
new ile birlikte sınıf adını bir değişkende de tutabilirsiniz: $ad = 'Urun'; new $ad(); yazımı geçerlidir. Sınıf bir namespace içindeyse değişkendeki adın tam nitelikli olması gerekir. Bir nesnenin hangi sınıftan geldiğini instanceof operatörüyle sınayabilir, sınıfın tam adını ise ::class ile alabilirsiniz. PHP 8.0'dan beri ::class nesne üzerinde de çalışır ve get_class() ile aynı sonucu verir.
<?php
class Fatura {}
$sinifAdi = 'Fatura';
$f = new $sinifAdi();
var_dump($f instanceof Fatura); // çıktı: bool(true)
echo $f::class, PHP_EOL; // çıktı: Fatura
echo get_class($f), PHP_EOL; // çıktı: Fatura
echo Fatura::class, PHP_EOL; // çıktı: FaturaBir nesne null olabiliyorsa PHP 8.0 ile gelen nullsafe operatörü ?-> işinizi kolaylaştırır: sol taraf null ise zincirin geri kalanı atlanır ve sonuç null olur. Kılavuz bu operatörü, null değerinin beklenen ve geçerli bir durum olduğu yerlerde önerir; bir hatayı bildirmek için istisna fırlatmak daha uygundur.
<?php
class Adres
{
public function __construct(public string $sehir) {}
}
class Kullanici
{
public function __construct(public ?Adres $adres = null) {}
}
$k1 = new Kullanici(new Adres('İzmir'));
$k2 = new Kullanici();
var_dump($k1->adres?->sehir); // çıktı: string(6) "İzmir"
var_dump($k2->adres?->sehir); // çıktı: NULLSık yapılan hatalar

- Dinamik özellik oluşturmak: Sınıfta tanımlı olmayan bir özelliğe değer atamak PHP 8.2'den beri kullanımdan kaldırılmış durumdadır ve uyarı üretir. Özelliği sınıfta tanımlayın; gerçekten serbest adlı alanlara ihtiyacınız varsa __get() ve __set() sihirli metotlarını kullanın.
- Tipli özelliği başlatmadan okumak: "must not be accessed before initialization" hatası, değer atanmamış tipli bir özelliği okuduğunuzu söyler. Başlangıç değeri verin veya kurucuda atayın.
- Statik olmayan metodu statik çağırmak:
Sinif::metot()yazımı, metotstaticdeğilse PHP 8.0'dan beriErrorfırlatır. - Nesne atamasını kopya sanmak:
$b = $aaynı nesneyi paylaşır; bağımsız kopya içinclonekullanın. - Alt sınıfta parent kurucuyu çağırmayı unutmak: Alt sınıf kendi kurucusunu yazarsa üst sınıfın kurucusu otomatik çalışmaz. Ayrıntısı kalıtım yazısında.
<?php
class Ayar
{
public string $tema = 'acik';
}
$a = new Ayar();
$a->dil = 'tr';
echo $a->dil, PHP_EOL;Deprecated: Creation of dynamic property Ayar::$dil is deprecated in /yol/ornek.php on line 8
trSık Sorulan Sorular
Sınıf ile nesne arasındaki fark nedir?
Sınıf bir tanımdır: hangi özelliklerin ve metotların olacağını söyler. Nesne bu tanımdan new ile üretilmiş somut bir örnektir ve kendi özellik değerlerini taşır. Bir sınıftan istediğiniz kadar nesne üretebilirsiniz.
$this ne zaman kullanılamaz?
$this yalnızca bir nesne üzerinden çağrılan metotlarda tanımlıdır. static olarak işaretlenmiş metotlarda nesne olmadığı için $this kullanılamaz; bunun yerine self:: veya static:: kullanılır.
Constructor promotion eski sürümlerde çalışır mı?
Hayır. Promotion PHP 8.0 ile geldi; PHP 7.x sürümlerinde sözdizimi hatası verir. final özelliklerle promotion ise PHP 8.5 ve sonrasını gerektirir.
Bir sınıfta birden çok kurucu tanımlayabilir miyim?
Hayır, PHP sınıf başına tek bir __construct() destekler. Farklı kaynaklardan nesne üretmek için statik oluşturucu metotlar (örneğin fromJson()) yazmanız önerilir.
new Sinif()->metot() yazımı neden hata veriyor?
Bu yazım PHP 8.4 ile geldi. Sunucunuz daha eski bir sürüm çalıştırıyorsa ifadeyi (new Sinif())->metot() biçiminde parantez içine alın.
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 · Görünürlük ve readonly · static ve late static binding · Sihirli metotlar · clone ve nesne karşılaştırma · PHP nedir