PHP 8.0: Yeniliklere Genel Bakış (1/4)

4 yıl önce Yazan David Grudl  

PHP 8.0 sürümü yayınlandı. Daha önceki hiçbir sürümde olmadığı kadar yeniliklerle dolu. Tanıtımları tam dört ayrı makale gerektirdi. Bu ilkinde, dil açısından neler getirdiğine bakacağız.

PHP'ye dalmadan önce, Nette'nin mevcut sürümünün sekizinci sürüm için tamamen hazır olduğunu bilin. Dahası, hediye olarak Nette 2.4 de yayınlandı ve onunla tamamen uyumlu, bu nedenle framework açısından yeni sürümü kullanmaya başlamanıza hiçbir engel yok.

Adlandırılmış Argümanlar

Ve hemen oyunun kurallarını değiştiren olarak nitelendirilebilecek bir bomba ile başlayalım. Artık fonksiyonlara ve metotlara argümanlar sadece konumsal olarak değil, aynı zamanda isimlerine göre de iletilebilir. Bu, bir metodun gerçekten çok sayıda parametresi olduğunda kesinlikle harika:

class Response implements IResponse
{
	public function setCookie(
		string $name,
		string $value,
		string|DateInterface|null $time,
		string $path = null,
		string $domain = null,
		bool $secure = null,
		bool $httpOnly = null,
		string $sameSite = null
	) {
		...
	}
}

İlk iki argümanı konumsal olarak, diğerlerini isme göre ileteceğiz: (adlandırılmış olanlar her zaman konumsal olanlardan sonra gelmelidir)

$response->setCookie('lang', $lang, sameSite: 'None');

// çılgınca yerine
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Bu özelliğin gelmesinden önce, Nette'de çerez göndermek için yeni bir API oluşturma planı vardı, çünkü setCookie() parametrelerinin sayısı gerçekten arttı ve konumsal yazım anlaşılmazdı. Artık buna gerek yok, çünkü adlandırılmış argümanlar bu durumda en pratik API'dir. IDE'ler onlara ipucu verecek ve tip kontrolüne sahip olacaklar.

Mantıksal parametreleri açıklamak için de harikadırlar; burada kullanımları zorunlu olmasa da, tek başına true veya false pek bir şey ifade etmez:

// eskiden
$db = $container->getService(Database::class, true);

// şimdi
$db = $container->getService(Database::class, need: true);

Parametre adları artık genel API'nin bir parçası haline geliyor. Şimdiye kadar olduğu gibi keyfi olarak değiştirilemezler. Bu nedenle, Nette de tüm parametrelerin uygun adlandırmaya sahip olup olmadığını kontrol etmek için bir denetimden geçiyor.

Adlandırılmış argümanlar variadics ile birlikte de kullanılabilir:

function variadics($a, ...$args) {
	dump($args);
}

variadics(a: 1, b: 2, c: 3);
// $args içinde ['b' => 2, 'c' => 3] olacak

Artık $args dizisi sayısal olmayan anahtarlar da içerebilir, bu da belirli bir BC (Geriye Dönük Uyumluluk) kırılmasıdır. Aynı durum, $args dizisindeki anahtarların artık önemli bir rol oynadığı call_user_func_array($func, $args) fonksiyonunun davranışı için de geçerlidir. Tersine, func_*() ailesindeki fonksiyonlar adlandırılmış argümanlardan etkilenmez.

Adlandırılmış argümanlarla yakından ilgili olan bir diğer gerçek de, splat operatörü ...'nın artık ilişkisel dizileri de açabilmesidir:

variadics(...['b' => 2, 'c' => 3]);

Şaşırtıcı bir şekilde, bu henüz diziler içinde çalışmıyor:

$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys

Adlandırılmış argümanların ve variadics'in birleşimi, örneğin presenter'ın link() metodu için nihayet sabit bir sözdizimine sahip olma imkanı verir; şimdi ona adlandırılmış argümanları konumsal olanlar gibi iletebiliriz:

// eskiden
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // dizi olmak zorundaydı

// şimdi
$presenter->link('Product:detail', $id, page: 1);

Adlandırılmış argümanlar için sözdizimi, diziler yazmaktan çok daha seksidir, bu nedenle Latte bunu hemen benimsedi, örneğin {include} ve {link} etiketlerinde kullanılabilir:

