TypeError, bir fonksiyona beklediği tipte olmayan bir argüman verdiğinizde, fonksiyon bildirilen dönüş tipinden farklı bir değer döndürdüğünde ya da tipli bir özelliğe uyumsuz değer atadığınızda fırlatılır. ArgumentCountError eksik (yerleşik fonksiyonlarda fazla) argüman, ValueError tipi doğru ama değeri geçersiz argüman, DivisionByZeroError ise sıfıra bölme durumunda oluşur. Dördü de Error sınıfından türer, yani try/catch ile yakalanabilir; yakalanmazsa betik "Fatal error: Uncaught ..." ile durur.

Bu yazıda her hatanın PHP 8.5.12'de üretilen gerçek mesajını gösteriyor, mesajın nasıl okunacağını ve kalıcı çözümü anlatıyoruz. Hata yakalamanın temelleri için PHP try catch ve exception, serinin geneli için PHP öğrenme rehberi yazılarına bakabilirsiniz.

Hata hiyerarşisinde yerleri

Hata hiyerarşisinde yerleri: TypeError, ArgumentCountError, ValueError, ArithmeticError

Kılavuzun Throwable errors sayfasındaki hiyerarşiye göre bu sınıfların hepsi Throwable arayüzünü uygulayan Error dalındadır. Exception ise ayrı bir daldır; bu yüzden yalnızca catch (Exception $e) yazan kod bu hataları yakalamaz. İlişkiler şöyle: ArgumentCountError, TypeError'ın alt sınıfıdır; DivisionByZeroError, ArithmeticError'ın alt sınıfıdır; ValueError doğrudan Error'dan türer.

SınıfÜst sınıfNe zamanÖrnek mesaj
TypeErrorErrorArgüman, dönüş veya özellik tipi uyumsuzArgument #1 ($a) must be of type int, string given
ArgumentCountErrorTypeErrorEksik argüman; yerleşik fonksiyonda fazla argümanToo few arguments to function topla()
ValueErrorErrorTip doğru, değer geçersizArgument #2 ($times) must be greater than or equal to 0
ArithmeticErrorErrorintdiv sınır aşımı, negatif bit kaydırmaDivision of PHP_INT_MIN by -1 is not an integer
DivisionByZeroErrorArithmeticError/, % veya intdiv ile sıfıra bölmeDivision by zero, Modulo by zero

Bu hiyerarşi pratikte şu anlama gelir: catch (TypeError $e) eksik argüman hatalarını da yakalar, catch (ArithmeticError $e) sıfıra bölmeyi de kapsar. Yakaladığınız nesnenin getMessage(), getFile() ve getLine() metotları, ekranda gördüğünüz mesajın parçalarını ayrı ayrı verir; günlüğe yazarken bu değerleri kullanmak, mesajı metin olarak ayrıştırmaktan çok daha güvenilirdir.

TypeError: "must be of type int, string given"

PHP varsayılan olarak "zorlayıcı" (coercive) tip modunda çalışır: skaler tipli bir parametreye uyumlu bir değer gelirse dönüştürür. Örneğin int parametreye "5" metni verirseniz 5 sayısına çevrilir. Ancak "beş" gibi sayıya çevrilemeyen bir metin geldiğinde dönüşüm mümkün olmadığı için TypeError fırlatılır.

php
<?php
function topla(int $a, int $b): int
{
    return $a + $b;
}

echo topla('5', 3), PHP_EOL;   // çıktı: 8
echo topla('beş', 3), PHP_EOL;
/* çıktı:
8

Fatal error: Uncaught TypeError: topla(): Argument #1 ($a) must be of type int, string given, called in /yol/ornek.php on line 8 and defined in /yol/ornek.php:2
Stack trace:
#0 /yol/ornek.php(8): topla('be\xC5\x9F', 3)
#1 {main}
  thrown in /yol/ornek.php on line 2
*/

Mesajı parçalara ayıralım: topla() hatanın oluştuğu fonksiyon; Argument #1 ($a) sorunlu parametrenin sırası ve adı; must be of type int, string given beklenen ve gelen tip; called in ... on line 8 hatalı çağrının yapıldığı yer; defined in ...:2 fonksiyonun tanımlandığı yer. Düzeltmeniz gereken yer neredeyse her zaman "called in" kısmıdır, çünkü yanlış değeri gönderen çağıran taraftır. Yığın izindeki 'be\xC5\x9F' ise "beş" metninin bayt gösterimidir; PHP yığın izinde ASCII dışı baytları bu biçimde yazar.

strict_types=1 ile katı mod

Dosyanın en başına declare(strict_types=1); eklerseniz o dosyadan yapılan çağrılarda dönüşüm yapılmaz: int parametre yalnızca int kabul eder. Kılavuzun tek istisnası, float bekleyen parametreye int verilmesidir. Önemli bir ayrıntı daha var: katı mod, fonksiyonun tanımlandığı dosyaya değil, çağrının yapıldığı dosyaya göre belirlenir.

php
<?php
declare(strict_types=1);

function topla(int $a, int $b): int
{
    return $a + $b;
}

echo topla(5, 3), PHP_EOL;   // çıktı: 8
echo topla('5', 3), PHP_EOL;
/* çıktı:
8

Fatal error: Uncaught TypeError: topla(): Argument #1 ($a) must be of type int, string given, called in /yol/ornek.php on line 10 and defined in /yol/ornek.php:4
Stack trace:
#0 /yol/ornek.php(10): topla('5', 3)
#1 {main}
  thrown in /yol/ornek.php on line 4
*/

Katı mod, form verisi gibi her zaman metin olarak gelen değerleri açıkça dönüştürmeye zorlar: topla((int) $_POST['a'], ...). Bu ilk bakışta fazladan iş gibi görünse de "hangi değer nerede dönüştürüldü" sorusunu ortadan kaldırır. Tip bildirimlerinin tamamı PHP tip bildirimleri ve strict_types yazısında.

Zorlayıcı modda hangi dönüşümlerin sessizce yapıldığını bilmek de önemlidir. Aşağıdaki örnekte float parametre int, sayısal metin ve hatta true değerini kabul ediyor. int parametreye kesirli bir float verildiğinde ise PHP 8.1'den beri hata yerine eskimiş özellik uyarısı alırsınız; kesirli kısım kaybolduğu için bu bir veri kaybıdır.

php
<?php
function oran(float $x): float
{
    return $x / 2;
}
var_dump(oran(5));     // çıktı: float(2.5)
var_dump(oran('7.5')); // çıktı: float(3.75)
var_dump(oran(true));  // çıktı: float(0.5)
function sayi(int $n): int { return $n; }
var_dump(sayi(7.0));   // çıktı: int(7)
var_dump(sayi(7.5));
/* son satırın çıktısı:
Deprecated: Implicit conversion from float 7.5 to int loses precision in /yol/ornek.php on line 9
int(7)
*/

Dönüş tipi, özellik tipi ve yerleşik fonksiyonlar

Dönüş tipi, özellik tipi ve yerleşik fonksiyonlar

TypeError yalnızca argümanlarda değil, iki başka yerde de oluşur. Fonksiyon bildirilen dönüş tipine uymayan bir değer döndürürse mesaj "Return value must be of type ..." olur. Tipli bir özelliğe uyumsuz değer atarsanız "Cannot assign ... to property ..." mesajını alırsınız. PHP'nin yerleşik fonksiyonları da aynı biçimde hata verir; mesajda parametrenin kılavuzdaki adı görünür, bu da kılavuz sayfasında hangi parametreye bakacağınızı söyler.

php
<?php
function fiyatGetir(): int
{
    return 'ücretsiz';
}

class Urun
{
    public int $stok = 0;
}

