PHP 8.0: Öznitelikler (3/4)

4 yıl önce Kimden David Grudl  

PHP 8.0 sürümü şu anda yayınlanıyor. Daha önce hiçbir sürümde olmadığı kadar yeni şeylerle dolu. Bunların tanıtımı dört ayrı makaleyi hak ediyordu. Üçüncü bölümde özniteliklere bir göz atacağız.

Nitelikler, sınıflar ve tüm üyelerinin yanı sıra işlevler, kapanışlar ve parametreleri için yapılandırılmış meta veriler yazmak için yepyeni bir yol sağlar. PhpDoc yorumları şimdiye kadar bu amaç için kullanıldı, ancak sözdizimleri her zaman o kadar gevşek ve tutarsızdı ki, bunları makine işlemeye başlamak mümkün değildi. Bu nedenle, sözdizimi belirlenmiş ve yansıma sınıflarında desteklenen niteliklerle değiştiriliyorlar.

Bu nedenle, daha önce phpDoc yorumlarını ayrıştırarak meta verileri alan kütüphaneler bunları özniteliklerle değiştirebileceklerdir. Bunun bir örneği, Application ve DI'nin son sürümlerinde @persistent, @crossOrigin ve @inject ek açıklamaları yerine Persistent, CrossOrigin ve Inject özniteliklerini kullanabileceğiniz Nette'dir.

Ek açıklamalar kullanarak kodlama:

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

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

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

Aynı şey nitelikler için de geçerlidir:

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 öznitelik isimlerini, isim alanları ve use cümleleri bağlamında sınıflar gibi değerlendirir. Bu nedenle, bunları örneğin aşağıdaki gibi yazmak bile mümkün olacaktır:

use Nette\Application\Attributes;

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

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

Özniteliği temsil eden sınıf var olabilir ya da olmayabilir. Ancak var olması kesinlikle daha iyidir, çünkü o zaman editör yazım sırasında önerebilir, statik analizci yazım hatalarını tanır, vb.

Sözdizimi

PHP'nin 8. sürümünden önce öznitelikleri yalnızca yorum olarak görmesi akıllıcadır, bu nedenle eski sürümlerde çalışması gereken kodlarda da kullanılabilirler.

Bireysel bir niteliğin sözdizimi, new operatörünü atlarsak bir nesne örneği oluşturmaya benzer. Yani, sınıfın adı ve ardından parantez içinde isteğe bağlı argümanlar:

#[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, tek tek yazılabilen veya virgülle ayrılabilen birden fazla özniteliğe sahip olabilir:

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

#[Inject, Lazy]
public Bar $bar;

Aşağıdaki özellik her üç özellik için de geçerlidir:

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

Derleme sırasında değerlendirilebilen ve özellikler için varsayılan değerler olarak kullanılan basit ifadeler ve sabitler, özniteliklerde bağımsız değişken olarak kullanılabilir:

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

Ne yazık ki, bir argümanın değeri başka bir öznitelik olamaz, yani öznitelikler iç içe geçemez. Örneğin, Doctrine'de kullanılan aşağıdaki ek açıklamanın özniteliklere dönüştürülmesi için doğrudan bir yol yoktur:

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

Ayrıca, örneğin Nette Tester tarafından kullanılan phpDoc dosyası, yani bir dosyanın başında bulunan bir yorum için eşdeğer bir öznitelik yoktur.

Öznitelik yansıması

Tek tek öğelerin hangi niteliklere sahip olduğu yansıma kullanılarak belirlenebilir. Reflection sınıfları, ReflectionAttribute nesnelerinden oluşan bir dizi döndüren yeni bir getAttributes() yöntemine sahiptir.

use MyAttributes\Example;

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

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

foreach ($reflection->getAttributes() as $attribute) {
	$attribute->getName();      // full attribute name, e.g. MyAttributes\Example
	$attribute->getArguments(); // ['Hello', 123]
	$attribute->newInstance();  // returns an instance: new MyAttributes\Example('Hello', 123)
}

Döndürülen öznitelikler bir parametre ile filtrelenebilir, örneğin $reflection->getAttributes(Example::class) yalnızca Example özniteliklerini döndürür.

Öznitelik sınıfları

Öznitelik sınıfı MyAttributes\Example mevcut olmayabilir. Yalnızca bir yöntem çağrısı newInstance() onu örneklediği için varlığını gerektirir. Öyleyse yazalım. Tamamen sıradan bir sınıf olacak, sadece Attribute özniteliğini sağlamamız gerekiyor (yani küresel sistem ad alanından):

namespace MyAttributes;

use Attribute;

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

Hangi dil öğeleri için özniteliğin kullanımına izin verileceği kısıtlanabilir. Örneğin, sadece sınıflar ve özellikler için:

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

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

Ancak dikkat edin, doğru veya yanlış kullanımın doğrulanması şaşırtıcı bir şekilde yalnızca newInstance() yöntemi ç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 ilk sürümünden bu yana kalıcı parametreleri ve bileşenleri belirtmek için ek açıklamaları kullanıyor olsa da, bunlar yerel bir dil yapısı olmadıkları için daha yaygın olarak kullanılmadılar, bu nedenle editörler bunları önermedi ve hata yapmak kolaydı. Bu durum editör eklentileri tarafından halihazırda ele alınıyor olsa da, öznitelikler tarafından getirilen gerçekten yerel yol tamamen yeni olasılıkların önünü açmaktadır.

Bu arada, öznitelikler Nette Kodlama Standardında bir istisna kazanmıştır, bu da sınıf adının özelliğe ek olarak (örn. Product, InvalidValue), bir genellik de içermesini gerektirir (örn. ProductPresenter, InvalidValueException). Aksi takdirde, kod içinde kullanıldığında, sınıfın tam olarak neyi temsil ettiği açık olmayacaktır. Nitelikler için bu arzu edilmez, bu nedenle sınıf InjectAttribute yerine Inject olarak adlandırılır.

Son bölümde, PHP'de hangi yeni işlevlerin ve sınıfların ortaya çıktığına bakacağız ve Tam Zamanında Derleyici'yi tanıtacağız.