PHP 8.5'in en çok konuşulan yeniliği, bir değeri fonksiyondan fonksiyona aktaran pipe operatörü |> oldu. Bunun yanında clone() artık bir fonksiyon gibi çağrılıp kopyadaki özellikleri (readonly olanlar dahil) değiştirebiliyor, #[\NoDiscard] özniteliği dönüş değeri kullanılmayan çağrılarda uyarı veriyor, array_first() ve array_last() geldi ve URL ayrıştırma için her zaman etkin olan yeni bir URI eklentisi eklendi.

Aşağıdaki örneklerin hepsini PHP 8.5.12 CLI ile çalıştırdık; çıktı yorumları gerçek çıktıdır. Yararlı olduğu yerlerde aynı işin PHP 8.4 ve öncesinde nasıl yapıldığını da yanına koyduk, çünkü birçok sunucu hâlâ eski sürümlerle çalışıyor. Bir önceki sürümün yenilikleri için PHP 8.4 yenilikleri, serinin tamamı için PHP öğrenme rehberi yazılarına bakabilirsiniz.

PHP 8.5 yeniliklerine kısa bakış

PHP 8.5 yeniliklerine kısa bakış: Pipe operatörü |>, clone($nesne, [...]), #[\NoDiscard], (void) dönüşümü
YenilikNe sağlarEski yöntem
Pipe operatörü |>Değeri sağdaki tek parametreli callable'a aktarırİç içe fonksiyon çağrısı veya geçici değişken
clone($nesne, [...])Kopyalarken özellik değiştirme, readonly dahil__clone içinde elle atama, Reflection
#[\NoDiscard]Dönüş değeri yok sayılırsa uyarıYalnızca belgeleme
(void) dönüşümüDönüş değerini bilerek yok saymayı belirtirYok
array_first, array_lastDizinin ilk ve son değerireset, end veya array_key_first
URI eklentisiRFC 3986 ve WHATWG URL ayrıştırmaparse_url()
Sabit ifadelerinde closureVarsayılan parametre, öznitelik argümanı olarak closureMümkün değildi
Fatal error backtraceÖlümcül hatalarda yığın iziYalnızca hata satırı
FILTER_THROW_ON_FAILUREDoğrulama başarısızsa istisnafalse kontrolü

Pipe operatörü |>

Pipe operatörü sol taraftaki değeri alır, sağ taraftaki callable'a tek argüman olarak verir ve o çağrının sonucunu üretir. Kılavuzun Functional Operators sayfasına göre sağ taraf; closure, strlen(...) gibi first-class callable ya da __invoke() uygulayan bir nesne olabilir. Tek bir çağrı için pek fark yaratmaz, asıl yararı birden fazla dönüşümü soldan sağa okunur bir zincir hâline getirmesidir.

php
<?php
$slug = '  PHP 8.5 Yenilikleri  '
    |> trim(...)
    |> strtolower(...)
    |> (fn($s) => str_replace([' ', '.'], '-', $s));

echo $slug, PHP_EOL; // çıktı: php-8-5-yenilikleri

// PHP 8.4 ve öncesinde aynı iş:
$slug2 = str_replace([' ', '.'], '-', strtolower(trim('  PHP 8.5 Yenilikleri  ')));
echo $slug2, PHP_EOL; // çıktı: php-8-5-yenilikleri

İki satır aynı sonucu verir; fark okuma yönündedir. İç içe çağrıda en içteki trim() ilk çalışır ama satırda en sağda durur. Pipe zincirinde ise işlemler yazıldığı sırayla, yukarıdan aşağıya akar. Ok fonksiyonunun paranteze alındığına dikkat edin: kılavuz, sözdizimi belirsizliğini önlemek için pipe ile kullanılan arrow function'ların parantez içinde yazılması gerektiğini, aksi hâlde ölümcül hata alınacağını belirtir.

