PHP 8.0: Yeni İşlevler, Sınıflar ve JIT (4/4)

4 yıl önce Kimden David Grudl  

PHP 8.0 sürümü yayınlandı. Daha önce hiçbir sürümde olmayan yeni özelliklerle dolu. Bunların tanıtımı dört ayrı makaleyi hak ediyordu. Son bölümde yeni fonksiyonlara ve sınıflara bir göz atacağız ve Tam Zamanında Derleyici'yi tanıtacağız.

Yeni Fonksiyonlar

Standart PHP kütüphanesi yüzlerce fonksiyona sahiptir ve 8.0 sürümünde altı yeni fonksiyon ortaya çıkmıştır. Çok fazla görünmüyor, ancak bunların çoğu dilin zayıf noktalarına çare oluyor. Bu da PHP'yi daha önce hiçbir sürümde olmadığı kadar sıkılaştıran ve birleştiren 8.0 sürümünün konseptiyle güzel bir şekilde örtüşüyor. Tüm yeni işlev ve yöntemlere genel bakış geçiş kılavuzunda bulunabilir.

str_contains() str_starts_with() str_ends_with()

Bir dizenin başlayıp başlamadığını, bitip bitmediğini veya bir alt dize içerip içermediğini belirleyen işlevler.

if (str_contains('Nette', 'te')) {
	...
}

Bu üçlünün ortaya çıkmasıyla PHP, arama sırasında boş bir dizenin nasıl ele alınacağını tanımlar, bu da diğer tüm ilgili işlevlerin bağlı olduğu şeydir ve boş bir dize her yerde bulunur:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (previously false)

Bu sayede, üçlünün davranışı Nette analoglarıyla tamamen aynıdır:

str_contains()      # Nette\Utils\String::contains()
str_starts_with()   # Nette\Utils\String::startsWith()
str_ends_with()     # Nette\Utils\String::endsWith()

Bu fonksiyonlar neden bu kadar önemli? Tüm dillerin standart kütüphaneleri her zaman tarihsel gelişimin yükünü taşır; tutarsızlıklar ve yanlış adımlardan kaçınılamaz. Ancak bu aynı zamanda ilgili dilin de bir kanıtıdır. Şaşırtıcı bir şekilde, 25 yıllık PHP, bir dizinin ilk ya da son elemanını döndürmek, kötü sürprizler olmadan HTML'den kaçmak (htmlspecialchars kesme işaretinden kaçmaz) ya da sadece bir dize içinde bir dize aramak gibi temel işlemler için işlevlerden yoksundur. Sonuç okunaklı ve anlaşılabilir bir kod olmadığı için bir şekilde atlanabileceği anlamına gelmez. Bu, tüm API yazarları için bir derstir. Fonksiyonun dokümantasyonunun büyük bir kısmının tuzaklarla ilgili açıklamalardan oluştuğunu gördüğünüzde ( strpos'un geri dönüş değerleri gibi), bu kütüphaneyi değiştirmek ve str_contains'u eklemek için açık bir işarettir.

get_debug_type()

Artık kullanılmayan get_type() adresinin yerini alır. integer gibi uzun türler yerine, günümüzde kullanılan int türünü döndürür, nesneler söz konusu olduğunda doğrudan türü döndürür:

Değer gettype() get_debug_type()
'abc' string string
[1, 2] array array
231 integer int
3.14 double float
true boolean bool
null NULL null
new stdClass object stdClass
new Foo\Bar object Foo\Bar
function() {} object Closure
new class {} object class@anonymous
new class extends Foo {} object Foo@anonymous
curl_init() resource resource (curl)
curl_close($ch) resource (closed) resource (closed)

Kaynaktan Nesneye Geçiş

Kaynak türü değerleri, PHP'nin henüz nesnelere sahip olmadığı, ancak onlara gerçekten ihtiyaç duyduğu bir zamandan gelir. Kaynaklar bu şekilde doğdu. Bugün nesnelerimiz var ve kaynaklara kıyasla çöp toplayıcı ile çok daha iyi çalışıyorlar, bu yüzden plan yavaş yavaş hepsini nesnelerle değiştirmektir.

PHP 8.0'dan itibaren resimler, curl birleşimleri, openssl, xml, vb. kaynaklar nesnelere dönüştürülmüştür. PHP 8.1'de bunu FTP bağlantıları vb. takip edecektir.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC break

Bu nesnelerin henüz herhangi bir yöntemi yoktur ve bunları doğrudan örnekleyemezsiniz. Şimdiye kadar, API'yi değiştirmeden PHP'den eski kaynaklardan kurtulma meselesi gerçekten. Ve bu iyi bir şey, çünkü iyi bir API oluşturmak ayrı ve zorlu bir görevdir. Kimse SplFileObject gibi yeni PHP sınıflarının fgetc() veya fgets() adlı yöntemlerle oluşturulmasını istemez.

PhpToken

Tokenizer ve token_get_all etrafındaki fonksiyonlar da nesnelere taşınmıştır. Bu kez mesele kaynaklardan kurtulmak değil, bir PHP belirtecini temsil eden tam teşekküllü bir nesne elde etmektir.