try {
    fiyatGetir();
} catch (TypeError $e) {
    echo $e->getMessage(), PHP_EOL;
    // çıktı: fiyatGetir(): Return value must be of type int, string returned
}

try {
    $u = new Urun();
    $u->stok = 'çok';
} catch (TypeError $e) {
    echo $e->getMessage(), PHP_EOL;
    // çıktı: Cannot assign string to property Urun::$stok of type int
}

try {
    strlen([]);
} catch (TypeError $e) {
    echo $e->getMessage(), PHP_EOL;
    // çıktı: strlen(): Argument #1 ($string) must be of type string, array given
}

Union, nullable ve isteğe bağlı parametrelerde mesajlar

Tip bildirimi karmaşıklaştıkça mesajlar da biraz değişir. Aşağıdaki örnek beş farklı durumu tek döngüde deniyor. Birkaç ayrıntıya dikkat edin: int|string olarak yazdığınız union tip mesajda string|int sırasıyla görünür; nullable olmayan bir parametreye null verilince mesaj "null given" der; isteğe bağlı parametresi olan bir fonksiyonda eksik argüman mesajı "exactly" yerine "at least" kullanır.

php
<?php
function kimlik(int|string $id): string { return (string) $id; }
function etiket(?string $ad): string { return $ad ?? 'yok'; }
function sayfala(int $sayfa, int $adet = 20): int { return $sayfa * $adet; }
function yaz(string $metin): void { echo $metin, PHP_EOL; }

foreach ([
    fn() => kimlik([1]),
    fn() => etiket(null),
    fn() => yaz(null),
    fn() => sayfala(),
    fn() => kimlik(1.5),
] as $f) {
    try {
        echo var_export($f(), true), PHP_EOL;
    } catch (TypeError $e) {
        echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
    }
}
/* çıktı:
TypeError: kimlik(): Argument #1 ($id) must be of type string|int, array given, called in /yol/ornek.php on line 8
'yok'
TypeError: yaz(): Argument #1 ($metin) must be of type string, null given, called in /yol/ornek.php on line 10
ArgumentCountError: Too few arguments to function sayfala(), 0 passed in /yol/ornek.php on line 11 and at least 1 expected

Deprecated: Implicit conversion from float 1.5 to int loses precision in /yol/ornek.php on line 2
'1'
*/

Son satır ilginç: int|string parametreye verilen 1.5, zorlayıcı modda int'e çevrildi ve kesirli kısım kayboldu; sonuç '1'. Union tiplerde hangi dönüşümün seçileceği kuralları ayrıntılıdır ve bu tür beklenmedik sonuçlardan kaçınmanın en kolay yolu yine strict_types=1 kullanmaktır. Kullanıcı tanımlı yaz() fonksiyonu null değerine doğrudan TypeError verdi; aynı durumun yerleşik fonksiyonlarda nasıl davrandığını bir sonraki bölümde görüyoruz.

Yerleşik fonksiyona null vermek

Yakından ilişkili bir uyarı da yerleşik fonksiyonların null kabul etmeyen parametrelerine null geçirmektir. PHP 8.1 Deprecated Features sayfasına göre bu kullanım PHP 8.1'den beri eskimiş sayılır; şimdilik hata değil uyarıdır, ama aynı durum kullanıcı tanımlı bir fonksiyonda doğrudan TypeError verir.

php
<?php
echo strlen(null), PHP_EOL;
echo str_contains('merhaba', null) ? 'evet' : 'hayır', PHP_EOL;
/* çıktı:
Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /yol/ornek.php on line 2
0

Deprecated: str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated in /yol/ornek.php on line 3
evet
*/

Bu uyarının kaynağı genellikle veritabanından NULL gelen bir sütun ya da tanımsız bir dizi anahtarıdır. Değeri fonksiyona vermeden önce $deger ?? '' ile varsayılan verin. İkinci satırdaki "evet" sonucu da dikkat çekici: null boş metne dönüştüğü için her metin "boş metni içerir" kabul edildi. Bu tür sessiz mantık hataları, uyarıyı ciddiye almanız için iyi bir neden. Tanımsız anahtar uyarıları için undefined variable ve array key yazısına bakın.