Pipe operatörünün iki önemli sınırı vardır. Birincisi, sağdaki callable tek bir zorunlu parametre almalıdır; birden fazla zorunlu parametresi olan bir fonksiyon, eksik argümanla çağrılmış gibi hata verir. İkincisi, parametresini referansla alan fonksiyonlar kullanılamaz. Bu yüzden diziyi yerinde sıralayan sort() pipe ile çalışmaz:

php
<?php
$sonuc = [3, 1, 2] |> sort(...);
/* çıktı:
Fatal error: Uncaught Error: sort(): Argument #1 ($array) could not be passed by reference in /yol/ornek.php:2
Stack trace:
#0 {main}
  thrown in /yol/ornek.php on line 2
*/

Böyle durumlarda değeri döndüren bir ok fonksiyonu yazabilir ya da dönüşümü klasik değişken atamasıyla yapabilirsiniz. Closure ve first-class callable sözdizimi PHP anonim fonksiyonlar ve arrow function yazısında anlatılıyor.

clone() ile özellik değiştirerek kopyalama

readonly özellikli değişmez (immutable) nesnelerde "bir alanı değiştirilmiş kopya" döndüren withX() tarzı metotlar yazmak PHP 8.4'e kadar zahmetliydi, çünkü readonly özelliğe kopyada da yeniden yazılamıyordu. PHP 8.5'te clone aynı zamanda bir fonksiyondur ve ikinci parametresi withProperties, kopyada değiştirilecek özellikleri bir dizi olarak alır. Kılavuza göre bu değişiklikler __clone() çalıştıktan sonra uygulanır ve özelliklerin görünürlük kurallarına uyar.

php
<?php
final class Para
{
    public function __construct(
        public readonly int $tutar,
        public readonly string $birim = 'TRY',
    ) {}

    public function ekle(int $miktar): static
    {
        return clone($this, ['tutar' => $this->tutar + $miktar]);
    }
}

$a = new Para(100);
$b = $a->ekle(50);
echo $a->tutar, ' ', $a->birim, PHP_EOL; // çıktı: 100 TRY
echo $b->tutar, ' ', $b->birim, PHP_EOL; // çıktı: 150 TRY

Orijinal nesne değişmedi; yalnızca kopyadaki tutar güncellendi, birim kopyalandığı değerle kaldı. Değişiklikler görünürlüğe uyduğu için yukarıdaki çağrı sınıfın içinden yapıldı; dışarıdan clone($a, ['tutar' => 1]) yazmak, readonly özelliğin yazma kapsamı sınıf ve alt sınıflarla sınırlı olduğundan "Cannot modify protected(set) readonly property Para::$tutar from global scope" hatasını verir. Klonlamanın sığ kopya davranışı ve __clone() ile derin kopya için PHP clone ve nesne karşılaştırma yazısına bakın.

#[\NoDiscard] özniteliği ve (void) dönüşümü

Bazı fonksiyonların dönüş değerini kontrol etmemek neredeyse her zaman bir hatadır: başarı bilgisini döndüren bir kayıt fonksiyonu ya da yeni bir kopya döndüren değişmez bir nesne metodu gibi. #[\NoDiscard] ile işaretlenen bir fonksiyon, sonucu hiçbir şekilde kullanılmadan çağrılırsa PHP 8.5 uyarı verir. Bilerek yok saymak istediğinizde yeni (void) dönüşümünü kullanırsınız.

php
<?php
#[\NoDiscard('dosya yazılamamış olabilir')]
function kaydet(string $veri): bool
{
    return $veri !== '';
}

kaydet('merhaba');          // uyarı verir
$tamam = kaydet('merhaba'); // uyarı yok: değer atandı
(void) kaydet('merhaba');   // uyarı yok: bilerek yok sayıldı
echo 'bitti', PHP_EOL;
/* çıktı:
Warning: The return value of function kaydet() should either be used or intentionally ignored by casting it as (void), dosya yazılamamış olabilir in /yol/ornek.php on line 8
bitti
*/

