---
title: 'Sihirli Yöntemler'
source: 'https://academia.sh/tr/kurslar/php-nesne-modeli/sihirli-yontemler'
course: 'Nesneye Dayalı PHP ve Standartlar'
language: tr
updated: '2026-10-21T05:03:35+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - '__get, __set, __isset, __call ve __toString yöntemlerini tanımlar ve ne zaman çağrıldıklarını gözlemler'
    - 'Özellik okumasının bir yönteme çevrildiği durumda iç adın değişmesinin kullananı nasıl etkilediğini ölçer'
    - '__get bilinmeyen adı reddettiğinde aynı değişikliğin kullananı durdurduğunu gösterir'
---

# Sihirli Yöntemler

__get, __set, __isset, __call ve __toString yöntemleri, okunan bir adın yöntemden çözülmesi ve bu çözümün değişince kullananın uğradığı örtük ve kıran sonuçlar.

Statik Üyeler dersinde sabitin değeri değişince kullanan hata almadan kaydı. Sihirli yöntemlerde (magic method) aynı sessizlik daha büyük bir alana yayılır, çünkü bir özelliği okumak ya da bir yöntemi çağırmak artık sınıfın içindeki bir yönteme çevrilebilir. Soru, bu çevirinin iç adı değişince kullananın neyi gördüğüdür.

## Sihirli Yöntemler

Sihirli yöntemler adı iki alt çizgiyle başlayan, PHP'nin belirli durumlarda kendisi çağırdığı yöntemlerdir. `__get` olmayan ya da erişilemeyen bir özellik okunduğunda, `__set` yazıldığında, `__isset` `isset()` ile sorulduğunda, `__call` olmayan bir yöntem çağrıldığında, `__toString` nesne metin olarak kullanıldığında çalışır.

```php
<?php
// Ayar.php — okuma, yazma, sorma, yontem cagrisi ve metne cevirmeyi yakalayan sinif
class Ayar
{
    private array $degerler = [];

    public function __set(string $ad, mixed $deger): void
    {
        $this->degerler[$ad] = $deger;
    }

    public function __get(string $ad): mixed
    {
        return $this->degerler[$ad] ?? null;
    }

    public function __isset(string $ad): bool
    {
        return isset($this->degerler[$ad]);
    }

    public function __call(string $ad, array $arguman): string
    {
        return $ad . '(' . count($arguman) . ')';
    }

    public function __toString(): string
    {
        return 'Ayar[' . count($this->degerler) . ']';
    }
}
```

```php
<?php
// ayar_kullan.php — sihirli yontemler kullananin yazdigi ifadelerden tetiklenir
require 'Ayar.php';

$ayar = new Ayar();
$ayar->tema = 'koyu';
echo $ayar->tema, "\n";
var_dump(isset($ayar->tema), isset($ayar->dil));
echo $ayar->kaydet(1, 2), "\n";
echo $ayar, "\n";
```

```
koyu
bool(true)
bool(false)
kaydet(2)
Ayar[1]
```

**NM22.** `tema` diye bir özellik tanımlı değildi ve kullanan yine de yazıp okuyabildi: `__set` değeri `$degerler` dizisine koydu, `__get` oradan çıkardı. `kaydet()` diye bir yöntem de yoktu, `__call` adı ve argüman sayısını geri verdi. Bu, Sınıflar ve Nesneler dersindeki dinamik özellikten farklıdır: orada özellik nesnenin üzerine kuruluyordu ve bir kullanımdan kaldırma uyarısı (`E_DEPRECATED`) doğuruyordu, burada ise tanımlı bir yöntem karşılıyor.

## __unset ve __callStatic

`__unset`, erişilemeyen bir özellik `unset()` ile silinmek istendiğinde çalışır; `__isset` ile birlikte yazılır, çünkü ikisi aynı veri yapısına bakar. `__callStatic` ise `__call`'ın statik karşılığıdır: sınıfta olmayan bir yöntem `Sinif::ad()` biçiminde çağrıldığında devreye girer. İkisi de adı ve argümanları yöntemin gövdesine verir.

```php
<?php
// Kayit.php — silmeyi ve statik cagriyi yakalayan sinif
class Kayit
{
    private array $veri = ['a' => 1, 'b' => 2];

    public function __isset(string $ad): bool
    {
        return isset($this->veri[$ad]);
    }

    public function __unset(string $ad): void
    {
        unset($this->veri[$ad]);
    }

    public static function __callStatic(string $ad, array $arguman): string
    {
        return 'statik ' . $ad . '(' . count($arguman) . ')';
    }
}
```

```php
<?php
// kayit_kullan.php — unset ve statik cagri sihirli yontemlere gider
require 'Kayit.php';

$kayit = new Kayit();
var_dump(isset($kayit->a));
unset($kayit->a);
var_dump(isset($kayit->a));
echo Kayit::bul(7), "\n";
```

