PHP'de sihirli metotlar (magic methods), adları iki alt çizgiyle (__) başlayan ve belirli bir olay gerçekleştiğinde PHP tarafından kendiliğinden çağrılan özel metotlardır. Örneğin tanımsız bir özelliği okumak __get(), tanımsız bir metodu çağırmak __call(), nesneyi echo ile yazdırmak __toString(), nesneyi fonksiyon gibi çağırmak __invoke(), serialize() ise __serialize() metodunu tetikler. Bu metotları siz doğrudan çağırmazsınız; PHP'nin varsayılan davranışını değiştirmek için tanımlarsınız.
Bu yazıda özellik aşırı yüklemesi (__get, __set, __isset, __unset), metot aşırı yüklemesi (__call, __callStatic), __toString ve Stringable interface'i, __invoke, __serialize/__unserialize, __debugInfo ve __set_state metotlarını çalışan örneklerle anlatıyoruz. __construct ve __destruct için sınıf ve nesne yazısına, __clone için clone yazısına, serinin tamamı için PHP öğrenme rehberine bakabilirsiniz.
Sihirli metotların genel kuralları
Kılavuz, __ ile başlayan bütün metot adlarının PHP için ayrıldığını, bu yüzden PHP'nin davranışını değiştirmek dışında bu öneki kullanmamanızı önerir. Sihirli sayılan metotlar şunlardır: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __serialize(), __unserialize(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() ve __debugInfo().
İki kural daha var. Birincisi, __construct(), __destruct() ve __clone() dışındaki bütün sihirli metotlar public olmak zorundadır; değilse PHP bir uyarı (E_WARNING) üretir. İkincisi, bu metotlarda tip bildirimi kullanırsanız tipler kılavuzdaki imzayla birebir aynı olmalıdır, yoksa ölümcül hata alırsınız. Kurucu ve yıkıcı ise dönüş tipi bildiremez.
<?php
class Hatali
{
private function __get(string $name): mixed
{
return null;
}
}
echo 'tanım yüklendi', PHP_EOL;Warning: The magic method Hatali::__get() must have public visibility in /yol/ornek.php on line 4
tanım yüklendi<?php
class Hatali
{
public function __toString(): int
{
return 1;
}
}Fatal error: Hatali::__toString(): Return type must be string when declared in /yol/ornek.php on line 4
Stack trace:
#0 {main}__get, __set, __isset ve __unset: özellik aşırı yüklemesi