{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}

Adlandırılmış parametrelere üçüncü bölümde niteliklerle bağlantılı olarak geri döneceğiz.

İfade bir istisna fırlatabilir

Bir istisna fırlatmak artık bir ifadedir. Örneğin, parantez içine alıp bir if koşuluna ekleyebilirsiniz. Hmmm, bu pek pratik görünmüyor. Ama bu daha ilginç:

// eskiden
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('değer ayarlanmadı');
}
$value = $arr['value'];


// şimdi, throw bir ifade olduğunda
$value = $arr['value'] ?? throw new \InvalidArgumentException('değer ayarlanmadı');

Ok fonksiyonları şimdiye kadar yalnızca tek bir ifade içerebildiğinden, bu özellik sayesinde istisnalar fırlatabilirler:

// sadece tek ifade
$fn = fn() => throw new \Exception('hay aksi');

Match İfadeleri

switch-case yapısının iki büyük kusuru vardır:

  • === yerine katı olmayan == karşılaştırmasını kullanır
  • yanlışlıkla break unutmamaya dikkat etmelisiniz

Bu nedenle PHP, katı karşılaştırma kullanan ve tersine break kullanmayan yeni match yapısı şeklinde bir alternatifle geliyor.

switch kodu örneği:

switch ($statusCode) {
    case 200:
    case 300:
        $message = $this->formatMessage('tamam');
        break;
    case 400:
        $message = $this->formatMessage('bulunamadı');
        break;
    case 500:
        $message = $this->formatMessage('sunucu hatası');
        break;
    default:
        $message = 'bilinmeyen durum kodu';
        break;
}

Ve aynısı (sadece katı karşılaştırma ile) match kullanılarak yazılmış:

$message = match ($statusCode) {
    200, 300 => $this->formatMessage('tamam'),
    400 => $this->formatMessage('bulunamadı'),
    500 => $this->formatMessage('sunucu hatası'),
    default => 'bilinmeyen durum kodu',
};

match'in switch gibi bir kontrol yapısı değil, bir ifade olduğuna dikkat edin. Örnekte sonuç değerini bir değişkene atıyoruz. Aynı zamanda, bireysel “seçenekler” de ifadelerdir, bu nedenle switch durumunda olduğu gibi birden fazla adım yazılamaz.

Seçeneklerden hiçbiriyle eşleşme olmazsa (ve default yan tümcesi yoksa), UnhandledMatchError istisnası fırlatılır.

Bu arada, Latte'de de {switch}, {case} ve {default} etiketleri vardır. İşleyişleri tam olarak yeni match'e karşılık gelir. Katı karşılaştırma kullanırlar, break gerektirmezler ve case'de virgülle ayrılmış birden fazla değer belirtilebilir.

Nullsafe operatörü

İsteğe bağlı zincirleme (optional chaining), null ile karşılaştığında değerlendirmesi duran bir ifade yazmayı sağlar. Ve bu, yeni ?-> operatörü sayesinde olur. Aksi takdirde tekrar tekrar null kontrolü yapacak birçok kodu değiştirir:

$user?->getAddress()?->street

// yaklaşık olarak şu anlama gelir
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

Neden “yaklaşık olarak anlamına gelir”? Çünkü gerçekte ifade daha akıllıca değerlendirilir ve hiçbir adım tekrarlanmaz. Örneğin, $user->getAddress() yalnızca bir kez çağrılır, bu nedenle metodun ilk ve ikinci kez farklı bir şey döndürmesinden kaynaklanan bir sorun oluşamaz.

Bu taze güzellik bir yıl önce Latte tarafından getirildi. Şimdi PHP'nin kendisine geliyor. Harika.

Constructor property promotion

Tipin iki kez ve değişkenin dört kez yazılmasından tasarruf sağlayan sözdizimsel şeker. Keşke bugün bizim için yazan bu kadar akıllı IDE'lere sahip olmadığımız bir zamanda gelmeseydi 🙂

class Facade
{
	private Nette\Database\Connection $db;
	private Nette\Mail\Mailer $mailer;

	public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
	{
		$this->db = $db;
		$this->mailer = $mailer;
	}
}
class Facade
{
	public function __construct(
		private Nette\Database\Connection $db,
		private Nette\Mail\Mailer $mailer,
	) {}
}

Nette DI ile çalışır, hemen kullanmaya başlayabilirsiniz.