```
bool(true)
bool(false)
statik bul(1)
```

`$veri` özel olduğu için `$kayit->a` dışarıdan erişilemezdi; `isset` ve `unset` bu yüzden sihirli yöntemlere düştü ve dizideki anahtarı sildi. `__unset` yazılmasaydı `unset($kayit->a)` hiçbir şey yapmadan geçerdi.

## Sihirli Yöntem Yalnız Erişilemeyen Adda Çalışır

Sihirli okuma yöntemi bir kısayol değildir: PHP önce adı sınıfın gerçek özelliklerinde arar, bulursa yöntemi hiç çağırmaz. `__get` yalnız ad yoksa ya da çağıran yerden görünmüyorsa çalışır. Bu ayrım, `public` bir özelliği sonradan sihirli yönteme taşımanın kullananı nasıl etkilediğini belirler.

```php
<?php
// Kasa.php — gercek ozellik varken __get calismaz
class Kasa
{
    public string $acik = 'a';
    private string $gizli = 'g';

    public function __get(string $ad): string
    {
        return 'sihirli:' . $ad;
    }
}
```

```php
<?php
// kasa_kullan.php — __get yalniz erisilemeyen adda calisir
require 'Kasa.php';

$kasa = new Kasa();
echo $kasa->acik, ' ', $kasa->gizli, ' ', $kasa->yok, "\n";

unset($kasa->acik);
echo $kasa->acik, "\n";
```

```
a sihirli:gizli sihirli:yok
sihirli:acik
```

`acik` gerçek ve görünür olduğu için sihirli yönteme uğramadı; `gizli` dışarıdan görünmediği, `yok` hiç olmadığı için uğradı. `acik` silindikten sonra ise adı gerçek özellik olarak bulunamadı ve `__get` devreye girdi. Bu davranış, sihirli yöntemin gerçek özelliğin yerini sessizce alabileceğini gösterir.

## __toString ve Stringable

`__toString` nesne bir metin bağlamına girdiğinde çalışır: `echo`, birleştirme, metin bekleyen bir fonksiyon parametresi. PHP 8'de bu yöntemi tanımlayan her sınıf, `Stringable` arayüzünü kendiliğinden gerçekleştirmiş sayılır; arayüz bildirimine gerek yoktur. Yöntemi olmayan bir nesne metin bağlamına girerse `Error` doğar.

```php
<?php
// Etiket.php — __toString ve Stringable
class Etiket
{
    public function __construct(private string $ad)
    {
    }

    public function __toString(): string
    {
        return '<' . $this->ad . '>';
    }
}

class Susuz
{
}

$etiket = new Etiket('b');
echo $etiket, ' ', strlen($etiket), ' ', $etiket instanceof Stringable ? 'evet' : 'hayir', "\n";
echo 'Metin: ' . $etiket . "\n";

try {
    echo new Susuz();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
<b> 3 evet
Metin: <b>
Error
```

`strlen()` nesneyi metne çevirip `<b>` üzerinden `3` saydı. `Stringable` bildirimi hiçbir yerde yazılmadığı hâlde `instanceof` doğru çıktı. Sınıfa `__toString` eklemek, o sınıfın nesnelerini metin bekleyen her yere sokar; bu kolaylık, dönüşümün biçimini, kullananın söz hakkı olmadan, sınıfın tek başına belirlemesi demektir.

## __set ve Yazım Hatası

`__set` her bilinmeyen adı kabul ettiğinde, adı yanlış yazan kod hata almaz. Yanlış yazılmış ad dizide yeni bir anahtar olur; doğru ad ise eski değerinde kalır. Okuma tarafında `?? null` bırakılmışsa yanlış adı okumak da sessizce `null` verir.

```php
<?php
// Esnek.php — her adi kabul eden __set ve __get
class Esnek
{
    public string $baslik = '';
    private array $ek = [];

    public function __set(string $ad, mixed $deger): void
    {
        $this->ek[$ad] = $deger;
    }

    public function __get(string $ad): mixed
    {
        return $this->ek[$ad] ?? null;
    }
}

$esnek = new Esnek();
$esnek->baslik = 'ilk';
$esnek->baslk = 'ikinci';
echo $esnek->baslik, ' ', $esnek->baslk, "\n";
var_dump($esnek->baslikk);
```

```
ilk ikinci
NULL
```

`baslk` yazım hatasıydı ve `__set` onu sorgusuz kabul etti; `baslik` ilk değerinde kaldı. `baslikk` okunduğunda da hata çıkmadı. Sihirli yöntemi yazan taraf kabul edilen adları bir listeyle sınırlamazsa, kullanan yazım denetimini kaybeder.

## __call ile Yönlendirme

