PHP 8.0: Nitelikler (3/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 üçüncüsünde niteliklere bakacağız.

Nitelikler, sınıflara ve tüm üyelerine, ayrıca fonksiyonlara, closure'lara ve parametrelerine yapılandırılmış meta verileri yazmak için tamamen yeni bir yol getiriyor. Bu amaçla şimdiye kadar phpDoc yorumları kullanılıyordu, ancak sözdizimleri her zaman o kadar serbest ve tutarsızdı ki, makine tarafından işlenmeye başlanması mümkün değildi. Bu nedenle, sabit sözdizimine ve yansıma (reflection) sınıflarında desteğe sahip niteliklerle değiştiriliyorlar.

Dolayısıyla, şimdiye kadar phpDoc yorumlarını ayrıştırarak meta veri elde eden kütüphaneler, bunları niteliklerle değiştirebilecekler. Örneğin Nette, en son Application ve DI sürümlerinde @persistent, @crossOrigin ve @inject ek açıklamaları yerine Persistent, CrossOrigin ve Inject niteliklerini zaten kullanabilirsiniz.

Ek açıklamaları kullanan kod:

/**
 * @persistent(comp1, comp2)
 */
class SomePresenter
{
	/** @persistent */
	public $page = 1;

	/** @inject */
	public Facade $facade;

	/**
	 * @crossOrigin
	 */
	public function handleSomething()
	{
	}
}

Nitelikleri kullanarak aynısı:

use Nette\Application\Attributes\CrossOrigin;
use Nette\Application\Attributes\Persistent;
use Nette\DI\Attributes\Inject;

#[Persistent('comp1', 'comp2')]
class SomePresenter
{
	#[Persistent]
	public int $page = 1;

	#[Inject]
	public Facade $facade;

	#[CrossOrigin]
	public function handleSomething()
	{
	}
}

PHP, nitelik adlarını sanki sınıflarmış gibi, yani isim alanı (namespace) ve use yan tümceleri bağlamında değerlendirir. Dolayısıyla, örneğin şu şekilde de yazılabilirlerdi:

use Nette\Application\Attributes;

class SomePresenter
{
	#[Attributes\Persistent]
	public int $page = 1;

	#[\Nette\DI\Attributes\Inject]
	public Facade $facade;

Niteliği temsil eden sınıf var olabilir veya olmayabilir. Ancak var olması kesinlikle daha iyidir, çünkü o zaman editör yazarken ipucu verebilir, statik analizör yazım hatalarını tanıyabilir vb.

Sözdizimi

Şık olan şey, PHP 8'den önceki sürümlerin nitelikleri yalnızca yorum olarak görmesidir, bu nedenle eski sürümlerde çalışması gereken kodlarda bile kullanılabilirler.

Tek bir niteliğin yazımı, new operatörünü atlarsak bir nesne örneği oluşturmaya benzer. Yani, parantez içinde argümanların takip edebileceği sınıf adı:

#[Column('string', 32, true, false)]#
protected $username;

Ve işte PHP 8.0'ın yeni sıcak özelliğinin kullanılabileceği yer – adlandırılmış argümanlar:

#[Column(
	type: 'string',
	length: 32,
	unique: true,
	nullable: false,
)]#
protected $username;

Her öğe, ayrı ayrı veya virgülle ayrılarak yazılabilen birden fazla niteliğe sahip olabilir:

#[Inject]
#[Lazy]
public Foo $foo;

#[Inject, Lazy]
public Bar $bar;

Aşağıdaki nitelik üç özelliğin tümü için geçerlidir:

#[Common]
private $a, $b, $c;

Özelliklerin varsayılan değerlerinde, derleme sırasında değerlendirilebilen basit ifadeler ve sabitler kullanılabilir ve aynısı niteliklerin argümanları için de geçerlidir:

#[
	ScalarExpression(1 + 1),
	ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
	BitShift(4 >> 1, 4 << 1),
	BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]

Maalesef, bir argümanın değeri başka bir nitelik olamaz, yani nitelikler iç içe geçirilemez. Örneğin, Doctrine'de kullanılan aşağıdaki ek açıklama doğrudan niteliklere dönüştürülemez:

/**
 * @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
 */

Aynı şekilde, dosyanın başında bulunan ve örneğin Nette Tester tarafından kullanılan dosya phpDoc'u için bir nitelik eşdeğeri yoktur.

Nitelikleri Bulma

Hangi öğelerin hangi niteliklere sahip olduğunu yansıma (reflection) kullanarak öğreniriz. Yansıma sınıfları, ReflectionAttribute nesnelerinden oluşan bir dizi döndüren yeni bir getAttributes() metoduna sahiptir.

use MyAttributes\Example;

#[Example('Hello', 123)]
class Foo
{}

$reflection = new ReflectionClass(Foo::class);

foreach ($reflection->getAttributes() as $attribute) {
	$attribute->getName();      // tam nitelik adı, örn. MyAttributes\Example
	$attribute->getArguments(); // ['Hello', 123]
	$attribute->newInstance();  // new MyAttributes\Example('Hello', 123) örneğini döndürür
}

Döndürülen nitelikler bir parametre ile filtrelenebilir, örn. $reflection->getAttributes(Example::class) yalnızca Example niteliklerini döndürür.

Nitelik Sınıfları

MyAttributes\Example nitelik sınıfının var olması gerekmez. Yalnızca newInstance() metodunun çağrılması varlığını gerektirir çünkü onun örneğini oluşturur. Öyleyse onu yazalım. Tamamen sıradan bir sınıf olacak, sadece ona Attribute niteliğini (yani global sistem isim alanından) belirtmemiz gerekiyor:

namespace MyAttributes;

use Attribute;

#[Attribute]
class Example
{
	public function __construct(string $message, int $number)
	{
		...
	}
}

Niteliğin hangi dil öğelerinde kullanılmasının yasal olacağını sınırlamak mümkündür. Örneğin, bu şekilde yalnızca sınıflarda ve özelliklerde:

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
	...
}

TARGET_CLASS, TARGET_FUNCTION, TARGET_METHOD, TARGET_PROPERTY, TARGET_CLASS_CONSTANT, TARGET_PARAMETER bayrakları ve varsayılan TARGET_ALL mevcuttur.

Ancak dikkatli olun, doğru veya yanlış kullanımın doğrulanması şaşırtıcı bir şekilde yalnızca newInstance() metodu çağrıldığında gerçekleşir. Derleyicinin kendisi bu kontrolü yapmaz.

Niteliklerle Gelecek

Nitelikler ve yeni türler sayesinde, PHP dokümantasyon yorumları tarihlerinde ilk kez gerçekten sadece belgesel yorumlar haline gelecektir. PhpStorm, örneğin @deprecated ek açıklamasının yerini alabilecek özel nitelikler ile zaten geliyor. Ve bu niteliğin bir gün PHP'de varsayılan olarak yer alacağı varsayılabilir. Benzer şekilde, @throws vb. gibi diğer ek açıklamalar da değiştirilecektir.

Nette, kalıcı parametreleri ve bileşenleri işaretlemek için ilk sürümünden beri ek açıklamalar kullanmasına rağmen, bunların daha yaygın kullanımı gerçekleşmedi çünkü yerel bir dil yapısı değillerdi, bu yüzden editörler onlara ipucu vermiyordu ve içlerinde hata yapmak kolaydı. Bu sorun bugün editör eklentileriyle çözülmüş olsa da, niteliklerin getirdiği gerçekten yerel yol tamamen yeni olanaklar açıyor.

Bu arada, nitelikler, sınıf adının özgüllüğe (örn. Product, InvalidValue) ek olarak genelliği de (yani ProductPresenter, InvalidValueException) içermesini gerektiren Nette Kodlama Standardı'nda bir istisna kazandı. Aksi takdirde, kodda kullanıldığında sınıfın tam olarak neyi temsil ettiği açık olmazdı. Nitelikler için ise bu tam tersi istenmez, yani sınıf InjectAttribute yerine Inject olarak adlandırılır.

Son bölümde, PHP'de hangi yeni fonksiyonların ve sınıfların ortaya çıktığına bakacağız ve Just in Time Compiler'ı tanıtacağız.