Aritmetik ve bit düzeyinde operatörlerin katı davranışı

Bir zamanlar dinamik betik dillerini zirveye taşıyan şey, zamanla en zayıf noktaları haline geldi. PHP bir zamanlar “magic quotes”, global değişkenlerin kaydedilmesi gibi şeylerden kurtuldu ve şimdi gevşek davranış yerini katılığa bırakıyor. PHP'de toplama, çarpma vb. işlemleri neredeyse hiçbir anlam ifade etmeyen hemen hemen her veri tipiyle yapabildiğiniz zamanlar çoktan geride kaldı. Sürüm 7.0'dan başlayarak PHP giderek daha katı hale geldi ve sürüm 8.0'dan itibaren diziler, nesneler veya kaynaklar üzerinde herhangi bir aritmetik/bit düzeyinde operatör kullanma girişimi TypeError ile sonuçlanıyor. İstisna, dizilerin toplanmasıdır.

// aritmetik ve bit düzeyinde operatörler
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Daha mantıklı karakter dizisi ve sayı karşılaştırmaları

Veya gevşek operatörü yeniden harika yapın.

Gevşek operatör == için artık yer olmadığı, sadece === yazarken yapılan bir yazım hatası olduğu düşünülebilir, ancak bu değişiklik onu tekrar haritaya geri getiriyor. Madem elimizde var, mantıklı davransın. Önceki “mantıksız” karşılaştırmanın bir sonucu, örneğin sizi hoş olmayan bir şekilde yakalayabilen in_array() davranışıydı:

$validValues = ['foo', 'bar', 'baz'];
$value = 0;

dump(in_array($value, $validValues));
// şaşırtıcı bir şekilde true döndürüyordu
// PHP 8.0'dan itibaren false döndürüyor

== davranışındaki değişiklik, sayıların ve “sayısal” karakter dizilerinin karşılaştırılmasıyla ilgilidir ve aşağıdaki tabloyla gösterilir:

Karşılaştırma Öncesi PHP 8.0
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42 " true true
42 == "42foo" true false
42 == "abc42" false false
"42" == " 42" true true
"42" == "42 " false true

Şaşırtıcı olan, bunun dilin temelinde bir BC kırılması olması ve herhangi bir direniş olmadan onaylanmasıdır. Ve bu iyi bir şey. JavaScript burada çok kıskanabilirdi.

Hata Raporlama

Birçok dahili fonksiyon artık kolayca gözden kaçırılabilecek uyarılar yerine TypeError ve ValueError fırlatıyor. Çekirdekteki bir dizi uyarı yeniden sınıflandırıldı. Shutup operatörü @ artık ölümcül hataları susturmuyor. Ve PDO varsayılan modda istisnalar fırlatıyor.

Nette her zaman bu tür şeyleri bir şekilde çözmeye çalıştı. Tracy, shutup operatörünün davranışını değiştirdi, Database PDO'nun davranışını değiştirdi, Utils, göze çarpmayan uyarılar yerine istisnalar fırlatan standart fonksiyonların yerine geçenleri içerir vb. Nette'nin DNA'sında bulunan katı yönelimin dilin doğal yönelimi haline geldiğini görmek güzel.

Negatif indeksli diziler

$arr[-5] = 'first';
$arr[] = 'second';

İkinci elemanın anahtarı ne olacak? Eskiden 0 idi, PHP 8'den itibaren -4.

Sondaki virgül

Sondaki virgülün olamayacağı son yer fonksiyon parametrelerinin tanımıydı. Bu artık geçmişte kaldı:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // sondaki virgül
	) {
		....
	}

$object::class

Sihirli sabit ::class, $object::class nesneleriyle de çalışır ve böylece get_class() fonksiyonunu tamamen değiştirir.

Değişkensiz catch

Ve son olarak: catch yan tümcesinde istisna için bir değişken belirtmek gerekli değildir:

try {
	$container->getService(Database::class);

} catch (MissingServiceException) {  // $e yok
	$logger->log('....');
}

Gelecek bölümlerde veri tiplerindeki önemli yenilikler bizi bekliyor, niteliklerin ne olduğunu, PHP'de hangi yeni fonksiyonların ve sınıfların ortaya çıktığını göstereceğiz ve Just in Time Compiler'ı tanıtacağız.