ArgumentCountError: "Too few arguments"

Kullanıcı tanımlı bir fonksiyonu zorunlu parametre sayısından az argümanla çağırırsanız ArgumentCountError alırsınız. Fazla argüman vermek ise kullanıcı fonksiyonlarında hata değildir; fazlalık sessizce yok sayılır. Yerleşik fonksiyonlarda durum farklıdır: kılavuza göre variadic olmayan bir yerleşik fonksiyona fazla argüman vermek de bu hatayı fırlatır.

php
<?php
function topla(int $a, int $b): int
{
    return $a + $b;
}

try {
    topla(1);
} catch (ArgumentCountError $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}

try {
    strlen('a', 'b');
} catch (ArgumentCountError $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}

echo topla(1, 2, 3), PHP_EOL; // fazla argüman sessizce yok sayılır
var_dump(new ArgumentCountError() instanceof TypeError);
/* çıktı:
ArgumentCountError: Too few arguments to function topla(), 1 passed in /yol/ornek.php on line 8 and exactly 2 expected
ArgumentCountError: strlen() expects exactly 1 argument, 2 given
3
bool(true)
*/

Mesajdaki "1 passed ... and exactly 2 expected" kısmı ne kadar argüman verildiğini ve kaç tane beklendiğini söyler. Fonksiyonun bazı parametreleri isteğe bağlıysa mesaj "at least" ifadesini kullanır. Çözüm ya eksik argümanı vermek ya da parametreye anlamlı bir varsayılan değer tanımlamaktır. Varsayılan değerler ve isimli argümanlar PHP fonksiyon tanımlama ve parametreler yazısında anlatılıyor.

ValueError: tip doğru, değer yanlış

Kılavuza göre ValueError, argümanın tipi doğru ama değeri geçersiz olduğunda fırlatılır: pozitif sayı bekleyen yere negatif sayı, boş olmaması gereken yere boş metin ya da dizi gibi. PHP 8.0'dan itibaren birçok yerleşik fonksiyon eskiden uyarı verip false döndürdüğü durumlarda artık bu hatayı fırlatıyor ve her yeni sürüm listeye ekleme yapıyor.

php
<?php
$denemeler = [
    fn() => str_repeat('a', -1),
    fn() => array_combine(['a', 'b'], [1]),
    fn() => array_fill(0, -5, 'x'),
    fn() => json_decode('[]', depth: 0),
];

foreach ($denemeler as $d) {
    try {
        $d();
    } catch (ValueError $e) {
        echo $e->getMessage(), PHP_EOL;
    }
}
/* çıktı:
str_repeat(): Argument #2 ($times) must be greater than or equal to 0
array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements
array_fill(): Argument #2 ($count) must be greater than or equal to 0
json_decode(): Argument #3 ($depth) must be greater than 0
*/

ValueError neredeyse her zaman bir programlama hatasına işaret eder; yakalayıp devam etmek yerine argümanı üreten hesabı düzeltmek gerekir. Örneğin array_combine() hatası, iki dizinin farklı uzunlukta oluştuğunu, yani verinin bir yerinde eksik ya da fazla eleman olduğunu söyler. Kendi fonksiyonlarınızda benzer bir durumu bildirmek için standart kütüphanedeki InvalidArgumentException sınıfı daha uygundur; ValueError ağırlıklı olarak PHP'nin kendi fonksiyonları tarafından kullanılır.

DivisionByZeroError ve ArithmeticError