PHP'deki "aşırı yükleme" (overloading) kavramı, aynı adla farklı parametre listeleri tanımlamak anlamına gelen diğer dillerdeki kullanımdan farklıdır; kılavuz bunu özellikle not eder. PHP'de aşırı yükleme, erişilemeyen özellik ve metotlarla etkileşime girildiğinde çalışan sihirli metotlar demektir. "Erişilemeyen" iki durumu kapsar: hiç tanımlanmamış üyeler ve o kapsamdan görünmeyen (protected veya private) üyeler.
__set(string $name, mixed $value): void: Erişilemeyen bir özelliğe yazılırken çalışır.__get(string $name): mixed: Erişilemeyen bir özellik okunurken çalışır.__isset(string $name): bool: Erişilemeyen bir özellik üzerindeisset()veyaempty()çağrıldığında çalışır.__unset(string $name): void: Erişilemeyen bir özellik üzerindeunset()çağrıldığında çalışır.
<?php
class Ayarlar
{
private array $veri = [];
public function __set(string $name, mixed $value): void
{
echo "__set($name)\n";
$this->veri[$name] = $value;
}
public function __get(string $name): mixed
{
echo "__get($name)\n";
return $this->veri[$name] ?? null;
}
public function __isset(string $name): bool
{
echo "__isset($name)\n";
return isset($this->veri[$name]);
}
public function __unset(string $name): void
{
echo "__unset($name)\n";
unset($this->veri[$name]);
}
}
$a = new Ayarlar();
$a->tema = 'koyu';
echo $a->tema, PHP_EOL;
var_dump(isset($a->tema));
unset($a->tema);
var_dump(isset($a->tema));
var_dump($a->dil);
/*
__set(tema)
__get(tema)
koyu
__isset(tema)
bool(true)
__unset(tema)
__isset(tema)
bool(false)
__get(dil)
NULL
*/Çıktıdaki sıra, hangi işlemin hangi metodu tetiklediğini gösteriyor. isset() çağrısı __isset() metodunu çalıştırdı; bu metodu tanımlamasaydınız, sihirli yoldan okunabilen bir özellik için bile isset() her zaman false dönerdi. Bu yüzden __get() yazan bir sınıfta __isset() yazmayı unutmamak önemlidir. Dinamik özellik oluşturmanın PHP 8.2'de kullanımdan kaldırılması, PHP 8.2 sürüm notlarına göre __get()/__set() kullanan sınıfları etkilemez; serbest adlı alanlar için bu metotlar hâlâ önerilen yoldur.
Tanımlı ve görünür özellikler sihirli metodu tetiklemez
Sihirli metotlar yalnızca erişilemeyen özelliklerde devreye girer. Tanımlı ve o kapsamdan görünen bir özelliği okumak normal şekilde çalışır. Private bir özellik ise sınıf dışından erişilemediği için dışarıdan okunduğunda __get() çağrılır; sınıfın içinden okunduğunda çağrılmaz.
<?php
class Profil
{
public string $ad = 'Ali';
private string $gizli = 'sır';
public function __get(string $name): mixed
{
return "[$name sihirli yoldan okundu]";
}
public function gizliyiOku(): string
{
return $this->gizli;
}
}
$p = new Profil();
echo $p->ad, PHP_EOL; // çıktı: Ali
echo $p->gizli, PHP_EOL; // çıktı: [gizli sihirli yoldan okundu]
echo $p->gizliyiOku(), PHP_EOL; // çıktı: sır
echo $p->yok, PHP_EOL; // çıktı: [yok sihirli yoldan okundu]Özyineleme koruması ve diğer ayrıntılar
PHP, bir aşırı yükleme metodunu kendi içinden tekrar çağırmaz. Örneğin __get() içinde return $this->$name; yazarsanız sonsuz döngüye girmezsiniz; PHP bu kez gerçek özelliği arar, bulamazsa null döndürür ve bir uyarı üretir. Kılavuz ayrıca şu noktaları belirtir: __set() metodunun dönüş değeri yok sayılır, $a = $nesne->b = 8; gibi zincirleme atamalarda __get() çağrılmaz, bu metotların hiçbir argümanı referansla alınamaz ve static bağlamda çalışmadıkları için static olarak bildirilmemelidir.
<?php
class Dongu
{
public function __get(string $name): mixed
{
return $this->$name; // aynı sihirli metodu yeniden tetiklemez
}
}
var_dump((new Dongu())->renk);Warning: Undefined property: Dongu::$renk in /yol/ornek.php on line 6
NULL__call ve __callStatic: metot aşırı yüklemesi
__call(string $name, array $arguments): mixed, bir nesne üzerinde erişilemeyen bir metot çağrıldığında; __callStatic() ise aynı durum static bağlamda yaşandığında çalışır. $name çağrılan metodun adını, $arguments ise geçirilen argümanları sıralı bir dizi olarak taşır. Kılavuzun örneğinde belirtildiği gibi $name değeri büyük küçük harfe duyarlıdır, yani çağrıda nasıl yazıldıysa öyle gelir.
<?php
class Kayit
{
private array $alanlar = [];
public function __call(string $name, array $arguments): mixed
{
if (str_starts_with($name, 'set')) {
$this->alanlar[lcfirst(substr($name, 3))] = $arguments[0];
return $this;
}
if (str_starts_with($name, 'get')) {
return $this->alanlar[lcfirst(substr($name, 3))] ?? null;
}
throw new BadMethodCallException("Tanımsız metot: $name");
}
public static function __callStatic(string $name, array $arguments): mixed
{
echo "static çağrı: $name(" . implode(', ', $arguments) . ")\n";
return null;
}
}
$k = (new Kayit())->setAd('Elif')->setSehir('Bursa');
echo $k->getAd(), ' / ', $k->getSehir(), PHP_EOL; // çıktı: Elif / Bursa
Kayit::bul(5, 'aktif'); // çıktı: static çağrı: bul(5, aktif)
try {
$k->sil();
} catch (BadMethodCallException $e) {
echo $e->getMessage(), PHP_EOL; // çıktı: Tanımsız metot: sil
}Tanımadığınız adlar için mutlaka bir istisna fırlatın. Aksi hâlde yanlış yazılmış her metot adı sessizce null döndürür ve hatayı bulmak zorlaşır. __call() ile karşılanan metotlar sınıf tanımında görünmediği için sınıfı okuyan kişi hangi adların geçerli olduğunu koddan çıkarmak zorunda kalır; sınırlı sayıda ve öngörülebilir ad için gerçek metot yazmak çoğu zaman daha anlaşılırdır.
__toString ve Stringable

