PHP Tip Sistemi: Tür Dönüşümü, Karşılaştırma ve strict_types

PHP Tip Sistemi: Tür Dönüşümü, Karşılaştırma ve strict_types

7 dakika okuma

PHP değişkenin tipini çalışma zamanında belirler. Bu esneklik ilk aşamada yazmayı hızlandırır, ancak tip bildirimi kullanılmayan bir kod tabanında hataların önemli bir bölümü beklenenden farklı bir değerin katmanlar arasında sessizce dolaşmasından doğar. Bir metin olarak gelen kimlik numarası, sayı bekleyen bir karşılaştırmaya girdiğinde hata vermez; yanlış sonuç üretir.

PHP 8 ile tip davranışı belirgin biçimde sıkılaştı. Sayısal olmayan metinlerle aritmetik artık hata fırlatıyor, metin ile sayı karşılaştırması daha sezgisel bir kurala bağlandı. Bu bölümde tip bildirimleri, katı mod, karşılaştırma kuralları ve güvenli dönüşüm yöntemleri ele alınıyor. Örneklerin tamamı PHP 8.4 ile çalışır.

Tip bildirimleri

Tip bildirimi bir yorum satırı değildir. Çalışma zamanında denetlenir ve uyuşmayan bir değer geldiğinde TypeError fırlatılır. Parametre, dönüş değeri ve sınıf özelliği için ayrı ayrı yazılabilir.

PHP19 satır
function fiyatHesapla(float $tutar, int $adet): float
{
    return $tutar * $adet;
}

function kullaniciBul(int $id): ?Kullanici   // nesne ya da null
{
    return $this->depo->getir($id);
}

function kaydet(array $veri): void           // değer döndürmez
{
    // ...
}

function hataVer(string $mesaj): never       // hiç dönmez, akış burada biter
{
    throw new RuntimeException($mesaj);
}

Sınıf özelliklerinde de bildirim kullanılır. Bildirimli bir özellik, değer atanmadan okunursa Error fırlatır. Bu davranış sessiz bir null dolaşmasından daha iyidir, çünkü sorun kaynağında görünür.

PHP8 satır
final class Siparis
{
    public function __construct(
        public readonly string $numara,
        public readonly float $tutar,
        private ?string $aciklama = null,
    ) {}
}

Katı mod: declare(strict_types=1)

Varsayılan gevşek modda PHP, bildirilen tipe uyacak şekilde değeri dönüştürmeye çalışır. Metin olarak gelen "3" değeri float bekleyen bir parametreye sorunsuz girer. Katı modda bu dönüşüm yapılmaz ve uyuşmayan değer hata verir.

PHP9 satır
declare(strict_types=1);   // dosyanın ilk ifadesi olmak zorunda

function oran(float $x): float
{
    return $x * 2;
}

oran(3);      // float(6) - tam sayıdan ondalığa genişletme katı modda da serbesttir
oran("3");    // TypeError: Argument #1 ($x) must be of type float, string given

İki ayrıntı önemlidir. Birincisi, bildirim dosya düzeyinde çalışır ve çağrıyı yapan dosyanın ayarı geçerlidir; kütüphane dosyasında katı mod açık olsa bile çağrı gevşek modda yazılmış bir dosyadan geliyorsa dönüşüm uygulanır. İkincisi, tam sayıdan ondalık sayıya genişletme katı modda da kabul edilir, çünkü değer kaybı oluşmaz.

Yeni yazılan her dosyada katı modun açılması önerilir. Dönüşümün örtük olarak yapılmadığı bir kod tabanında, veri sınırdan içeri girerken bilinçli olarak dönüştürülür ve tipi belirsiz değer taşınmaz.

Gevşek ve sıkı karşılaştırma

== operatörü karşılaştırmadan önce tip dönüşümü uygular, === ise hem değeri hem tipi karşılaştırır. PHP 8 ile metin ve sayı karşılaştırması değişti: artık sayı metne dönüştürülüp metin olarak karşılaştırılıyor, tersi değil.

PHP8 satır
var_dump(0 == "foo");     // bool(false)  - PHP 7'de true idi
var_dump("abc" == 0);     // bool(false)
var_dump("1" == "01");    // bool(true)   - iki taraf da sayısal metin
var_dump("10" == "1e1");  // bool(true)   - 10 ile 10.0 aynı sayı
var_dump(null == false);  // bool(true)
var_dump("" == null);     // bool(true)

var_dump("1" === "01");   // bool(false)  - tip ve değer birebir karşılaştırılır

Kural basit tutulabilir: karşılaştırmada varsayılan olarak === kullanılır. Gevşek karşılaştırma yalnızca iki tarafın tipi bilinçli olarak biliniyorsa tercih edilir. Aynı gerekçe in_array ve array_search için de geçerlidir; bu fonksiyonlar varsayılan olarak gevşek karşılaştırma yapar ve üçüncü parametre true verilmeden beklenmedik eşleşme üretebilir.

PHP5 satır
$roller = ['0', 'admin', 'editor'];

var_dump(in_array('admin', $roller));        // bool(true)
var_dump(in_array(0, $roller));              // bool(true)  - '0' ile eşleşti
var_dump(in_array(0, $roller, strict: true)); // bool(false) - doğru davranış

Sayısal metinleri güvenle dönüştürmek

Kullanıcıdan gelen her değer metindir. Sorgu dizesindeki sayfa numarası ya da form alanındaki tutar, sayıya çevrilmeden kullanılmamalıdır. Doğrudan tür dönüştürme yapmak en kolay yol gibi görünse de hatalı girdiyi sessizce sıfıra çevirdiği için tercih edilmez.

PHP11 satır
$ham = $_GET['sayfa'] ?? '1';

$yanlis = (int) $ham;        // "abc" değeri sessizce 0 olur

$sayfa = filter_var($ham, FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 500],
]);

if ($sayfa === false) {
    throw new InvalidArgumentException('Geçersiz sayfa numarası');
}

Aritmetik işlemlerde de davranış tipe göre değişir. Sayısal olmayan bir metin işleme girdiğinde TypeError fırlatılır; başı sayısal olan bir metin ise uyarı üretip baştaki sayıyı kullanır. İkinci durum daha tehlikelidir, çünkü kod çalışmaya devam eder.

PHP6 satır
"abc" + 1;      // TypeError: Unsupported operand types: string + int
"5 elma" + 1;   // int(6) ve "A non-numeric value encountered" uyarısı

is_numeric("1e3");   // true - bilimsel gösterim de sayısaldır
is_numeric("12 ");   // true - sondaki boşluk yok sayılır
is_numeric(" 12");   // true - baştaki boşluk da yok sayılır

Para tutarları ondalık sayı ile tutulmamalıdır. İkilik tabanda tam olarak gösterilemeyen değerler nedeniyle toplamlar kuruş düzeyinde kayar. Tutarlar ya en küçük birim cinsinden tam sayı olarak ya da ondalık aritmetik sağlayan bir kütüphaneyle saklanır.

PHP5 satır
var_dump(0.1 + 0.2 === 0.3);          // bool(false)
var_dump(abs((0.1 + 0.2) - 0.3) < PHP_FLOAT_EPSILON); // bool(true)

// tutarı kuruş cinsinden tam sayı olarak tutmak en dayanıklı yöntemdir
$tutarKurus = 1999;   // 19,99

Null ile çalışmak

null, değerin bulunmadığını anlatır ve sıfır ya da boş metin ile aynı anlama gelmez. Bu ayrımı korumak için birleştirme operatörleri kullanılır.

PHP8 satır
$ad = $veri['ad'] ?? 'Bilinmiyor';     // yoksa ya da null ise varsayılan
$ayar['dil'] ??= 'tr';                 // yalnızca tanımsızsa ata

$sehir = $kullanici?->adres?->sehir;   // zincirde null varsa sonuç null olur

// PHP 8.4 ile örtük nullable bildirimi kullanımdan kalktı
function eski(string $ad = null) {}    // Deprecated uyarısı üretir
function dogru(?string $ad = null) {}  // açık bildirim

Dizilerde isset ve array_key_exists farkı gözden kaçar. Değeri null olan bir anahtar için isset false döner; anahtarın gerçekten var olup olmadığı sorgulanacaksa array_key_exists kullanılır.

Birleşik ve kesişim tipleri

Bir değer birden fazla tip alabiliyorsa bu durum bildirimde açıkça yazılabilir. Birleşik tip alternatifleri, kesişim tipi ise aynı anda sağlanması gereken arayüzleri anlatır.