/ operatörüyle sıfıra bölmek "Division by zero", % operatörüyle sıfıra göre mod almak "Modulo by zero" mesajlı DivisionByZeroError fırlatır. intdiv() da sıfır bölende aynı hatayı verir. Kılavuzun intdiv sayfasına göre PHP_INT_MIN değerini -1'e bölmek ise sonuç int sınırlarına sığmadığı için üst sınıf olan ArithmeticError'ı fırlatır. Hepsini tek bir catch (ArithmeticError $e) ile yakalayabilirsiniz.

php
<?php
foreach ([fn() => 10 / 0, fn() => 10 % 0, fn() => intdiv(10, 0), fn() => intdiv(PHP_INT_MIN, -1)] as $f) {
    try {
        $f();
    } catch (ArithmeticError $e) {
        echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
    }
}
var_dump(fdiv(10, 0)); // çıktı: float(INF)
var_dump(fdiv(0, 0));  // çıktı: float(NAN)
var_dump(fmod(10, 0)); // çıktı: float(NAN)
/* döngünün çıktısı:
DivisionByZeroError: Division by zero
DivisionByZeroError: Modulo by zero
DivisionByZeroError: Division by zero
ArithmeticError: Division of PHP_INT_MIN by -1 is not an integer
*/

IEEE 754 kurallarına göre sonsuz veya NAN sonucu bilerek istiyorsanız fdiv() kullanın; kayan noktalı mod için fmod() da sıfır bölende hata fırlatmak yerine NAN döndürür. Ama çoğu iş uygulamasında doğru çözüm, bölmeden önce böleni kontrol edip anlamlı bir sonuç (örneğin "oran hesaplanamadı") üretmektir. Sıfırın negatif kuvvetinin eskimiş sayılması da aynı mantıktan gelir: PHP 8.4 deprecated uyarıları.

Uygulamalı örnek: hataları doğru sırayla yakalamak

Birden fazla catch bloğu yazdığınızda PHP ilk eşleşen bloğu çalıştırır. ArgumentCountError bir TypeError olduğu için, ikisini ayrı ele almak istiyorsanız alt sınıfı önce yazmalısınız; aksi hâlde TypeError bloğu her ikisini de yakalar. Aşağıdaki örnekte kendi doğrulamamız InvalidArgumentException fırlatıyor, PHP'nin tip kontrolleri ise TypeError ve ArgumentCountError üretiyor. Aynı blokta birden fazla sınıfı | ile birleştirebilirsiniz.

php
<?php
function bol(int $a, int $b): float
{
    if ($b === 0) {
        throw new InvalidArgumentException('Bölen sıfır olamaz');
    }
    return $a / $b;
}

function guvenliCalistir(callable $is): void
{
    try {
        echo $is(), PHP_EOL;
    } catch (ArgumentCountError $e) {
        echo 'Eksik argüman: ', $e->getMessage(), PHP_EOL;
    } catch (TypeError $e) {
        echo 'Tip hatası: ', $e->getMessage(), PHP_EOL;
    } catch (ValueError | InvalidArgumentException $e) {
        echo 'Değer hatası: ', $e->getMessage(), PHP_EOL;
    }
}

guvenliCalistir(fn() => bol(10, 4));
guvenliCalistir(fn() => bol(10, 0));
guvenliCalistir(fn() => bol('on', 2));
guvenliCalistir(fn() => bol(10));
/* çıktı:
2.5
Değer hatası: Bölen sıfır olamaz
Tip hatası: bol(): Argument #1 ($a) must be of type int, string given, called in /yol/ornek.php on line 25
Eksik argüman: Too few arguments to function bol(), 1 passed in /yol/ornek.php on line 26 and exactly 2 expected
*/

Bu kalıbı her fonksiyon çağrısının etrafına koymayın. TypeError ve ArgumentCountError kodun yanlış yazıldığını gösterir; düzgün çalışan bir programda hiç oluşmamaları gerekir. Bunları yakalamanın yeri, uygulamanın en dış katmanında hatayı günlüğe yazıp kullanıcıya genel bir hata sayfası göstermektir. Global işleyici kurmak ve yakalanmayan hataların çıktısını okumak için PHP fatal error: uncaught error çözümü yazısına bakın.

