PHP 8.0: Veri Tipleri (2/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 ikincisinde veri tiplerine bakacağız.

Tarihe geri dönelim. PHP 7'nin temel atılımı, skaler tip ipuçlarının getirilmesiydi. Neredeyse gerçekleşmiyordu. declare(strict_types=1) sayesinde tamamen geriye dönük uyumlu ve isteğe bağlı olan harika çözümün yazarı Andrea Faulds, topluluk tarafından çirkin bir şekilde reddedildi. Neyse ki, o zamanlar Anthony Ferrara hem onu hem de önerisini savundu, bir kampanya başlattı ve RFC çok az bir farkla geçti. Ufff. PHP 8'deki yeniliklerin çoğu efsanevi Nikita Popov tarafından yapıldı ve oylamada tereyağından kıl çeker gibi geçti. Dünya daha iyiye doğru değişiyor.

PHP 8, tipleri mükemmelliğe taşıyor. Koddaki @param, @return ve @var phpDoc ek açıklamalarının büyük çoğunluğu ortadan kalkacak ve yerini yerel yazım ve en önemlisi PHP motoru tarafından kontrol alacak. Yorumlarda yalnızca string[] gibi yapıların açıklamaları veya PHPStan için daha karmaşık ek açıklamalar kalacak.

Union Tipleri

Union tipleri, bir değerin alabileceği iki veya daha fazla tipin bir listesidir:

class Button
{
	private string|object $caption;

	public function setCaption(string|object $caption)
	{
		$this->caption = $caption;
	}
}

PHP zaten bazı özel union tiplerini biliyordu. Örneğin, ?string gibi nullable tipler, bu string|null union tipinin eşdeğeridir ve soru işareti yazımı sadece bir kısaltma olarak kabul edilebilir. Elbette PHP 8'de de çalışır, ancak dikey çizgilerle birleştirilemez, yani ?string|object yerine tam string|object|null yazmak gerekir. Ayrıca, iterable her zaman array|Traversable eşdeğeriydi. Belki de float'ın aslında int|float kabul eden, ancak float'a dönüştüren bir union tipi olması sizi şaşırtabilir.

Union'larda void ve mixed sahte tipleri kullanılamaz, çünkü bunun hiçbir anlamı olmazdı.

Nette birlik türleri için hazır. Schema'da, Expect::from() bunları kabul eder ve sunum yapanlar da bunları kabul eder. Bunları örneğin render ve action yöntemlerinde kullanabilirsiniz:

public function renderDetail(int|array $id)
{
	...
}

Tersine, Nette DI'daki otomatik kablolama (autowiring) union tiplerini reddeder. Henüz, örneğin bir yapıcının ya o ya da bu nesneyi kabul etmesinin anlamlı olacağı bir kullanım durumu yoktur. Elbette ortaya çıkarsa, konteynerin davranışını buna göre ayarlamak mümkün olacaktır.

Nette\Utils\Reflection'daki getParameterType(), getReturnType() ve getPropertyType() metotları, union tipi durumunda bir istisna fırlatır (sürüm 3.1'de, eski 3.0 sürümünde uyumluluk nedeniyle null döndürürler).

mixed

mixed sahte tipi, değerin kesinlikle herhangi bir şey olabileceğini söyler.

Parametreler ve özellikler durumunda, bu aslında hiçbir tip belirtmediğimiz zamankiyle aynı davranıştır. Öyleyse ne işe yarar? Tipin basitçe eksik olduğu ile gerçekten mixed olduğu arasında ayrım yapabilmek için.

Fonksiyon ve metotların dönüş değeri durumunda, tip belirtmemek ile mixed tipini belirtmek farklıdır. Aslında void'un tersidir, çünkü fonksiyonun bir şey döndürmesi gerektiğini söyler. Eksik return o zaman ölümcül bir hatadır.

Pratikte, onu nadiren kullanmalısınız, çünkü union tipleri sayesinde değeri daha kesin olarak belirleyebilirsiniz. Bu nedenle istisnai durumlarda kullanışlıdır:

function dump(mixed $var): mixed
{
	// değişkeni yazdır
	return $var;
}

false

Yeni false sahte tipi ise yalnızca union tiplerinde kullanılabilir. Başarısızlık durumunda tarihsel olarak false döndüren yerel fonksiyonların dönüş değerinin tipini yerel olarak tanımlama ihtiyacından doğmuştur:

function strpos(string $haystack, string $needle): int|false
{
}

Bu nedenle, true tipi yoktur, tek başına false veya false|null ya da bool|false kullanılamaz.

static

static sahte tipi yalnızca bir metodun dönüş tipi olarak kullanılabilir. Metodun, nesnenin kendisiyle aynı türden bir nesne döndürdüğünü söyler (oysa self, metodun tanımlandığı sınıfı döndürdüğünü söyler). Bu, akıcı arayüzleri (fluent interfaces) tanımlamak için mükemmeldir:

class Item
{
	public function setValue($val): static
	{
		$this->value = $val;
		return $this;
	}
}

class ItemChild extends Item
{
	public function childMethod()
	{
	}
}

$child = new ItemChild;
$child->setValue(10)
	->childMethod();

resource

Bu tip PHP 8'de mevcut değildir ve gelecekte de getirilmeyecektir. Kaynaklar (Resources), PHP'nin henüz nesnelere sahip olmadığı zamanlardan kalma tarihsel bir kalıntıdır. Kaynaklar yavaş yavaş nesnelerle değiştirilecek ve zamanla bu tip tamamen ortadan kalkacaktır. Örneğin, PHP 8.0, resmi temsil eden kaynağı GdImage nesnesiyle ve curl bağlantı kaynağını CurlHandle nesnesiyle değiştirir.

Stringable

Bu, sihirli __toString() metoduna sahip her nesnenin otomatik olarak uyguladığı bir arayüzdür.

class Email
{
	public function __toString(): string
	{
		return $this->value;
	}
}

function print(Stringable|string $s)
{
}

print('abc');
print(new Email);

Sınıf tanımında açıkça class Email implements Stringable belirtmek mümkündür, ancak gerekli değildir.

Bu adlandırma stili, önceki IHtmlString yerine Nette\HtmlStringable arayüzünü uygulayan Nette\Utils\Html tarafından da yansıtılır. Bu tür nesneler daha sonra örneğin Latte tarafından kaçış işlemine tabi tutulmaz.

Tip varyansı, kontravaryans, kovaryans

Liskov Yerine Geçme Prensibi (Liskov Substitution Principle – LSP), bir sınıfın alt sınıflarının ve arayüz uygulamalarının asla ebeveynden daha fazlasını gerektirmemesi ve daha azını sağlamaması gerektiğini söyler. Yani, bir alt sınıfın metodu, ebeveynden daha fazla argüman gerektiremez veya parametrelerde daha dar bir tip aralığını kabul edemez ve tersine daha geniş bir tip aralığı döndüremez. Ancak daha dar bir aralık döndürebilir. Neden? Çünkü aksi takdirde kalıtım hiç çalışmazdı. Fonksiyon belirli bir türden bir nesneyi kabul etse bile, metotlara hangi parametrelerin iletilebileceğini ve gerçekte ne döndüreceklerini bilemezdi, çünkü herhangi bir alt sınıf bunu bozabilirdi.

Dolayısıyla OOP'de şu geçerlidir: alt sınıf şunları yapabilir:

  • parametrelerde daha geniş bir tip aralığını kabul edebilir (buna kontravaryans denir)
  • daha dar bir tip aralığı döndürebilir (kovaryans)
  • ve özellikler tipi değiştiremez (değişmezdirler)

PHP bunu 7.4 sürümünden beri yapabiliyor ve PHP 8.0'da tanıtılan tüm yeni tipler de kontravaryans ve kovaryansı destekliyor.

mixed durumunda, alt sınıf dönüş değerini herhangi bir tipe daraltabilir, ancak void'a daraltamaz, çünkü bu bir değer tipi değil, onun yokluğudur. Alt sınıf da tip belirtmeyemez, çünkü bu da yokluğa izin verir.

class A
{
    public function foo(mixed $foo): mixed
    {}
}

class B extends A
{
    public function foo($foo): string
    {}
}

Ayrıca union tipleri parametrelerde genişletilebilir ve dönüş değerlerinde daraltılabilir:

class A
{
    public function foo(string|int $foo): string|int
    {}
}

class B extends A
{
    public function foo(string|int|float $foo): string
    {}
}

Ayrıca, false parametrede bool'a genişletilebilir veya tersine bool dönüş değerinde false'a daraltılabilir.

Kovaryans/kontravaryansa karşı tüm ihlaller PHP 8.0'da ölümcül hataya yol açar.

Gelecek bölümlerde 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.