`__call` sık kullanılan bir amaca hizmet eder: başka bir nesneye yönlendirmek. Sarmalayan sınıf bilmediği her yöntemi içindeki nesneye aktarır. Kullanan sarmalayıcıyı çağırır, ama aslında içerideki nesnenin yöntemi çalışır; içerideki nesnede de olmayan bir ad ise yine `Error` doğurur.

```php
<?php
// Sarmal.php — bilinmeyen yontemi ic nesneye yonlendiren sinif
class Sarmal
{
    public function __construct(private ArrayObject $ic)
    {
    }

    public function __call(string $ad, array $arguman): mixed
    {
        return $this->ic->$ad(...$arguman);
    }
}

$sarmal = new Sarmal(new ArrayObject([3, 1, 2]));
echo $sarmal->count(), "\n";

try {
    $sarmal->yoktur();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
3
Error
```

`count()` sarmalayıcıda tanımlı değildi ve yönlendirme sayesinde `ArrayObject` tarafından yanıtlandı. Bu yönlendirme, sarmalanan sınıfın yöntemlerini sarmalayıcının bildiriminden gizler; hangi adların çalıştığı kodu okuyarak değil çalıştırarak anlaşılır.

## __invoke

`__invoke` bir nesneyi fonksiyon gibi çağırmaya izin verir. Nesne `$nesne(argüman)` biçiminde çağrılır ve PHP yöntemin gövdesini çalıştırır. Nesne bu yüzden `callable` kabul edilir: `array_map` gibi bir fonksiyona geri çağrı olarak verilebilir. Durum taşıyan bir dönüşüm gerektiğinde, bu özellik nesnenin özelliklerini çağrının yanında götürür.

```php
<?php
// Carpan.php — nesneyi islev gibi cagirmak
class Carpan
{
    public function __construct(private int $katsayi)
    {
    }

    public function __invoke(int $sayi): int
    {
        return $sayi * $this->katsayi;
    }
}

$uc = new Carpan(3);
echo $uc(4), ' ', implode(',', array_map($uc, [1, 2, 3])), ' ', var_export(is_callable($uc), true), "\n";
```

```
12 3,6,9 true
```

`$uc(4)` doğrudan `__invoke(4)` oldu; `array_map` nesneyi dizinin her öğesi için çağırdı. Katsayı nesnede durduğu için dönüşümü farklı katsayıyla yeniden kullanmak, yeni bir nesne yaratmaktan ibaret kaldı.

## Ölçüm Çekirdeği

Bu dersin ölçümü de `yayilim.php` dosyasını kullanır; çekirdek ilk derste satır satır açıklanmıştı.

```php
<?php
// yayilim.php — bir degisikligin nereye ulastigini olcen yardimcilar

function yaz(string $dizin, array $dosyalar): void
{
    foreach ($dosyalar as $ad => $icerik) {
        if ($icerik === null) {
            continue; // null: dosya silindi
        }
        $yol = "$dizin/$ad";
        if (!is_dir(dirname($yol))) {
            mkdir(dirname($yol), 0777, true);
        }
        file_put_contents($yol, $icerik);
    }
}

function oku(string $dizin): array
{
    $dosyalar = [];
    $gezgin = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dizin, FilesystemIterator::SKIP_DOTS)
    );
    foreach ($gezgin as $dosya) {
        $ad = substr($dosya->getPathname(), strlen($dizin) + 1);
        $dosyalar[$ad] = file_get_contents($dosya->getPathname());
    }
    return $dosyalar;
}

function kostur(string $dizin): array
{
    $komut = 'cd ' . escapeshellarg($dizin) . ' && php -d display_errors=0 main.php 2>/dev/null';
    exec($komut, $satirlar, $kod);
    return [$kod, implode("\n", $satirlar)];
}

function olc(string $ad, array $taban, array $degisiklik, array $baglanti = []): string
{
    if (array_key_exists('main.php', $degisiklik)) {
        throw new LogicException("$ad: kullanan duzenlenmez");
    }
    yaz("olcum/$ad/once", $taban);
    yaz("olcum/$ad/sonra", array_merge($taban, $degisiklik));
    [$kodOnce, $ciktiOnce] = kostur("olcum/$ad/once");
    [$kodSonra, $ciktiSonra] = kostur("olcum/$ad/sonra");
    if ($kodOnce !== 0) {
        throw new LogicException("$ad: taban calismadi");
    }
    $sinif = match (true) {
        $kodSonra !== 0 => 'kiran',
        $ciktiOnce !== $ciktiSonra => 'ortuk',
        array_intersect(array_keys($degisiklik), $baglanti) !== [] => 'baglantida',
        default => 'yerinde',
    };
    printf(
        "%-18s %d dosya  cikis=%-3d cikti %-6s -> %s\n",
        $ad,
        count($degisiklik),
        $kodSonra,
        $ciktiOnce === $ciktiSonra ? 'ayni' : 'farkli',
        $sinif
    );
    return $sinif;
}
```