Sık yapılan hatalar

Sık yapılan hatalar: catch (Exception $e) yazıp TypeError'ın yakalanmasını beklemek: Error ve..., catch (TypeError $e) bloğunu ArgumentCountError bloğundan önce yazmak: alt...
  • catch (Exception $e) yazıp TypeError'ın yakalanmasını beklemek: Error ve Exception ayrı dallardır, ikisini birden yakalamak için Throwable kullanın.
  • catch (TypeError $e) bloğunu ArgumentCountError bloğundan önce yazmak: alt sınıf bloğu hiç çalışmaz.
  • Form değerlerini katı moddaki fonksiyonlara doğrudan vermek: $_POST değerleri her zaman metindir, önce dönüştürün ve doğrulayın (filter_var).
  • Sıfıra bölmeyi @ ile susturmaya çalışmak: güncel sürümlerde bu bir istisnadır ve @ yalnızca tanılama mesajlarını bastırır, istisnaları değil.
  • ValueError'ı yakalayıp yok saymak: hatayı üreten hesap düzeltilmeden aynı sorun başka yerde tekrar çıkar.

Sık Sorulan Sorular

TypeError ile Warning arasındaki fark nedir?

Warning bir tanılama mesajıdır; betik devam eder. TypeError ise bir Throwable'dır: yakalanmazsa betik o noktada durur. Kısacası tanımsız bir değişkeni okumak uyarı, o değişkenin null değerini int bekleyen kendi fonksiyonunuza vermek TypeError'dır.

Mesajın sonundaki "thrown in ... on line 2" neden fonksiyonun tanım satırını gösteriyor?

Argüman tip hatası, fonksiyona girilirken, yani tanımın bulunduğu yerde fırlatılır. Bu yüzden son satır tanım satırını gösterir. Hatalı değeri gönderen çağrıyı bulmak için mesajın ortasındaki "called in ... on line N" kısmına ya da yığın izindeki #0 satırına bakın; orada çağrının yapıldığı dosya, satır ve verilen argümanlar listelenir. Yığın izini okumanın ayrıntıları fatal error çözümleri yazısında.

strict_types kullanmalı mıyım?

Yeni yazılan kodda yaygın bir tercihtir, çünkü sessiz dönüşümleri ortadan kaldırır ve hatayı kaynağına yakın yakalar. Mevcut büyük bir projede ise dosya dosya eklemek ve her dosyadan yapılan çağrıları test etmek daha güvenlidir; katı mod çağrının yapıldığı dosyaya göre işlediği için etkisi kademeli olarak yayılır.

Eski kodlarda sıfıra bölme neden yalnızca uyarı veriyordu?

Kılavuzun PHP 7.0 geçiş notlarına göre, 7.0 öncesinde hem / hem % sıfır bölende uyarı verip false döndürüyordu. PHP 7.0 ile % operatörü DivisionByZeroError fırlatmaya, / ise uyarıyla birlikte INF veya NAN döndürmeye başladı. Yukarıdaki örnekte gördüğünüz gibi güncel sürümlerde / da hata fırlatıyor. Eski bir projeyi taşırken sıfıra bölmenin sessizce geçtiği yerlerin artık betiği durdurabileceğini hesaba katın.

Kendi fonksiyonumda tip hatası mı fırlatmalıyım, istisna mı?

Tip kontrolünü PHP'ye bırakın: parametreye tip bildirirseniz TypeError kendiliğinden oluşur. İş kuralı ihlalleri (negatif miktar, geçersiz durum) için ise InvalidArgumentException veya kendi istisna sınıfınızı fırlatın; bunlar çağıranın beklemesi ve ele alması gereken durumlardır.

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 · Tip bildirimleri ve strict_types · try catch ve exception · Fatal error çözümleri · Undefined variable uyarısı · Tip dönüşümü