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:
| Sabit | Değer | Açıklama |
|---|---|---|
E_ERROR | 1 | Ölümcül çalışma zamanı hatası; yürütme durur. |
E_WARNING | 2 | Ölümcül olmayan çalışma zamanı uyarısı. |
E_PARSE | 4 | Sözdizimi hatası; çözümleyici üretir. |
E_NOTICE | 8 | Olası sorun bildirimi; tanımsız değişken gibi. |
E_STRICT | 2048 | İleriye dönük uyumluluk önerileri. |
E_ALL | 32767 | Tü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.