Kılavuzun The NoDiscard attribute sayfası iki ayrıntıya dikkat çeker. Öznitelikler geriye uyumlu olduğu için #[\NoDiscard] eklenmiş kod PHP 8.4 ve öncesinde de çalışır, yalnızca uyarı üretilmez. Ancak (void) dönüşümü 8.5 öncesinde desteklenmez; eski sürümleri de destekleyecekseniz değeri $_ gibi bir değişkene atamanız önerilir. Ayrıca öznitelik, çağrılan somut bildirime göre değerlendirilir: interface veya abstract metoda konan #[\NoDiscard] uyarı üretmez, trait'ten gelen metot ise özniteliği korur. Öznitelik sözdizimi için PHP attributes yazısına bakın.

array_first() ve array_last()

array_first() ve array_last()

array_first() dizinin ilk, array_last() son değerini döndürür; dizi boşsa sonuç null olur. Buradaki "ilk", en küçük anahtar değil, dizideki sıra bakımından ilk elemandır. PHP 8.4 ve öncesinde aynı iş için array_key_first() ile anahtarı alıp diziye erişirdiniz ya da diziyi değiştirmeyi göze alarak iç işaretçiyi oynatan reset() ve end() kullanırdınız.

php
<?php
$sehirler = [3 => 'İzmir', 1 => 'Ankara', 7 => 'Bursa'];

var_dump(array_first($sehirler)); // çıktı: string(6) "İzmir"
var_dump(array_last($sehirler));  // çıktı: string(5) "Bursa"
var_dump(array_first([]));        // çıktı: NULL

// PHP 8.4 ve öncesi:
var_dump($sehirler[array_key_first($sehirler)]); // çıktı: string(6) "İzmir"
var_dump($sehirler[array_key_last($sehirler)]);  // çıktı: string(5) "Bursa"

"İzmir" için string(6) görmeniz şaşırtmasın: var_dump() bayt sayısını gösterir ve "İ" UTF-8'de iki bayttır. array_find() ile aynı tuzak burada da geçerli: dizinin ilk değeri gerçekten null ise boş diziyle ayırt edemezsiniz. Dizi fonksiyonlarının geneli için PHP dizi fonksiyonları yazısına bakabilirsiniz.

URI eklentisi: Uri\Rfc3986\Uri ve Uri\WhatWg\Url

PHP 8.5 her zaman etkin olan yeni bir uri eklentisiyle geliyor. Eklenti iki standardı ayrı sınıflarla uygular: Uri\Rfc3986\Uri genel URI standardı RFC 3986'yı, Uri\WhatWg\Url ise tarayıcıların kullandığı WHATWG URL standardını izler. Her iki sınıf da değişmezdir (readonly); withPath(), withQuery() gibi metotlar yeni bir nesne döndürür. Kurucu (constructor) geçersiz adreste istisna fırlatır, statik parse() ise null döndürür.

php
<?php
use Uri\Rfc3986\Uri;
use Uri\WhatWg\Url;
use Uri\WhatWg\InvalidUrlException;

$uri = new Uri('https://kullanici@ornek.com:8080/blog/yazi?sayfa=2#yorumlar');
var_dump($uri->getScheme(), $uri->getHost(), $uri->getPort(), $uri->getPath(), $uri->getQuery());
/* çıktı:
string(5) "https"
string(9) "ornek.com"
int(8080)
string(10) "/blog/yazi"
string(7) "sayfa=2"
*/

$yeni = $uri->withPath('/iletisim')->withQuery(null)->withFragment(null);
echo $yeni->toString(), PHP_EOL; // çıktı: https://kullanici@ornek.com:8080/iletisim

$url = new Url('HTTPS://Örnek.com.tr/a/../b');
echo $url->toAsciiString(), PHP_EOL;   // çıktı: https://xn--rnek-4qa.com.tr/b
echo $url->toUnicodeString(), PHP_EOL; // çıktı: https://örnek.com.tr/b

var_dump(Uri::parse('http://[bozuk')); // çıktı: NULL
try {
    new Url('bozuk adres');
} catch (InvalidUrlException $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
    // çıktı: Uri\WhatWg\InvalidUrlException: The specified URI is malformed (MissingSchemeNonRelativeUrl)
}

WHATWG sınıfının şemayı küçük harfe çevirdiğine, /a/../b yolunu /b olarak normalleştirdiğine ve Türkçe karakterli alan adını Punycode biçimine (xn--) dönüştürdüğüne dikkat edin. Kullanıcıdan gelen bağlantıları tarayıcının göreceği biçimde işlemek istiyorsanız WHATWG, genel amaçlı URI'ler (örneğin urn: veya özel şemalar) için RFC 3986 sınıfı daha uygundur.

Sabit ifadelerinde closure ve tür dönüşümü

PHP 8.5 ile closure'lar ve first-class callable'lar sabit ifadelerinde kullanılabiliyor: öznitelik argümanları, özellik ve parametre varsayılan değerleri, sabitler. Aynı sürümle sabit ifadelerinde (int) gibi tür dönüşümleri de serbest kaldı. Kılavuzun örneğinde const T1 = (int) 0.3; satırı eskiden "Constant expression contains invalid operations" hatası veriyordu.

php
<?php
const KATSAYI = (int) '7';

function uygula(int $x, Closure $islem = static function (int $n): int { return $n * KATSAYI; }): int
{
    return $islem($x);
}

echo uygula(3), PHP_EOL;                  // çıktı: 21
echo uygula(3, fn($n) => $n + 1), PHP_EOL; // çıktı: 4

Hata yönetiminde yenilikler

Kılavuzun hata yönetimi sayfasındaki değişiklik kaydına göre PHP 8.5'te, örneğin çalışma süresi sınırının aşılması gibi ölümcül hatalar artık bir yığın izi (stack trace) içeriyor. Bu, hatanın hangi çağrı zincirinde oluştuğunu görmeyi çok kolaylaştırır:

php
<?php
function yavas(): void
{
    while (true) {
    }
}
set_time_limit(1);
yavas();
/* çıktı:
Fatal error: Maximum execution time of 1 second exceeded in /yol/ornek.php on line 4
Stack trace:
#0 /yol/ornek.php(8): yavas()
#1 {main}
*/

Diğer yenilikler: get_error_handler() ve get_exception_handler() ile o anda kayıtlı işleyiciyi okuyabiliyorsunuz. Filter eklentisine gelen FILTER_THROW_ON_FAILURE bayrağı, doğrulama başarısız olduğunda false döndürmek yerine Filter\FilterFailedException fırlatıyor.

php
<?php
set_error_handler(fn() => true);
var_dump(get_error_handler() instanceof Closure); // çıktı: bool(true)
var_dump(get_exception_handler());                // çıktı: NULL

try {
    filter_var('abc', FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE);
} catch (Filter\FilterFailedException $e) {
    echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
    // çıktı: Filter\FilterFailedException: filter validation failed: filter int not satisfied by 'abc'
}
var_dump(filter_var('42', FILTER_VALIDATE_INT, FILTER_THROW_ON_FAILURE)); // çıktı: int(42)

Kılavuza göre bu bayrak FILTER_NULL_ON_FAILURE ile birlikte kullanılamaz; ikisini birleştirmek ValueError fırlatır. Filtrelerin geneli için PHP filter_var ile veri doğrulama, hata işleyicileri için PHP error_reporting ve hata seviyeleri yazılarına bakın.

Nesne yönelimli küçük eklemeler ve Intl

Nesne yönelimli küçük eklemeler ve Intl: Statik özelliklerde asimetrik görünürlük, Özelliklerde #[\Override], final özelliklerde constructor promotion, Sabitlerde öznitelik
  • Statik özelliklerde asimetrik görünürlük: public private(set) static int $sayac artık yazılabiliyor.
  • Özelliklerde #[\Override]: öznitelik, üst sınıftaki bir özelliği bilerek yeniden bildirdiğinizi belirtmek için özelliklere de konabiliyor. Ayrıntı: PHP kalıtım, abstract ve interface.
  • final özelliklerde constructor promotion: yapıcıda tanıtılan özellikler final olarak işaretlenebiliyor.
  • Sabitlerde öznitelik: const ile bildirilen genel sabitlere öznitelik eklenebiliyor; #[\Deprecated] artık sabitlerde de kullanılabiliyor.
  • Closure::getCurrent(): o anda çalışan closure'ı döndürür; isimsiz fonksiyonlarda özyineleme için kullanılabilir.
  • IntlListFormatter: bir listeyi yerel ayara göre "ve" veya "veya" ile birleştirir.

İlk iki maddeyi tek örnekte görelim. Kılavuza göre Closure::getCurrent(), closure değişkenini use (&$f) ile referans olarak yakalamadan özyinelemeli closure yazmayı sağlar; bir closure dışında çağrılırsa Error fırlatır. Statik özellikteki private(set) ise sayacın dışarıdan okunabilmesini ama yalnızca sınıf içinden artırılabilmesini garanti eder.

php
<?php
$faktoriyel = function (int $n): int {
    return $n <= 1 ? 1 : $n * Closure::getCurrent()($n - 1);
};
echo $faktoriyel(5), PHP_EOL; // çıktı: 120

class Ziyaret
{
    public private(set) static int $sayi = 0;

    public static function kaydet(): void
    {
        self::$sayi++;
    }
}
Ziyaret::kaydet();
Ziyaret::kaydet();
echo Ziyaret::$sayi, PHP_EOL; // çıktı: 2
try {
    Ziyaret::$sayi = 99;
} catch (Error $e) {
    echo $e->getMessage(), PHP_EOL;
    // çıktı: Cannot modify private(set) property Ziyaret::$sayi from global scope
}

Intl eklentisindeki IntlListFormatter ise kullanıcıya gösterilecek listeleri dil kurallarına göre birleştirmek için kullanışlıdır; virgül ve bağlaç seçimini sizin yerinize yapar:

php
<?php
$fmt = new IntlListFormatter('tr_TR', IntlListFormatter::TYPE_AND);
echo $fmt->format(['elma', 'armut', 'muz']), PHP_EOL; // çıktı: elma, armut ve muz
$fmt2 = new IntlListFormatter('tr_TR', IntlListFormatter::TYPE_OR);
echo $fmt2->format(['çay', 'kahve']), PHP_EOL;        // çıktı: çay veya kahve
var_dump(locale_is_right_to_left('ar'));                // çıktı: bool(true)

PHP 8.5'te eskimiş sayılanlar

PHP 8.5 Deprecated Features sayfası, 8.5'e geçerken karşılaşacağınız yeni uyarıları listeler. Dil düzeyinde en sık rastlanacak olanlar şunlardır: kanonik olmayan tür dönüşüm adları (integer), (boolean), (double), (binary); sayısal olmayan bir metni ++ ile artırmak; dizi anahtarı olarak null kullanmak; case satırını iki nokta yerine noktalı virgülle bitirmek; shell_exec() yerine ters tırnak operatörü kullanmak.

php
<?php
$x = (integer) '5';
$s = 'a9';
$s++;
$d = [null => 'boş'];
echo str_increment('a9'), PHP_EOL;
/* çıktı:
Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead in /yol/ornek.php on line 2

Deprecated: Increment on non-numeric string is deprecated, use str_increment() instead in /yol/ornek.php on line 4

Deprecated: Using null as an array offset is deprecated, use an empty string instead in /yol/ornek.php on line 5
b0
*/

Düzeltmeler basittir: (int), (bool), (float), (string) yazın; harf ve rakam artırma için str_increment() kullanın; null yerine boş metin anahtarı verin. Ayrıca __sleep() ve __wakeup() yumuşak biçimde eskimiş sayıldı, yerine __serialize() ve __unserialize() önerilir (PHP sihirli metotlar). Kendi kaynakları otomatik serbest bırakılan curl_close(), finfo_close(), imagedestroy() ve xml_parser_free() çağrıları da artık eskimiş. Bir önceki sürümün listesi için PHP 8.4 deprecated uyarıları yazısına bakın.

Geriye dönük uyumsuzluklar

PHP 8.5 Backward Incompatible Changes sayfasındaki dil düzeyindeki başlıca maddeler şunlar:

  • null dışındaki dizi olmayan değerleri [] veya list() ile parçalamak (destructuring) artık uyarı veriyor.
  • int olarak temsil edilemeyen bir float (veya float gibi görünen metin) int'e dönüştürüldüğünde uyarı üretiliyor; NAN başka tiplere dönüştürülürken de uyarı var.
  • "array" ve "callable" artık class_alias() ile takma ad olarak kullanılamıyor.
  • Trait'ler artık üst sınıftan önce bağlanıyor; çok ince bir davranış değişikliği olsa da karmaşık kalıtım hiyerarşilerinde test etmeye değer.
  • Soyut sınıf, enum, interface veya trait üzerine #[\Attribute] koymak derleme sırasında hata veriyor.
  • OPcache artık her zaman PHP ikili dosyasına derleniyor ve yükleniyor; opcache.enable ayarları hâlâ geçerli.

Sık yapılan hatalar

  • Pipe zincirinde ok fonksiyonunu parantezsiz yazmak: |> fn($x) => ... ölümcül hata verir, |> (fn($x) => ...) yazın.
  • Birden fazla zorunlu parametresi olan fonksiyonu doğrudan pipe'a vermek: str_replace(...) gibi fonksiyonları bir ok fonksiyonuyla sarın.
  • 8.5 sözdizimini eski sunucuya göndermek: |> ve (void), PHP 8.4 ve öncesinde ayrıştırma hatası (ParseError) üretir; bu hatanın nasıl okunacağı PHP fatal error çözümleri yazısında.
  • clone() ile dışarıdan readonly özellik değiştirmeye çalışmak: değişiklikler görünürlük kurallarına uyar, bu yüzden withX() tarzı metotları sınıfın içine yazın.

Sık Sorulan Sorular

Pipe operatörü performansı etkiler mi?

Kılavuz pipe zincirini, aynı işi geçici değişkenlerle yapan koda mantıksal olarak eşdeğer tanımlar. Performans ölçümüne dair bir bilgi vermez; okunabilirlik için kullanın ve gerçekten kritik bir döngüde kendi ölçümünüzü yapın.

parse_url() kullanmayı bırakmalı mıyım?

parse_url() kaldırılmadı ve eskimiş sayılmadı. Yeni URI sınıfları standartlara uyan ayrıştırma, normalleştirme ve with*() metotlarıyla güvenli değişiklik sunar. PHP 8.5'e geçtiyseniz yeni kodda bu sınıfları tercih etmek mantıklıdır; eski sürümleri desteklemeniz gerekiyorsa parse_url() ile devam edebilirsiniz.

#[\NoDiscard] eklediğim kütüphane PHP 8.4 kullanan projelerde bozulur mu?

Hayır. Öznitelikler geriye uyumlu tasarlandığı için eski sürümler bilmedikleri özniteliği çalışma sırasında dikkate almaz ve kod çalışmaya devam eder. Bozulmaya yol açabilecek olan, 8.5'e özgü (void) dönüşümünü ya da pipe operatörünü kodun içinde kullanmaktır; bunlar eski sürümlerde ayrıştırılamaz.

Sunucumun PHP 8.5 olup olmadığını nasıl anlarım?

Komut satırında php -v çalıştırın ya da kodda PHP_VERSION sabitini yazdırın. CLI ile web sunucusunun farklı PHP sürümleri kullanabileceğini unutmayın; komut satırı kullanımı için PHP CLI, sürüm seçimi için PHP hosting rehberi yazılarına bakın.

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.4 yenilikleri · Anonim fonksiyonlar · clone ve nesne karşılaştırma · PHP attributes · PHP nedir