__toString(): string, nesne bir string gibi kullanıldığında, örneğin echo $nesne, string birleştirme veya (string) dönüşümünde çalışır. PHP 8.0'dan beri __toString() metodu olan her sınıf, Stringable interface'ini örtük olarak uygular; yani instanceof Stringable kontrolünden geçer. Kılavuz yine de interface'i açıkça yazmanızı önerir. Stringable interface'inin asıl değeri, string|Stringable union tipiyle hem düz string hem de stringe çevrilebilen nesne kabul eden fonksiyonlar yazabilmektir.
<?php
declare(strict_types=1);
class Para implements Stringable
{
public function __construct(private int $kurus) {}
public function __toString(): string
{
return number_format($this->kurus / 100, 2, ',', '.') . ' TL';
}
}
class Etiket
{
public function __toString(): string
{
return 'indirim';
}
}
function yazdir(string|Stringable $deger): void
{
echo "Değer: $deger", PHP_EOL;
}
function sadeceString(string $s): void
{
echo $s, PHP_EOL;
}
$p = new Para(149990);
echo $p, PHP_EOL; // çıktı: 1.499,90 TL
yazdir($p); // çıktı: Değer: 1.499,90 TL
yazdir(new Etiket()); // çıktı: Değer: indirim
var_dump(new Etiket() instanceof Stringable); // çıktı: bool(true)
sadeceString((string) $p); // çıktı: 1.499,90 TL
sadeceString($p);1.499,90 TL
Değer: 1.499,90 TL
Değer: indirim
bool(true)
1.499,90 TL
Fatal error: Uncaught TypeError: sadeceString(): Argument #1 ($s) must be of type string, Para given, called in /yol/ornek.php on line 38 and defined in /yol/ornek.php:27
Stack trace:
#0 /yol/ornek.php(38): sadeceString(Object(Para))
#1 {main}
thrown in /yol/ornek.php on line 27Etiket sınıfı implements Stringable yazmadığı hâlde kontrolden geçti. Son satır ise kılavuzdaki önemli bir uyarıyı doğruluyor: strict_types açıkken bir Stringable nesne, yalnızca string bekleyen bir parametreye geçirilemez. Bu durumda ya açıkça (string) dönüşümü yapmalı ya da parametre tipini string|Stringable yapmalısınız. Strict mod olmadan nesne kendiliğinden stringe çevrilir. Tip denetiminin ayrıntıları tip bildirimleri ve strict_types yazısında.
__invoke: nesneyi fonksiyon gibi çağırmak
__invoke() tanımlı bir nesneyi $nesne(...) biçiminde fonksiyon gibi çağırabilirsiniz ve bu nesne is_callable() kontrolünden true döner. Bu, yapılandırılabilir geri çağırma (callback) fonksiyonları için kullanışlıdır: ayarı kurucuda alırsınız, nesneyi de doğrudan usort() veya array_map() gibi fonksiyonlara verirsiniz.
<?php
class AlanaGoreSirala
{
public function __construct(private string $alan) {}
public function __invoke(array $a, array $b): int
{
return $a[$this->alan] <=> $b[$this->alan];
}
}
$urunler = [
['ad' => 'Monitör', 'fiyat' => 5200],
['ad' => 'Fare', 'fiyat' => 300],
['ad' => 'Klavye', 'fiyat' => 750],
];
usort($urunler, new AlanaGoreSirala('fiyat'));
echo implode(', ', array_column($urunler, 'ad')), PHP_EOL; // çıktı: Fare, Klavye, Monitör
$karsilastir = new AlanaGoreSirala('ad');
var_dump(is_callable($karsilastir)); // çıktı: bool(true)
echo $karsilastir(['ad' => 'b'], ['ad' => 'a']), PHP_EOL; // çıktı: 1Aynı işi bir closure ile de yapabilirsiniz; __invoke() ise karşılaştırıcıya bir ad, tip ve gerekirse ek metotlar vermenizi sağlar. Closure seçenekleri için anonim fonksiyon ve arrow function yazısına, sıralama fonksiyonlarının tamamı için sort ve usort yazısına bakabilirsiniz.
__serialize ve __unserialize
serialize() bir nesneyi saklanabilir bir metne çevirirken sınıfta __serialize(): array varsa önce onu çağırır. Bu metot, nesnenin saklanacak hâlini temsil eden bir ilişkisel dizi döndürmelidir; dizi döndürmezse TypeError fırlatılır. Dizinin anahtarlarının özellik adlarıyla aynı olması gerekmez. unserialize() ise metni geri çevirirken __unserialize(array $data): void metoduna bu diziyi verir ve nesneyi yeniden kurmak size kalır. Bu ikili PHP 7.4 ile geldi.
Tipik kullanım, saklanamayan veya saklanmaması gereken kaynakları (veritabanı bağlantısı, açık dosya) dışarıda bırakıp geri yüklemede yeniden oluşturmaktır.
<?php
class Oturum
{
private ?string $baglanti = null;
public function __construct(private string $kullanici, private string $rol)
{
$this->baglan();
}
private function baglan(): void
{
$this->baglanti = "bağlı:{$this->kullanici}";
}
public function __serialize(): array
{
return ['k' => $this->kullanici, 'r' => $this->rol]; // bağlantı saklanmaz
}
public function __unserialize(array $data): void
{
$this->kullanici = $data['k'];
$this->rol = $data['r'];
$this->baglan(); // kaynak yeniden kurulur
}
public function durum(): string
{
return "$this->baglanti ($this->rol)";
}
}
$metin = serialize(new Oturum('deniz', 'editor'));
echo $metin, PHP_EOL;
// çıktı: O:6:"Oturum":2:{s:1:"k";s:5:"deniz";s:1:"r";s:6:"editor";}
$geri = unserialize($metin);
echo $geri->durum(), PHP_EOL; // çıktı: bağlı:deniz (editor)Kılavuz iki öncelik kuralı verir: aynı sınıfta hem __serialize() hem eski __sleep() varsa yalnızca __serialize(), hem __unserialize() hem __wakeup() varsa yalnızca __unserialize() çağrılır. PHP 8.5 ile __sleep() ve __wakeup() "yumuşak" biçimde kullanımdan kaldırıldı (soft-deprecated): geriye uyumluluk için duruyorlar ama yeni ve mevcut kodun __serialize()/__unserialize() ikilisine geçmesi öneriliyor.
__debugInfo ve __set_state
__debugInfo(): array, var_dump() bir nesneyi dökerken hangi özelliklerin gösterileceğini belirler. Metot tanımlı değilse public, protected ve private bütün özellikler gösterilir. Parola veya API anahtarı gibi değerlerin hata ayıklama çıktısına, dolayısıyla günlük dosyalarına düşmesini engellemek için kullanışlıdır. PHP 8.5 ile bu metottan null döndürmek kullanımdan kaldırıldı; gösterecek bir şey yoksa boş dizi döndürün.
<?php
class ApiIstemci
{
public function __construct(private string $url, private string $anahtar) {}
public function __debugInfo(): array
{
return ['url' => $this->url, 'anahtar' => '***gizli***'];
}
}
var_dump(new ApiIstemci('https://api.example.com', 'sk-123456'));
/*
object(ApiIstemci)#1 (2) {
["url"]=>
string(23) "https://api.example.com"
["anahtar"]=>
string(11) "***gizli***"
}
*/__set_state(array $properties): object ise static bir metottur ve var_export() ile dışa aktarılmış bir nesne kodu çalıştırıldığında çağrılır. var_export(), nesneyi aşağıdaki çıktıda görüldüğü gibi \Renk::__set_state(array(...)) biçiminde yazar; bu kodun geri yüklenebilmesi için sınıfın metodu tanımlaması gerekir. Kılavuz, var_export() fonksiyonunun metodun var olup olmadığını kontrol etmediğini, bu yüzden metodu olmayan sınıfları geri yüklemeye çalışmanın Error ile sonuçlanacağını hatırlatır.
<?php
class Renk
{
public int $r = 0;
public int $g = 0;
public int $b = 0;
public static function __set_state(array $properties): object
{
$c = new self();
$c->r = $properties['r'];
$c->g = $properties['g'];
$c->b = $properties['b'];
return $c;
}
}
$c = new Renk();
$c->r = 255;
$c->g = 128;
echo var_export($c, true), PHP_EOL;
/*
\Renk::__set_state(array(
'r' => 255,
'g' => 128,
'b' => 0,
))
*/Uygulamalı örnek: salt okunur bir yapılandırma nesnesi
Şimdi birkaç sihirli metodu tek bir sınıfta birleştirelim. Yapilandirma sınıfı bir diziden oluşturuluyor; değerleri $cfg->db_host gibi özellik yazımıyla okutuyor, isset() ve empty() çağrılarını doğru yanıtlıyor, ama her türlü yazma ve silme girişimini istisnayla reddediyor. __toString() ise anahtarların bir özetini veriyor, değerleri göstermiyor. Böylece nesne yanlışlıkla bir günlük satırına yazdırılsa bile parola gibi değerler sızmıyor.
<?php
final class Yapilandirma implements Stringable
{
public function __construct(private array $degerler) {}
public function __get(string $name): mixed
{
if (!array_key_exists($name, $this->degerler)) {
throw new OutOfBoundsException("Tanımsız ayar: $name");
}
return $this->degerler[$name];
}
public function __isset(string $name): bool
{
return isset($this->degerler[$name]);
}
public function __set(string $name, mixed $value): void
{
throw new LogicException("Ayarlar salt okunur: $name");
}
public function __unset(string $name): void
{
throw new LogicException("Ayarlar silinemez: $name");
}
public function __toString(): string
{
return 'Yapilandirma(' . implode(', ', array_keys($this->degerler)) . ')';
}
}
$cfg = new Yapilandirma(['db_host' => 'localhost', 'db_pass' => 's3cr3t', 'debug' => false]);
echo $cfg->db_host, PHP_EOL; // çıktı: localhost
var_dump(isset($cfg->debug)); // çıktı: bool(true)
var_dump(empty($cfg->debug)); // çıktı: bool(true)
echo "Yüklendi: $cfg", PHP_EOL; // çıktı: Yüklendi: Yapilandirma(db_host, db_pass, debug)
foreach ([fn() => $cfg->debug = true, fn() => $cfg->eposta] as $dene) {
try {
$dene();
} catch (LogicException | OutOfBoundsException $e) {
echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}
}
/*
LogicException: Ayarlar salt okunur: debug
OutOfBoundsException: Tanımsız ayar: eposta
*/empty($cfg->debug) satırına dikkat edin: empty() önce __isset() metodunu çağırır, true gelirse değeri okumak için __get() metoduna gider. Değer false olduğu için sonuç true çıktı. __isset() yazılmamış olsaydı her iki kontrol de sihirli alanı "yok" sayardı. Tanımsız bir ayarı sessizce null döndürmek yerine istisnayla bildirmek de yazım hatalarını erkenden yakalamanızı sağlar. Uyarı ve tanımsız anahtar mesajlarının genel çözümleri için undefined variable ve array key yazısına bakabilirsiniz.
Sihirli metotlar özet tablosu
| Metot | Ne zaman çalışır | Dönüş |
|---|---|---|
| __get($name) | Erişilemeyen özellik okunurken | mixed |
| __set($name, $value) | Erişilemeyen özelliğe yazılırken | void |
| __isset($name) | Erişilemeyen özellikte isset() veya empty() | bool |
| __unset($name) | Erişilemeyen özellikte unset() | void |
| __call($name, $arguments) | Nesnede erişilemeyen metot çağrısı | mixed |
| __callStatic($name, $arguments) | Static bağlamda erişilemeyen metot çağrısı | mixed |
| __toString() | Nesne string olarak kullanıldığında | string |
| __invoke(...) | Nesne fonksiyon gibi çağrıldığında | mixed |
| __serialize() | serialize() sırasında | array |
| __unserialize($data) | unserialize() sırasında | void |
| __debugInfo() | var_dump() sırasında | array |
| __set_state($properties) | var_export() çıktısı çalıştırıldığında | object |
Sık yapılan hatalar