<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // instance PhpToken

echo $token->id;             // T_OPEN_TAG
echo $token->text;           // '<?php'
echo $token->line;           // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING);   // false
echo $token->isIgnorable();  // true

isIgnorable() yöntemi T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ve T_OPEN_TAG belirteçleri için true değerini döndürür.

Zayıf Haritalar

Zayıf eşlemeler, artık kullanılmayan tüm nesneleri ve değerleri bellekten silen çöp toplayıcı ile ilgilidir (yani, bunları içeren bir değişken veya özellik yoktur). PHP iş parçacıkları kısa ömürlü olduğundan ve sunucularımızda bol miktarda bellek bulunduğundan, genellikle etkin bellek boşaltma ile ilgili sorunlara hiç değinmeyiz. Ancak daha uzun süre çalışan betikler için bunlar çok önemlidir.

WeakMap nesnesi SplObjectStorage nesnesine benzer. Her ikisi de nesneleri anahtar olarak kullanır ve bunların altında rastgele değerlerin saklanmasına izin verir. Aralarındaki fark, WeakMap nesnenin çöp toplayıcı tarafından serbest bırakılmasını engellememesidir. Yani, nesnenin şu anda bulunduğu tek yer zayıf haritada bir anahtar ise, haritadan ve bellekten kaldırılacaktır.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $obj';

dump(count($map));  // 1
unset($obj);
dump(count($map));  // 0

Ne işe yarar? Örneğin, önbellekleme için. Bir blog makalesi ilettiğimiz ve tüm yorumlarını döndüren bir loadComments() metodumuz olsun. Yöntem aynı makale için tekrar tekrar çağrıldığından, ilk yöntemin sonucunu önbelleğe alacak başka bir getComments() oluşturacağız:

class Comments
{
	private WeakMap $cache;

	public function __construct()
	{
		$this->cache = new WeakMap;
	}

	public function getComments(Article $article): ?array
	{
		$this->cache[$article] ??= $this->loadComments($article);
		return $this->cache[$article]
	}

	...
}

Mesele şu ki, $article nesnesi serbest bırakıldığında (örneğin, uygulama başka bir makale ile çalışmaya başladığında), girişi de önbellekten serbest bırakılır.

PHP JIT (Tam Zamanında Derleyici)

PHP'nin örneğin burada görebileceğiniz ve bir PHP sanal makinesi tarafından çalıştırılan düşük seviyeli talimatlar olan opcode olarak adlandırılan kodlarla derlendiğini biliyor olabilirsiniz. Peki JIT nedir? JIT, PHP'yi doğrudan işlemci tarafından çalıştırılan makine koduna şeffaf bir şekilde derleyebilir, böylece sanal makine tarafından daha yavaş yürütme atlanır.

JIT bu nedenle PHP'yi hızlandırmak için tasarlanmıştır.

JIT'i PHP'ye uygulama çabası 2011 yılına dayanmaktadır ve Dmitry Stogov tarafından desteklenmektedir. O zamandan beri 3 farklı uygulama denedi, ancak üç nedenden dolayı hiçbiri son PHP sürümüne giremedi: sonuç tipik web uygulamaları için performansta önemli bir artış olmadı; PHP bakımını zorlaştırıyor (yani Dmitry dışında kimse anlamıyor 😉); JIT kullanmak zorunda kalmadan performansı artırmanın başka yolları da vardı.

PHP sürüm 7'de gözlemlenen performans artışı, JIT üzerindeki çalışmaların bir yan ürünüydü, ancak paradoksal olarak dağıtılmamıştı. Ancak abartılı beklentilerden uzak duracağım: muhtemelen herhangi bir hızlanma görmeyeceksiniz.

Peki JIT neden PHP'ye giriyor? Öncelikle, performansı artırmanın diğer yolları yavaş yavaş tükeniyor ve JIT basitçe bir sonraki adım. Yaygın web uygulamalarında herhangi bir hız iyileştirmesi getirmez, ancak örneğin matematiksel hesaplamaları önemli ölçüde hızlandırır. Bu da bu tür şeyleri PHP'de yazmaya başlamanın yolunu açıyor. Aslında, daha önce hız nedeniyle doğrudan C uygulaması gerektiren bazı işlevleri doğrudan PHP'de uygulamak mümkün olacaktır.

JIT, opcache uzantısının bir parçasıdır ve php.ini'de onunla birlikte etkinleştirilir (bu dört rakamla ilgili belgeleri okuyun):

zend_extension=php_opcache.dll
opcache.jit=1205              ; configuration using four digits OTRC
opcache.enable_cli=1          ; in order to work in the CLI as well
opcache.jit_buffer_size=128M  ; dedicated memory for compiled code

JIT'in çalıştığını örneğin Tracy Bar bilgi panelinden doğrulayabilirsiniz.

JIT, tüm değişkenlerin açıkça tanımlanmış tipleri varsa ve aynı kod tekrar tekrar çağrıldığında bile değişemiyorsa çok iyi çalışır. Bu nedenle bir gün PHP'de de değişkenler için tür bildirip bildirmeyeceğimizi merak ediyorum: string $s = 'Bye, this is the end of the series';