## İç Ad Değişir

Ölçülen değişiklik, `Ayar` sınıfının iç dizisindeki `tema` anahtarını `gorunum` olarak yeniden adlandırmaktır. `__get` ve sınıfın başka hiçbir yeri değişmez. Kullanan `main.php` `(new Ayar())->tema` değerini `var_dump` ile yazdırır.

```php
<?php
// sihirli_okuma.php — __get'in baktigi ic anahtar yeniden adlandirilir
require_once 'yayilim.php';

olc('sihirli-okuma', [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['tema' => 'koyu'];
            public function __get(string $ad): mixed { return $this->degerler[$ad] ?? null; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'ayar.php';
        var_dump((new Ayar())->tema);
        PHP,
], [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['gorunum' => 'koyu'];
            public function __get(string $ad): mixed { return $this->degerler[$ad] ?? null; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/sihirli-okuma/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
sihirli-okuma      1 dosya  cikis=0   cikti farkli -> ortuk
once cikis=0 cikti=string(4) "koyu"
sonra cikis=0 cikti=NULL
```

**NM23.** Kullanan hiçbir şey görmedi: çıkış 0 kaldı, ama `string(4) "koyu"` yerine `NULL` geldi. `__get` bilinmeyen adı `null` ile karşılıyordu, yani `tema` artık yok ve bunu söyleyen bir şey olmadı. Bu dersin birinci ölçü sınıfı örtüktür. Sihirli yöntem, okumayı bir sözleşmeden çıkarıp bir arama işlemine çevirdiği için yeniden adlandırma hiçbir imzayı bozmaz.

## Sınırlayıcı Ölçüm

Sessizliğin nedeni yeniden adlandırma mı, yoksa `__get`'in bilinmeyen adı `null` ile karşılaması mı? İkinci seçenek ölçülür: aynı yeniden adlandırma yapılır, ama bu kez `__get` bilinmeyen bir adda `OutOfRangeException` fırlatır.

```php
<?php
// bilinmeyen_ad.php — ayni yeniden adlandirma, bu kez __get bilinmeyen adi reddediyor
require_once 'yayilim.php';

olc('bilinmeyen-ad', [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['tema' => 'koyu'];
            public function __get(string $ad): mixed
            {
                if (!array_key_exists($ad, $this->degerler)) {
                    throw new OutOfRangeException($ad);
                }
                return $this->degerler[$ad];
            }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'ayar.php';
        var_dump((new Ayar())->tema);
        PHP,
], [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['gorunum' => 'koyu'];
            public function __get(string $ad): mixed
            {
                if (!array_key_exists($ad, $this->degerler)) {
                    throw new OutOfRangeException($ad);
                }
                return $this->degerler[$ad];
            }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/bilinmeyen-ad/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
bilinmeyen-ad      1 dosya  cikis=255 cikti farkli -> kiran
once cikis=0 cikti=string(4) "koyu"
sonra cikis=255 cikti=
```

**NM24.** Aynı yeniden adlandırma, aynı kullanan; ancak çıkış 255 oldu, çünkü `tema` adı artık bilinmiyordu ve `__get` bunu bir istisnayla bildirdi. Sessizliği doğuran yeniden adlandırma değil, `__get`'in bilinmeyen adı geçiştirmesiydi. Bir sihirli yöntemin yanıtı sınıf yazarının kararıdır: aynı değişiklik `null` ile örtük, istisnayla kıran olabiliyor.

## Özet

- Sihirli yöntemler `__get`, `__set`, `__isset`, `__call` ve `__toString` gibi adları iki alt çizgiyle başlayan ve PHP'nin kendisinin çağırdığı yöntemlerdir.
- Okuma, yazma ve yöntem çağrısı sihirli yönteme çevrilince tanımsız ad kullananı durdurmaz.
- `__get`'in baktığı iç anahtar `tema`'dan `gorunum`'a inince kullanan hata almadı, çıkış 0; `string(4) "koyu"` yerine `NULL` geldi, dersin ölçü sınıfı örtüktür.
- `__get` bilinmeyen adda `OutOfRangeException` fırlatınca aynı değişiklik kıran oldu, çıkış 255.
- Aynı değişikliğin örtük mü kıran mı olduğunu sihirli yöntemin bilinmeyen adı nasıl karşıladığı belirler.

## Sonraki Adım

Sihirli yöntemler gevşek bir çevirici koyuyordu; tip bildirimleri tam tersini yapar ve sınıfın sınırına bir parametre, bir dönüş, bir özellik tipi koyar. Tip Bildirimleri dersi bir yöntemin parametresine `int` yazmanın kullananı hangi girdilerde durdurduğunu soruyor.