- __get yazıp __isset yazmamak:
isset($nesne->alan)veempty()sihirli alanlar için yanlış sonuç verir. - Bilinmeyen adlarda sessizce null döndürmek:
__call()içinde tanımadığınız ad için istisna fırlatın; aksi hâlde yazım hataları gizlenir. - Sihirli metodu public yapmamak: Kurucu, yıkıcı ve
__clone()dışındakiler public olmalıdır. - İmzada farklı tip kullanmak:
__toString(): intgibi kılavuzdan farklı bir tip ölümcül hata verir. - strict_types açıkken Stringable nesneyi string parametresine geçirmek:
TypeErroralırsınız;string|Stringablekullanın. - Yeni kodda __sleep/__wakeup kullanmak: PHP 8.5 ile soft-deprecated oldular;
__serialize()ve__unserialize()tercih edin.
Sık Sorulan Sorular
__get() her özellik okumasında çalışır mı?
Hayır. Yalnızca tanımlanmamış veya o kapsamdan görünmeyen (protected/private) özellikler okunurken çalışır. Tanımlı ve görünür bir public özellik normal şekilde okunur.
Stringable interface'ini açıkça yazmam gerekir mi?
PHP 8.0 ve sonrasında __toString() tanımlayan her sınıf Stringable interface'ini örtük olarak uygular. Kılavuz yine de okunabilirlik için implements Stringable yazmanızı önerir.
__call() ile gerçek bir metodu ezebilir miyim?
Hayır. __call() yalnızca erişilemeyen metotlar için çalışır. Sınıfta tanımlı ve çağıran kapsamdan görünen bir metot varsa doğrudan o metot çalışır.
__set() metodundan değer döndürmek işe yarar mı?
Hayır. Kılavuza göre PHP atama operatörünü işleme biçimi nedeniyle __set() metodunun dönüş değerini yok sayar; bu yüzden imzası void dönüşlüdür. $a = $nesne->b = 8; gibi zincirleme bir atamada $a değişkenine atanan değer de __get() çağrılarak okunmaz.
__toString() içinden istisna fırlatabilir miyim?
PHP 7.4 ve sonrasında evet. Kılavuza göre daha eski sürümlerde __toString() içinden istisna fırlatmak ölümcül hataya yol açıyordu.
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 · clone ve __clone · Property hooks · Tip bildirimleri ve strict_types · json_encode ve json_decode