İçeriğe geç
Muhammet Şafak
Diller 4 dk okuma

PHP'de İstisnalar ve Hata Yönetimi

PHP'de istisnalar (exception) ve hata yönetimi: try-catch-finally yapıları ve özel istisnalar.


PHP’de hata yönetimi iki farklı mekanizma üzerinden yürür: hatalar (error) ve istisnalar (exception). İkisi arasındaki farkı anlamak, sadece teknik bir ayrıntı değil — nerede ne kullandığınız, uygulamanızın ne kadar öngörülebilir davrandığını doğrudan etkiliyor.

Hata (error), PHP motorunun tetiklediği bir durumdur: tanımsız bir değişken, bölünemez sıfır, mevcut olmayan bir fonksiyon çağrısı. İstisna ise sizin veya kullandığınız kütüphanenin bilerek fırlattığı, çağrı yığıtı boyunca taşınan ve yakalanabilir bir nesnedir. PHP 7 ile birlikte bu iki dünya Throwable arayüzü altında büyük ölçüde birleşti; artık Error sınıfı da catch edilebilir. Ama bu birleşme zihinsel modeli değiştirmiyor: hataları yakalamak çoğunlukla bir güvenlik ağı kurmak içindir, istisnalar ise iş akışının bir parçasıdır.

try-catch-finally

Temel yapı her dilde benzer; PHP’de şöyle görünür:

function bolme(int $bolunen, int $bolen): float
{
    if ($bolen === 0) {
        throw new InvalidArgumentException("Sıfıra bölme.");
    }
    return $bolunen / $bolen;
}

try {
    echo bolme(10, 2) . "\n"; // 5
    echo bolme(10, 0) . "\n"; // exception fırlatılır
} catch (InvalidArgumentException $e) {
    echo "Geçersiz argüman: " . $e->getMessage() . "\n";
} finally {
    echo "Her durumda çalışır.\n";
}

Çıktı:

5
Geçersiz argüman: Sıfıra bölme.
Her durumda çalışır.

Birkaç noktanın üzerinde durmak istiyorum.

InvalidArgumentException kullandım — Exception değil. PHP’nin SPL kütüphanesi anlamlı bir hiyerarşi sunuyor: RuntimeException, LogicException, InvalidArgumentException, OutOfRangeException gibi. Doğrudan Exception fırlatmak, catch tarafında filtreleme imkânı vermez; “ne olduğu belli olmayan bir şey oldu” demiş olursunuz. Doğru exception türünü seçmek, çağıranın hatayı anlamlı biçimde ele almasını sağlar.

finally bloku istisna yakalanıp yakalanmadığından bağımsız çalışır. Tipik kullanım: veritabanı bağlantısını kapatmak, kilit serbest bırakmak, geçici dosyayı silmek gibi temizlik işlemleri. finally içinde return veya yeni bir throw kullanmak olası; ama bu noktada kodu takip etmek güçleşiyor — dikkatli olmakta fayda var.

Birden fazla catch bloğu

Bir try bloğuna birden fazla farklı tipte exception için catch yazabilirsiniz:

try {
    $sonuc = veritabanindenCek($id);
} catch (NotFoundException $e) {
    return null; // kayıt yok, normal durum
} catch (DatabaseException $e) {
    logger()->error($e->getMessage());
    throw new ServiceException("Veri alınamadı.", previous: $e);
}

Buradaki yaklaşım bilinçli: NotFoundException sessizce null döndürüyor çünkü “kayıt yok” bir hata değil, beklenen bir sonuç. DatabaseException ise bir katman yukarı, daha genel bir exception olarak yeniden fırlatılıyor ve orijinal exception previous parametresiyle korunuyor. Bu, stack trace’i kaybetmeden soyutlama yapmanın standart yolu.

PHP 8.0’dan itibaren birden fazla exception türünü tek catch bloğunda tutmak da mümkün:

catch (NotFoundException | InvalidArgumentException $e) {
    // iki türü aynı şekilde ele al
}

Özel exception sınıfları

Kendi exception sınıflarınızı yazmak, uygulamanızın hata dilini zenginleştirir. Minimal bir örnek:

class PaymentFailedException extends RuntimeException
{
    public function __construct(
        private readonly string $transactionId,
        string $message = '',
        ?\Throwable $previous = null
    ) {
        parent::__construct($message, 0, $previous);
    }

    public function getTransactionId(): string
    {
        return $this->transactionId;
    }
}

Bu sınıfı catch eden kod $e->getTransactionId()’ye erişebilir. Log kaydına, alerting sistemine veya kullanıcıya dönen yanıta doğrudan bağlayabileceğiniz somut bir bağlam var.

Özellikle kütüphane yazarken, domain’inize özgü bir exception hiyerarşisi kurmak iyi bir pratik. Kütüphanenizin ürettiği tüm exceptionlar tek bir base exception’dan türerse, tüketici gerektiğinde hepsini tek catch ile yakalayabilir.

Global exception handler

Yakalanmamış exceptionlar için set_exception_handler() ile bir global handler tanımlayabilirsiniz:

set_exception_handler(function (\Throwable $e): void {
    logger()->critical($e->getMessage(), [
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString(),
    ]);
    http_response_code(500);
    echo json_encode(['error' => 'Beklenmeyen bir hata oluştu.']);
    exit(1);
});

Laravel, Symfony gibi framework’ler bunu zaten kendi Handler mekanizmalarıyla yönetiyor. Bare PHP ile çalışıyorsanız veya küçük bir kütüphane yazıyorsanız bu handler kritik.

Hata seviyeleri ve error_reporting

PHP’nin kendi hata sistemi ayrı bir boyut. Geliştirme ortamında görmek istediğiniz hata seviyelerini açık tutmak, production’da ise hataları log’a yönlendirip kullanıcıya göstermemek temel kural. php.ini üzerinden yapılandırmak tercih edilmeli; ama bazı paylaşımlı hosting ortamlarında bu erişim olmadığında error_reporting() ve ini_set() çalışma zamanında devreye girer:

// Geliştirme ortamı: her şeyi göster
error_reporting(E_ALL);
ini_set('display_errors', '1');

// Production: hataları gösterme, logla
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php-errors.log');

PHP’nin hata seviyeleri bir bitmap yapısına sahiptir:

SabitDeğerAçıklama
E_ERROR1Ölümcül çalışma zamanı hatası; yürütme durur.
E_WARNING2Ölümcül olmayan çalışma zamanı uyarısı.
E_PARSE4Sözdizimi hatası; çözümleyici üretir.
E_NOTICE8Olası sorun bildirimi; tanımsız değişken gibi.
E_STRICT2048İleriye dönük uyumluluk önerileri.
E_ALL32767Tüm hata ve uyarılar.

Bu seviyeleri bit operatörleriyle birleştirip çıkarabilirsiniz:

error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);

@ operatörü: neden kaçının

PHP’de @ operatörü, önüne konduğu ifadenin ürettiği hataları bastırır. Teknik olarak çalışır; pratik olarak büyük bir kötü pratiktir.

// Yapmayın
echo @$tanimsizDegisken;

// Yapın
echo $tanimsizDegisken ?? '';

@ operatörü bir sorunu gizler, çözmez. Gerçek bir hata sessizce geçer; debug sırasında saatlerce kayıp yaşarsınız. PHP 8.0’dan itibaren @ devre dışı bile bırakılabilir durumda. Null coalescing (??), isset(), ya da doğru exception yönetimi her zaman daha temiz bir seçenek.


PHP 7 ve sonrası, exception yönetimini ciddi ölçüde olgunlaştırdı. Throwable birleşimi, named arguments ile previous kullanımı, union catch blokları bunların bir kısmı. Ama temel fikir değişmedi: bir exception, “beklenmedik değil, öngörülemeyen” durumu temsil eder. Öngörebildiğinizi if/else ile halledersiniz; öngöremediğinizi exception hiyerarşisiyle sarıp anlamlı bir şekilde iletirsiniz. Bu ayrımı zihinsel model olarak benimsemek, catch bloklarının neyi yakalayıp ne yapacağını netleştiriyor.

Etiketler: #PHP
Paylaş:

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi