PHP 8.4, kılavuzun deyişiyle 8.3 serisinin ardından gelen "yeni bir minor sürüm"dür; ancak dile getirdiği değişiklikler günlük kod yazımını doğrudan etkileyecek kadar görünürdür. En önemli başlıklar şunlardır: özelliklere get ve set mantığı ekleyen property hooks, okuma ve yazma görünürlüğünü ayıran asimetrik görünürlük, başlatılması ertelenen lazy objects, parantez gerektirmeyen new zincirlemesi, array_find() ailesi, mb_trim() gibi çok baytlı metin fonksiyonları ve kendi kodunuzu eskimiş olarak işaretlemenizi sağlayan #[\Deprecated] özniteliği.

Bu yazıda her yeniliği php.net kılavuzundaki sürüm geçiş (migration) notlarına dayanarak anlatıyor, her biri için PHP 8.5.12 CLI üzerinde çalıştırdığımız kısa bir örnek veriyoruz. Yükseltme yapmadan önce bilmeniz gereken geriye dönük uyumsuzlukları da ayrı bir bölümde topladık. Eskimiş (deprecated) ilan edilen özellikleri ise PHP 8.4 deprecated uyarıları yazısında ayrıntılı ele alıyoruz. Serinin tamamı için PHP öğrenme rehberine bakabilirsiniz.

PHP 8.4 yeniliklerine kısa bakış

PHP 8.4 yeniliklerine kısa bakış: Property hooks, Asimetrik görünürlük, Lazy objects, new zincirleme

Aşağıdaki tablo, kılavuzun "New Features" ve "New Functions" sayfalarındaki dil düzeyindeki başlıkları özetliyor. cURL, PGSQL, Sodium gibi eklentilere özgü onlarca küçük ekleme de var; onlar için PHP 8.4 New Functions sayfasının tamamına bakmanız gerekir.

YenilikNe sağlarKılavuz bölümü
Property hooksÖzellik okuma/yazma sırasında kod çalıştırma, sanal özelliklanguage.oop5.property-hooks
Asimetrik görünürlükpublic private(set) gibi ayrı yazma görünürlüğülanguage.oop5.visibility
Lazy objectsNesne durumuna ilk erişilene kadar başlatmayı ertelemelanguage.oop5.lazy-objects
new zincirlemenew Sinif()->metot() parantezsizmigration84.new-features
#[\Deprecated]Kendi fonksiyon, metot ve sınıf sabitlerinizi eskimiş işaretlemeclass.deprecated
array_find ailesiarray_find, array_find_key, array_any, array_allmigration84.new-functions
mbstring eklerimb_trim, mb_ltrim, mb_rtrim, mb_ucfirst, mb_lcfirstmigration84.new-functions
RoundingMode enumround() için okunur yuvarlama modları ve 4 yeni modmigration84.other-changes
Dom\HTMLDocumentHTML5 uyumlu yeni DOM sınıflarımigration84.new-features
BcMath\NumberOperatörlerle kullanılabilen keyfi hassasiyetli sayı nesnesimigration84.new-classes

Property hooks: özelliklere get ve set mantığı eklemek

Eskiden bir özelliğe yazılan değeri doğrulamak ya da okurken hesaplamak istediğinizde özelliği private yapıp getAd(), setAd() gibi metotlar yazardınız. PHP 8.4 ile bu mantığı doğrudan özelliğin kendisine bağlayabilirsiniz. Bir hook, özelliğe erişildiği anda devreye giren küçük bir kod bloğudur. Yalnızca get hook'u olan ve kendi değerini hiç saklamayan özelliğe kılavuz "virtual" (sanal) özellik der; böyle bir özelliğe yazmaya çalışmak hata üretir.

php
<?php
class Kisi
{
    public string $tamAd {
        get => $this->ad . ' ' . $this->soyad;
    }

    public string $ad {
        set => mb_convert_case($value, MB_CASE_TITLE);
    }

    public string $soyad {
        set {
            if (mb_strlen($value) < 2) {
                throw new InvalidArgumentException('Soyad çok kısa');
            }
            $this->soyad = $value;
        }
    }
}

$k = new Kisi();
$k->ad = 'ayşe';
$k->soyad = 'Yılmaz';
echo $k->ad, PHP_EOL;     // çıktı: Ayşe
echo $k->tamAd, PHP_EOL;  // çıktı: Ayşe Yılmaz

try {
    $k->tamAd = 'Başka Biri';
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL; // çıktı: Property Kisi::$tamAd is read-only
}

Örnekte üç farklı kullanım var. $tamAd yalnızca okunur ve değer saklamaz. $ad için kısa (=>) set sözdizimi kullanıldı: dönen değer, özelliğe yazılan değer olur. $soyad ise blok gövdeli set kullanıyor ve değeri kendisi $this->soyad ile yazıyor. Hook içindeki $value, dışarıdan atanan değeri tutar. Hooks konusunun tüm kuralları (interface içinde özellik bildirimi, kalıtım, referans kısıtları) için ayrı yazımız PHP property hooks rehberine bakın.

Asimetrik görünürlük: public private(set)

Bir özelliği herkesin okuyabilmesini ama yalnızca sınıfın kendisinin değiştirebilmesini istiyorsanız PHP 8.4 öncesinde ya readonly kullanırdınız (bir kez yazılır, sonra hiç değişmez) ya da özelliği gizleyip okuma metodu yazardınız. Asimetrik görünürlük bu ikisinin arasındaki boşluğu doldurur: ilk anahtar kelime okuma, parantezli olan ise yazma görünürlüğünü belirler.

php
<?php
class Siparis
{
    public function __construct(
        public private(set) string $durum = 'yeni',
    ) {}

    public function onayla(): void
    {
        $this->durum = 'onaylandı';
    }
}

$s = new Siparis();
echo $s->durum, PHP_EOL; // çıktı: yeni
$s->onayla();
echo $s->durum, PHP_EOL; // çıktı: onaylandı

try {
    $s->durum = 'iptal';
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL;
    // çıktı: Cannot modify private(set) property Siparis::$durum from global scope
}

Kılavuzun Visibility sayfası birkaç kuralı özellikle vurgular:

  • Ayrı set görünürlüğü yalnızca tipi bildirilmiş (typed) özelliklerde kullanılabilir.
  • Yazma görünürlüğü okuma görünürlüğüyle aynı ya da ondan daha kısıtlı olmalıdır; protected public(set) sözdizimi hatasıdır.
  • Okuma görünürlüğü public ise yazılmayabilir: private(set) string $durum ile public private(set) string $durum aynı sonucu verir.
  • private(set) olan bir özellik otomatik olarak final sayılır, alt sınıfta yeniden bildirilemez.

Görünürlük kurallarının tamamını ve readonly ile karşılaştırmasını public, private, protected ve readonly yazısında bulabilirsiniz.

new ifadesini parantezsiz zincirlemek

PHP 8.3 ve öncesinde yeni oluşturduğunuz bir nesnenin metodunu aynı ifadede çağırmak için new ifadesini paranteze almak zorundaydınız. PHP 8.4 ile yapıcıya argüman verilen new ifadeleri doğrudan zincirlenebilir. Bu, akıcı arayüz (fluent interface) kullanan kodları sadeleştirir.

php
<?php
class Sayac
{
    public function __construct(public int $deger = 0) {}

    public function artir(): static
    {
        $this->deger++;
        return $this;
    }
}

// PHP 8.3 ve öncesi: parantez şarttı
echo (new Sayac(5))->artir()->deger, PHP_EOL; // çıktı: 6

// PHP 8.4: parantezsiz zincirleme
echo new Sayac(5)->artir()->artir()->deger, PHP_EOL; // çıktı: 7

Kılavuz bu kolaylığı özellikle "yapıcı argümanları olan new ifadeleri" için tanımlar; argümansız yazımda bile new Sayac() şeklinde parantez kullanmanız okunabilirliği korur. Sınıf ve nesne temelleri için PHP sınıf ve nesne yazısına göz atabilirsiniz.

Yeni dizi fonksiyonları: array_find, array_find_key, array_any, array_all

Yeni dizi fonksiyonları: array_find, array_find_key, array_any, array_all

Bir dizide koşula uyan ilk elemanı bulmak için şimdiye kadar foreach ile döngü kurup break kullanıyor ya da array_filter() sonucunun ilk elemanını alıyordunuz. İkinci yöntem bütün diziyi dolaştığı için gereksiz iş yapar. PHP 8.4 ile gelen dört fonksiyon, geri çağırma (callback) fonksiyonunu eleman ve anahtarla çağırır ve sonucu bulduğu anda durur.

php
<?php
$urunler = [
    'k1' => ['ad' => 'Klavye', 'stok' => 12],
    'm1' => ['ad' => 'Mouse', 'stok' => 0],
    'e1' => ['ad' => 'Ekran', 'stok' => 4],
];

$tukenen = array_find($urunler, fn($u) => $u['stok'] === 0);
echo $tukenen['ad'], PHP_EOL; // çıktı: Mouse

echo array_find_key($urunler, fn($u) => $u['stok'] === 0), PHP_EOL; // çıktı: m1

var_dump(array_any($urunler, fn($u) => $u['stok'] > 10));  // çıktı: bool(true)
var_dump(array_all($urunler, fn($u) => $u['stok'] > 0));   // çıktı: bool(false)
var_dump(array_find($urunler, fn($u) => $u['stok'] > 100)); // çıktı: NULL

Dikkat edilmesi gereken bir nokta var: array_find() eşleşme bulamazsa null döndürür. Dizinizde gerçekten null değerli elemanlar varsa "bulunamadı" ile "bulunan eleman null" durumunu ayıramazsınız. Böyle bir dizide array_find_key() kullanıp dönen anahtarın null olup olmadığına bakmak daha güvenlidir. Bu fonksiyonların array_map() ve array_filter() ile birlikte kullanımı PHP dizi fonksiyonları yazısında anlatılıyor.

mbstring ekleri: mb_trim, mb_ucfirst ve mb_lcfirst

Klasik trim() yalnızca boşluk, sekme, satır sonu gibi tek baytlık karakterleri temizler. Kopyala-yapıştır ile gelen metinlerde sık görülen bölünmez boşluk (U+00A0) ya da ideografik boşluk (U+3000) gibi Unicode boşlukları olduğu yerde kalır. mb_trim(), mb_ltrim() ve mb_rtrim() bu karakterleri de varsayılan listesinde tutar. Benzer şekilde ucfirst() bayt düzeyinde çalıştığı için "ç" gibi harfleri büyütemez; mb_ucfirst() ise ilk karakteri Unicode kurallarına göre başlık harfine çevirir.

php
<?php
$metin = "\u{00A0} istanbul \u{3000}";
var_dump(mb_trim($metin));         // çıktı: string(8) "istanbul"
var_dump(strlen(trim($metin)));    // çıktı: int(15)
echo mb_ucfirst('çağrı'), PHP_EOL; // çıktı: Çağrı
echo ucfirst('çağrı'), PHP_EOL;    // çıktı: çağrı
echo mb_lcfirst('Şehir'), PHP_EOL; // çıktı: şehir

#[\Deprecated] özniteliği

PHP'nin kendi fonksiyonları eskidiğinde E_DEPRECATED uyarısı üretir. PHP 8.4 aynı mekanizmayı sizin kodunuza açar: bir fonksiyonu, metodu ya da sınıf sabitini #[\Deprecated] ile işaretlediğinizde onu çağıran her kod bir uyarı alır. Kılavuza göre tek fark, üretilen hata kodunun E_USER_DEPRECATED olmasıdır. Özniteliğin message ve since adlı iki isteğe bağlı parametresi uyarı metnine eklenir.

php
<?php
#[\Deprecated(message: 'yerine yeniHesapla() kullanın', since: '2.0')]
function eskiHesapla(int $x): int
{
    return $x * 2;
}

echo eskiHesapla(4), PHP_EOL;
/* çıktı:
Deprecated: Function eskiHesapla() is deprecated since 2.0, yerine yeniHesapla() kullanın in /yol/ornek.php on line 8
8
*/

Kütüphane yazıyorsanız bu öznitelik, bir fonksiyonu kaldırmadan önce kullanıcılarınıza en az bir sürüm boyunca haber vermenin standart yoludur. Öznitelik sözdizimi ve Reflection ile okunması PHP attributes yazısının konusu.

Lazy objects: başlatmayı ertelenen nesneler

Lazy object, durumuna ilk kez bakılana ya da durumu ilk kez değiştirilene kadar gerçekten başlatılmayan nesnedir. Kılavuz bunun tipik kullanım alanlarını bağımlılık enjeksiyonu kapsayıcıları, veritabanından ancak erişildiğinde dolan ORM varlıkları ve ayrıştırmayı erteleyen JSON okuyucuları olarak sayar. İki strateji vardır: nesneyi yerinde başlatan ghost ve gerçek bir nesneye yönlendiren proxy. Her ikisi de ReflectionClass üzerindeki yeni metotlarla oluşturulur.

php
<?php
class Rapor
{
    public function __construct(public array $satirlar)
    {
        echo 'Rapor hazırlanıyor', PHP_EOL;
    }
}

$r = new ReflectionClass(Rapor::class);
$rapor = $r->newLazyGhost(function (Rapor $nesne) {
    $nesne->__construct(['a', 'b', 'c']);
});

echo 'Nesne oluşturuldu', PHP_EOL;
var_dump($r->isUninitializedLazyObject($rapor)); // çıktı: bool(true)
echo count($rapor->satirlar), PHP_EOL;          // önce "Rapor hazırlanıyor", sonra 3
var_dump($r->isUninitializedLazyObject($rapor)); // çıktı: bool(false)
/* tam çıktı:
Nesne oluşturuldu
bool(true)
Rapor hazırlanıyor
3
bool(false)
*/

Çıktı sırasına dikkat edin: "Rapor hazırlanıyor" ancak $rapor->satirlar okunduğunda yazdırıldı. Kılavuz; özellik okuma ve yazma, isset(), foreach ile özellikleri dolaşma, serialize(), json_encode() ve klonlamanın başlatmayı tetiklediğini; var_dump() ile nesneyi incelemenin ise tetiklemediğini belirtir. Lazy objects yalnızca kullanıcı tanımlı sınıflar ve stdClass için desteklenir. Günlük uygulama kodundan çok framework ve kütüphane yazarlarına yönelik bir özelliktir; ayrıntılar için Lazy Objects sayfasına bakın.

round() ve RoundingMode enum

round() fonksiyonunun mode parametresi artık RoundingMode enum değeri de kabul ediyor. Eski PHP_ROUND_* sabitlerine ek olarak sıfıra doğru, sıfırdan uzağa, pozitif ve negatif sonsuza doğru yuvarlayan dört yeni mod yalnızca bu enum üzerinden kullanılabilir. Kılavuz ayrıca yuvarlama kodunun yeniden yazıldığını ve bazı hataların düzeltildiğini not eder: örneğin 0.49999999999999994 eskiden 1.0 veriyordu, şimdi doğru sonuç olan 0.0 dönüyor.

php
<?php
echo round(2.5), PHP_EOL;                                       // çıktı: 3
echo round(2.5, 0, RoundingMode::HalfEven), PHP_EOL;           // çıktı: 2
echo round(-2.7, 0, RoundingMode::TowardsZero), PHP_EOL;       // çıktı: -2
echo round(2.1, 0, RoundingMode::PositiveInfinity), PHP_EOL;   // çıktı: 3
var_dump(round(0.49999999999999994));                           // çıktı: float(0)

Ayrıca geçersiz bir mod değeri verildiğinde round() artık sessizce PHP_ROUND_HALF_UP kullanmıyor, ValueError fırlatıyor. Enum kavramının kendisi için PHP enum kullanımı yazısına bakabilirsiniz.

Diğer dikkat çekici eklemeler

Diğer dikkat çekici eklemeler: PDO sürücü alt sınıfları, Dom\HTMLDocument, BcMath\Number, DateTime eklemeleri

Dil çekirdeği dışındaki eklentilerde de kullanışlı yenilikler var. Aşağıdaki örnek, her biri PHP 8.4 ile gelen dört API'yi tek dosyada gösteriyor:

php
<?php
$db = PDO::connect('sqlite::memory:');
echo get_class($db), PHP_EOL; // çıktı: Pdo\Sqlite

$t = DateTimeImmutable::createFromTimestamp(1700000000.5);
echo $t->format('Y-m-d H:i:s.u'), PHP_EOL; // çıktı: 2023-11-14 22:13:20.500000

$doc = Dom\HTMLDocument::createFromString('<p class="not">Merhaba <b>dünya</b></p>', LIBXML_NOERROR);
echo $doc->querySelector('p.not b')->textContent, PHP_EOL; // çıktı: dünya

$n = new BcMath\Number('0.1');
echo $n + '0.2', PHP_EOL; // çıktı: 0.3
  • PDO sürücü alt sınıfları: PDO::connect() bağlantı dizesine göre Pdo\Sqlite, Pdo\Mysql, Pdo\Pgsql gibi sürücüye özgü bir alt sınıf döndürür. Her sürücünün kendi SQL ayrıştırıcısı da eklendi. PDO temelleri için PHP PDO yazısına bakın.
  • Dom\HTMLDocument: Dom ad alanındaki yeni sınıflar HTML5 ile uyumludur ve WHATWG belirtimine uyar; eski DOMDocument geriye dönük uyumluluk için duruyor.
  • BcMath\Number: BCMath işlemlerini +, - gibi operatörlerle yapabilen nesne; para hesaplarında kayan nokta hatalarından kaçınmak için kullanılabilir.
  • DateTime eklemeleri: createFromTimestamp(), getMicrosecond() ve setMicrosecond(). Tarih işlemleri için PHP DateTime yazısı var.
  • request_parse_body(): PUT ve PATCH gibi POST dışı isteklerde gelen multipart gövdeyi ayrıştırır.
  • fpow(): IEEE 754 kurallarına göre üs alır; sıfırın negatif kuvvetinin eskimiş sayılmasıyla birlikte geldi.
  • PCRE 10.44: değişken uzunluklu lookbehind desteği ve yeni r değiştiricisi. Ancak {,3} artık metin değil, nicelik belirteci olarak yorumlanıyor.

Yükseltmeden önce: geriye dönük uyumsuzluklar

PHP 8.4 Backward Incompatible Changes sayfası, kodunuzu bozabilecek değişiklikleri listeler. Kılavuz en başta önemli bir uyarı yapar: her yeni fonksiyon, sınıf veya sabit, sizin aynı adla tanımladığınız bir sembolle çakışıp yeniden bildirim hatasına yol açabilir. Örneğin projenizde kendi array_find() fonksiyonunuz varsa, PHP 8.4 üzerinde "Cannot redeclare" hatası alırsınız. Bu hatanın çözümü PHP fatal error çözümleri yazısında.

exit() ve die() artık bir fonksiyon gibi davranıyor: callable olarak geçirilebiliyor, strict_types ayarından etkileniyor ve geçersiz tipte argüman verildiğinde TypeError fırlatıyor.

php
<?php
$sonuc = exit(...);
var_dump($sonuc instanceof Closure); // çıktı: bool(true)
try {
    exit([]);
} catch (TypeError $e) {
    echo $e->getMessage(), PHP_EOL;
    // çıktı: exit(): Argument #1 ($status) must be of type string|int, array given
}
  • E_STRICT kaldırıldı: hata seviyesi motor tarafından artık kullanılmıyor, E_STRICT sabiti de eskimiş sayılıyor.
  • Bcrypt maliyeti arttı: password_hash() fonksiyonunda PASSWORD_BCRYPT için varsayılan cost değeri 10'dan 12'ye çıktı. Bu, hash üretimini yavaşlatır ama güvenliği artırır. Ayrıntı: PHP password_hash.
  • Karşılaştırmada özyineleme: karşılaştırma sırasında sonsuz özyineleme oluşursa artık E_ERROR ölümcül hatası yerine Error istisnası fırlatılıyor; yani yakalanabiliyor.
  • Kaynaktan nesneye geçiş: DBA, ODBC ve SOAP'ta bazı resource türleri nesneye dönüştü. is_resource() ile yapılan kontroller false veya null kontrolüyle değiştirilmeli.
  • Kaldırılan eklentiler: IMAP, OCI8, PDO_OCI ve PSpell çekirdek dağıtımdan çıkarılıp PECL'e taşındı.
  • Yeni ValueError durumları: round(), str_getcsv(), php_uname() gibi fonksiyonlar geçersiz argümanda artık uyarı yerine ValueError fırlatıyor. Bu hata türleri TypeError ve ValueError yazısında anlatılıyor.

Sık yapılan hatalar

  • Hook tanımlanan özelliğe readonly eklemeye çalışmak: kılavuz property hooks ile readonly özelliklerin birlikte kullanılamayacağını belirtir; salt okunur davranış için yalnızca get hook'u veya private(set) kullanın.
  • Tipsiz özelliğe private(set) yazmak: asimetrik görünürlük yalnızca tipi bildirilmiş özelliklerde geçerlidir.
  • array_find() sonucunu doğrudan dizi gibi kullanmak: eşleşme yoksa null döner ve $sonuc['ad'] "Trying to access array offset on null" uyarısı üretir. Bu uyarının çözümü undefined variable ve array key yazısında.
  • Yeni fonksiyonları eski sunucuda çalıştırmak: array_find() veya mb_trim() kullanan kod PHP 8.3 üzerinde "Call to undefined function" hatası verir. Sunucunuzdaki sürümü php -v ile kontrol edin.

Sık Sorulan Sorular

PHP 8.4'e geçmek için kodumu değiştirmem gerekir mi?

Çoğu kod değişiklik gerektirmeden çalışır, ama eskimiş özellik uyarıları alabilirsiniz. En yaygın olanı, varsayılan değeri null olan tipli parametrelerde açık nullable işaretinin eksik olmasıdır. Geçişten önce PHP 8.4 Backward Incompatible Changes ve eskimiş özellikler listesini kodunuzla karşılaştırın, uygulamanızı E_ALL hata raporlamasıyla test edin.

Property hooks getter ve setter metotlarının yerini tamamen alır mı?

Basit doğrulama ve hesaplanan değerler için evet, büyük ölçüde. Ancak birden fazla parametre alan ya da yan etkisi büyük olan işlemler için açık bir metot genellikle daha anlaşılırdır. Hook'lar özelliğe erişim sözdizimini korurken mantık eklemek için tasarlanmıştır.

Lazy objects kendi projemde işime yarar mı?

Doğrudan kullanım daha çok bağımlılık enjeksiyonu kapsayıcıları ve ORM gibi altyapı kodları içindir. Bu tür bir kütüphane kullanıyorsanız, kütüphane PHP 8.4 üzerinde bu özelliği kendi içinde kullanabilir ve siz farkına varmadan bellek ve başlatma maliyetinden kazanırsınız.

PHP 8.4 mü PHP 8.5 mi kullanmalıyım?

Her iki sürüm de php.net tarafından destekleniyor. 8.5, pipe operatörü ve clone() ile özellik değiştirme gibi ek dil özellikleri getirir; ayrıntılar PHP 8.5 yenilikleri yazısında. Sunucu tarafında sürüm seçimi ve barındırma konuları için PHP hosting rehberine bakabilirsiniz.

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 · PHP 8.5 yenilikleri · PHP 8.4 deprecated uyarıları · Property hooks · Görünürlük ve readonly · PHP nedir