PHP18 satır
function bicimle(int|float $sayi): string
{
    return number_format($sayi, 2, ',', '.');
}

function say(Countable&Traversable $koleksiyon): int
{
    return count($koleksiyon);
}

abstract class Model
{
    // static dönüş tipi yalnızca sınıf içinde kullanılır, alt sınıfta kendi tipini gösterir
    public static function olustur(array $veri): static
    {
        return new static($veri);
    }
}

mixed bildirimi her tipi kabul eder ve pratikte bildirim yazmamakla aynı kapıya çıkar. Bir parametrenin tipi gerçekten belirsizse, sorun genellikle bildirimde değil tasarımdadır: aynı parametreye farklı anlamda değerler taşınıyordur.

Dizi bildirimi de tek başına yeterli bilgi vermez. array yazan bir parametrenin içinde ne olduğu koddan anlaşılmaz. İki çözüm vardır: belge etiketiyle içerik tipini bildirmek ya da diziyi bir nesneyle değiştirmek. İkincisi daha güvenlidir, çünkü denetim çalışma zamanına taşınır.

PHP17 satır
/** @param list<Urun> $urunler */
function toplam(array $urunler): int
{
    return array_sum(array_map(static fn (Urun $u): int => $u->fiyatKurus, $urunler));
}

// daha dayanıklı karşılığı: koleksiyonu türü belli bir nesneye almak
final class UrunListesi
{
    /** @var list<Urun> */
    private array $ogeler = [];

    public function ekle(Urun $urun): void
    {
        $this->ogeler[] = $urun;
    }
}

Sık yapılan hatalar

  • Girdi doğrulamasını tür dönüştürme ile yapmak. (int) hatalı değeri sıfıra çevirir ve sorun sonraki katmanda ortaya çıkar.
  • Boş kontrolünü empty ile yapmak. Bu fonksiyon "0" ve 0 değerlerini de boş sayar; sayısal alanlarda yanlış sonuç verir.
  • Katı modu yalnızca bazı dosyalarda açmak. Karışık kullanım, hangi çağrının dönüştürüldüğünü takip etmeyi zorlaştırır.
  • Ondalık sayıları eşitlik ile karşılaştırmak. Karşılaştırma, kabul edilebilir bir fark üzerinden yapılmalıdır.
  • Dönüş tipi bildirimini atlamak. Çağıran taraf tipi bilmediği için gereksiz denetim kodu birikir.

Kontrol listesi

  • Yeni dosyaların başında declare(strict_types=1) bulunuyor.
  • Parametre, dönüş değeri ve sınıf özelliklerinde tip bildirimi yazılıyor.
  • Karşılaştırmalarda === varsayılan; in_array çağrılarında katı kip açık.
  • Dış girdi filter_var ya da eşdeğeri ile doğrulanıp dönüştürülüyor.
  • Para tutarları ondalık sayı yerine en küçük birimde tam sayı olarak tutuluyor.
  • null ihtimali olan her dönüş değeri bildirimde ? ile işaretleniyor.

İlgili yazılar

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

  1. Kurulum ve temel sözdizimi
  2. Döngüler ve akış kontrolü
  3. Diziler
  4. Fonksiyonlar
  5. String fonksiyonları
  6. Süper globaller
  7. Form işleme
  8. Dosya işlemleri

Dilin ayrıntıları

  1. Tip sistemi ve strict_types (bu yazı)
  2. Hata ve istisna yönetimi
  3. Düzenli ifadeler
  4. Tarih ve saat işlemleri

Nesne yönelimli PHP

  1. Sınıf ve nesne
  2. Kalıtım, arayüz ve trait
  3. Statik üyeler, sabitler ve enum
  4. Namespace, Composer ve autoload

Veri ve güvenlik

  1. PDO ile veritabanı işlemleri
  2. Oturum yönetimi ve kimlik doğrulama
  3. Güvenli çıktı ve XSS
  4. JSON ile veri değişimi

Dış dünya ve üretim

  1. cURL ile dış API çağırma
  2. Kendi REST API'ni yazmak
  3. PHPUnit ile birim test
  4. Hata ayıklama ve performans ölçümü
  5. Üretime alma ve güvenlik

Yorumlar

Bu form reCAPTCHA ile korunur; Google Gizlilik Politikası ve Hizmet Şartları geçerlidir.

İlk yorumu sen yap.

İlgili Yazılar