
PHP Hata ve İstisna Yönetimi: try, catch, throw ve Özel İstisnalar
Hata yönetimi çoğu kod tabanında iki uçtan birine kayar: hiç yakalanmayan istisnalar ya da her satırı saran try blokları. İkisi de aynı sonucu doğurur; sorunun kaynağı kaybolur. Doğru yaklaşım, istisnanın nerede anlamlı biçimde ele alınabileceğini belirleyip yalnızca orada yakalamaktır.
Bu bölümde PHP'nin hata hiyerarşisi, try ve catch kullanımı, kendi istisna sınıflarının yazılması ve üretim ortamında hataların nasıl görüntüleneceği ele alınıyor.
Error ve Exception ayrımı
PHP 7 ile birlikte fırlatılabilir her şey Throwable arayüzünü uygular. Bu arayüzün altında iki ana dal bulunur. Exception uygulamanın öngördüğü, ele alınabilir durumları anlatır: dosya bulunamadı, doğrulama başarısız oldu. Error ise dilin kendi düzeyindeki sorunları temsil eder: tip uyuşmazlığı, tanımsız metot çağrısı, bellek sınırı.
try {
$sonuc = riskliIslem();
} catch (JsonException $e) {
// beklenen ve ele alınabilir durum
$sonuc = null;
} catch (TypeError $e) {
// kod hatası: yakalanıp gizlenmemeli, kayda alınıp yeniden fırlatılmalı
$this->gunluk->critical($e->getMessage(), ['iz' => $e->getTraceAsString()]);
throw $e;
}Genel kural, Error türevlerinin yakalanıp yutulmamasıdır. Bunlar kodun düzeltilmesi gereken kusurlarıdır; sessizce geçilirse yalnızca belirtiler gizlenir. Throwable yakalamak yalnızca uygulamanın en dış katmanında, kayıt tutmak ve düzgün bir yanıt üretmek için anlamlıdır.
try, catch, finally
finally bloğu, istisna fırlatılsın ya da fırlatılmasın çalışır. Açılan kaynakların kapatılması için uygundur. Birden fazla istisna tipi tek blokta yakalanabilir; yakalanan nesneye ihtiyaç yoksa değişken adı yazılmayabilir.
$dosya = fopen($yol, 'r');
try {
return islemeAl($dosya);
} catch (RuntimeException|LogicException $e) {
$this->gunluk->error('İşleme başarısız', ['hata' => $e->getMessage()]);
return null;
} catch (JsonException) { // nesneye ihtiyaç yoksa değişken yazılmaz
return null;
} finally {
fclose($dosya); // her durumda çalışır
}catch blokları yukarıdan aşağıya değerlendirilir, bu yüzden en özel tip en üstte yazılır. Üst tip önce yazılırsa alttaki bloklara hiç girilmez.
Kendi istisna sınıflarını yazmak
Yerleşik istisnalar genel amaçlıdır. Uygulamaya özgü durumlar için ayrı sınıf tanımlamak, çağıran tarafın hangi durumu ele aldığını mesaj metnine bakmadan seçebilmesini sağlar. Alan adına göre bir üst sınıf ve altında somut durumlar yeterlidir.
abstract class OdemeHatasi extends RuntimeException {}
final class YetersizBakiye extends OdemeHatasi
{
public function __construct(
public readonly int $eksikKurus,
) {
parent::__construct(sprintf('Bakiye %d kuruş yetersiz', $eksikKurus));
}
}
final class SaglayiciUlasilamadi extends OdemeHatasi {}İstisna nesnesine bağlam taşımak, mesaj metnini ayrıştırmaktan çok daha sağlıklıdır. Yukarıdaki örnekte eksik tutar bir özellik olarak taşındığı için çağıran taraf kullanıcıya doğru miktarı gösterebilir.
try {
$this->odeme->tahsilEt($siparis);
} catch (YetersizBakiye $e) {
return $this->uyari("Bakiyeniz {$e->eksikKurus} kuruş yetersiz.");
} catch (SaglayiciUlasilamadi $e) {
return $this->kuyrugaAl($siparis); // sonra tekrar denenecek
}İstisna zincirlemek
Alt katmandan gelen bir istisna, üst katmanın diliyle yeniden fırlatılırken aslı kaybedilmemelidir. Üçüncü parametre olarak verilen önceki istisna, izin tamamını korur.
try {
$satir = $this->pdo->query($sql)->fetch();
} catch (PDOException $e) {
throw new KullaniciDeposuHatasi(
'Kullanıcı okunamadı',
previous: $e, // asıl neden korunur
);
}Kayıt tutarken yalnızca en üstteki mesaj değil, zincirin tamamı yazılmalıdır. getPrevious ile ilerleyen kısa bir döngü, gerçek nedeni günlüğe taşır.
function zincir(Throwable $e): string
{
$satirlar = [];
do {
$satirlar[] = sprintf(
'%s: %s (%s:%d)',
$e::class, $e->getMessage(), $e->getFile(), $e->getLine()
);
} while ($e = $e->getPrevious());
return implode(PHP_EOL . 'neden -> ', $satirlar);
}Hangi katmanda yakalanmalı
Bir istisnanın yakalanacağı yer, o istisna hakkında karar verebilecek bilgiye sahip olan katmandır. Veritabanı sınıfı bağlantı hatasını yakalayıp boş dizi döndürürse, üst katman verinin gerçekten bulunmadığını mı yoksa erişilemediğini mi anlamaz. Bu iki durum farklı yanıtlar gerektirir: biri boş liste, diğeri hata sayfası.
Pratik bir ayrım şöyle kurulabilir. Altyapı katmanı istisnayı zenginleştirip yukarı taşır. Uygulama katmanı, alternatif bir yol varsa yakalar; örneğin önbellek okunamadığında kaynağa gider. Sunum katmanı ise kullanıcıya ne gösterileceğine karar verir ve son yakalayıcıyı burada bulundurur.
Boş catch bloğu hemen her durumda bir kusurdur. İstisnanın bilinçli olarak yok sayıldığı ender durumlarda, gerekçenin bir yorum satırıyla yazılması gerekir; aksi halde blok bir süre sonra gizlenmiş bir hataya dönüşür.
İstisnanın uygun olmadığı yerler
İstisnalar akış kontrolü aracı değildir. Beklenen ve sık yaşanan durumlar için kullanıldıklarında hem okunabilirlik düşer hem de gereksiz maliyet doğar. Kullanıcının formu eksik doldurması beklenen bir durumdur ve doğrulama sonucuyla ifade edilir; veritabanı sunucusunun yanıt vermemesi ise beklenmedik bir durumdur ve istisnayla ifade edilir.
// beklenen durum: sonuç nesnesi döndürülür
final class DogrulamaSonucu
{
public function __construct(
public readonly bool $gecerli,
/** @var list<string> */
public readonly array $hatalar = [],
) {}
}
// beklenmedik durum: istisna fırlatılır
if (!$this->pdo->beginTransaction()) {
throw new RuntimeException('İşlem başlatılamadı');
}Yerleşik fonksiyonların bir bölümü istisna fırlatmaz, false döndürür ya da uyarı üretir. Bu davranış açıkça değiştirilebildiği yerlerde değiştirilmelidir; böylece hata yönetimi tek bir yoldan yürür.
// PDO hataları varsayılan olarak sessizdir, istisnaya çevrilir
$pdo = new PDO($dsn, $kullanici, $parola, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
// JSON hataları da istisnaya çevrilebilir
$veri = json_decode($ham, true, flags: JSON_THROW_ON_ERROR);Yakalanmayan istisnalar ve genel işleyiciler
Uygulamanın en dış katmanında bir yakalayıcı bulunmalıdır. Amaç hatayı gizlemek değil, kullanıcıya anlaşılır bir yanıt verirken ayrıntıyı günlüğe yazmaktır.
set_exception_handler(function (Throwable $e): void {
error_log((string) $e);
http_response_code(500);
echo 'Beklenmeyen bir hata oluştu.';
});
// uyarı ve bildirimleri de istisnaya çevirerek tek bir yoldan yönetmek
set_error_handler(function (int $no, string $mesaj, string $dosya, int $satir): bool {
throw new ErrorException($mesaj, 0, $no, $dosya, $satir);
});
// ölümcül hatalar yukarıdaki iki işleyiciye düşmez
register_shutdown_function(function (): void {
$son = error_get_last();
if ($son !== null && $son['type'] === E_ERROR) {
error_log('Ölümcül hata: ' . $son['message']);
}
});Üretim ortamında hata görüntüleme
Hata metinlerinin ziyaretçiye gösterilmesi bilgi sızdırır. Dosya yolları, sorgu metinleri ve sınıf adları saldırı yüzeyini genişletir. Üretimde görüntüleme kapatılır, kayıt açık tutulur.
; php.ini - üretim
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/hata.log
error_reporting = E_ALLGeliştirme ortamında ise tersi geçerlidir: tüm hatalar görünür olmalıdır. Uyarıların bastırıldığı bir ortamda çalışan kod, üretimde ilk kez hata verdiğinde teşhis çok daha zordur. @ operatörü ile hata bastırma hemen her durumda yanlış çözümdür.
Kullanıcıya gösterilen mesaj ile günlüğe yazılan kayıt aynı olmak zorunda değildir. Ekranda kısa bir açıklama ve bir izleme numarası göstermek, destek kaydı geldiğinde ilgili günlük satırını saniyeler içinde bulmayı sağlar.
$izlemeNo = bin2hex(random_bytes(6));
error_log(sprintf('[%s] %s', $izlemeNo, (string) $e));
http_response_code(500);
echo "Beklenmeyen bir hata oluştu. Destek kodu: {$izlemeNo}";Kontrol listesi
- İstisnalar yalnızca anlamlı biçimde ele alınabilecekleri katmanda yakalanıyor.
Errortürevleri yutulmuyor; kayda alınıp yeniden fırlatılıyor.- Uygulamaya özgü durumlar için ayrı istisna sınıfları tanımlı.
- Yeniden fırlatmada
previousparametresi doldurularak zincir korunuyor. - En dış katmanda genel bir işleyici ve kapanış kaydı bulunuyor.
- Üretimde
display_errorskapalı,log_errorsaçık. - Kaynak kapatma işlemleri
finallybloğunda yapılıyor.
İlgili yazılar
- Üretimde yaşanan hataların analizi
- Dosya işlemlerinde dönüş değeri denetimi
- API yanıtlarında tutarlı hata biçimi
PHP eğitim serisi
Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.
Temeller
- Kurulum ve temel sözdizimi
- Döngüler ve akış kontrolü
- Diziler
- Fonksiyonlar
- String fonksiyonları
- Süper globaller
- Form işleme
- Dosya işlemleri
Dilin ayrıntıları
- Tip sistemi ve strict_types
- Hata ve istisna yönetimi (bu yazı)
- Düzenli ifadeler
- Tarih ve saat işlemleri
Nesne yönelimli PHP
- Sınıf ve nesne
- Kalıtım, arayüz ve trait
- Statik üyeler, sabitler ve enum
- Namespace, Composer ve autoload
Veri ve güvenlik
- PDO ile veritabanı işlemleri
- Oturum yönetimi ve kimlik doğrulama
- Güvenli çıktı ve XSS
- JSON ile veri değişimi
Dış dünya ve üretim
Yorumlar
İlk yorumu sen yap.
İlgili Yazılar

PHP Uygulamasını Üretime Almak: Yapılandırma ve Güvenlik
Geliştirme makinesinde çalışan kod ile üretimde güvenle çalışan kod arasındaki fark yapılandırmadır. Ayarlar, izinler, dağıtım akışı ve son kontroller.

PHP ile REST API Yazmak: Yönlendirme ve HTTP Durum Kodları
Bir API'nin kalitesi çoğunlukla ayrıntılarda belli olur: doğru durum kodu, tutarlı hata biçimi ve öngörülebilir adresler. Saf PHP ile uçtan uca kurulum.

PHPUnit ile Birim Test Yazmak: Kurulum, Sahte Nesneler ve Kapsam
Testin amacı hata bulmak değil, değişikliği güvenle yapabilmektir. Kurulum, ilk test, sahte nesneler ve test edilebilir kodun özellikleri.