PHP 8.0: Yeni Fonksiyonlar, Sınıflar ve JIT (4/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 son bölümde yeni fonksiyonlara ve sınıflara bakacağız ve Just in Time Compiler'ı tanıtacağız.

Yeni Fonksiyonlar

PHP standart kütüphanesi yüzlerce fonksiyona sahiptir ve 8.0 sürümünde altı yeni fonksiyon ortaya çıktı. Az gibi görünebilir, ancak çoğu dilin zayıf noktalarını kapatıyor. Bu, PHP'yi daha önceki hiçbir sürümün yapmadığı kadar tamamlayan ve konsolide eden 8.0 sürümünün genel havasıyla güzel bir şekilde örtüşüyor. Tüm yeni fonksiyonların ve metotların bir özetini geçiş kılavuzunda bulabilirsiniz.

str_contains() str_starts_with() str_ends_with()

Bir karakter dizisinin bir alt dize ile başlayıp başlamadığını, bitip bitmediğini veya içerip içermediğini belirlemek için fonksiyonlar.

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

Bu üçlünün gelişiyle birlikte PHP, arama yaparken boş bir karakter dizisiyle nasıl başa çıkılacağını tanımlar; ilgili tüm diğer fonksiyonlar da buna uyar ve bu şu şekildedir: boş karakter dizisi her yerde bulunur:

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

Bu sayede, üçlünün davranışı Nette'deki benzerleriyle 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şimle yüklenir ve tutarsızlıkların ve yanlış adımların ortaya çıkmasını engellemek mümkün değildir. Ancak aynı zamanda ilgili dilin kartvizitidir. 25 yıllık PHP'de, bir dizinin ilk veya son öğesini döndürme, hile yapmadan HTML kaçış işlemi (htmlspecialchars kesme işaretini kaçırmaz) veya tam da bir karakter dizisinde karakter dizisi arama gibi temel işlemler için fonksiyonların eksik olması şaşırtıcıdır. Bunun bir şekilde atlatılabileceği savunması geçerli değildir, çünkü sonuç okunabilir ve anlaşılır bir kod olmaz. Bu, tüm API yazarları için bir derstir. Bir fonksiyonun belgelerinin önemli bir bölümünün incelikleri (örneğin strpos'un dönüş değerleri gibi) açıklamaya ayrıldığını gördüğünüzde, bu kütüphaneyi düzenlemek ve tam da str_contains'i eklemek için açık bir işarettir.

get_debug_type()

Artık kullanımdan kaldırılmış olan get_type()'ın yerini alır. integer gibi uzun tipler yerine artık kullanılan int'i döndürür, nesneler durumunda doğrudan tipi 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)

Kaynakların Nesneleştirilmesi

Kaynak (resource) türündeki değerler, PHP'nin henüz nesnelere sahip olmadığı, ancak aslında onlara ihtiyaç duyduğu zamanlardan gelir. Böylece kaynaklar dünyaya geldi. Bugün nesnelerimiz var ve kaynaklara kıyasla çöp toplayıcı (garbage collector) ile çok daha iyi çalışıyorlar, bu yüzden plan, hepsini yavaş yavaş nesnelerle değiştirmektir.

PHP 8.0'dan itibaren resimler, curl bağlantıları, openssl, xml, vb. kaynakları nesnelere dönüşüyor. PHP 8.1'de sıra FTP bağlantılarına vb. gelecek.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC kırılması

Bu nesnelerin henüz hiçbir metodu yoktur ve örneklerini doğrudan oluşturamazsınız. Şimdilik gerçekten sadece PHP'den eski kaynakları API'yi değiştirmeden çıkarmakla ilgili. Ve bu iyi bir şey, çünkü iyi bir API oluşturmak ayrı ve zorlu bir görevdir. Kimse PHP'de fgetc() veya fgets() gibi adlandırılmış metotlara sahip SplFileObject gibi başka sınıfların ortaya çıkmasını istemez.

PhpToken

Tokenizatör ve dolayısıyla token_get_all etrafındaki fonksiyonlar da nesnelere taşınıyor. Bu sefer kaynaklardan kurtulmakla ilgili değil, tek bir PHP belirtecini temsil eden tam teşekküllü bir nesne alıyoruz.

<?php

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

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() metodu, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ve T_OPEN_TAG belirteçleri için true döndürür.

Weak maps

Zayıf haritalar (Weak maps), artık kullanılmayan tüm nesneleri ve değerleri (yani onları içeren kullanılan bir değişken veya özellik yoktur) bellekten serbest bırakan çöp toplayıcı ile ilgilidir. PHP iş parçacığının ömrü kısa ömürlü olduğundan ve bugün sunucularda bol miktarda belleğimiz olduğundan, genellikle bellek verimli bir şekilde serbest bırakma sorunlarıyla hiç ilgilenmeyiz. Ancak daha uzun süre çalışan betikler için bunlar çok önemlidir.

WeakMap nesnesi SplObjectStorage'a benzer. Her ikisinde de anahtar olarak nesneler kullanılır ve altlarında herhangi bir değerin saklanmasına izin verirler. Fark, WeakMap'in nesnenin çöp toplayıcı tarafından serbest bırakılmasını engellememesidir. Yani, nesnenin hala bulunduğu tek yer zayıf haritadaki bir anahtarsa, 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

Bu ne işe yarar? Örneğin önbellekleme için. Blog yazısını ilettiğimiz ve tüm yorumlarını döndüren bir loadComments() metodumuz olduğunu varsayalım. Metot aynı makaleyle tekrar tekrar çağrıldığı için, ilk metodun sonucunu önbelleğe alacak bir getComments() daha 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]
	}

	...
}

İşin püf noktası, $article nesnesi serbest bırakıldığı anda (örneğin uygulama başka bir makaleyle çalışmaya başladığında), önbellekteki girdisinin de serbest bırakılmasıdır.

PHP JIT (Just in Time Compiler)

Belki de PHP'nin, PHP sanal makinesi tarafından yürütülen ve örneğin burada inceleyebilirsiniz düşük seviyeli talimatlar olan opcode'a derlendiğini biliyorsunuzdur. Peki JIT nedir? JIT, PHP'yi şeffaf bir şekilde doğrudan işlemci tarafından yürütülen makine koduna derleyebilir, böylece sanal makinenin daha yavaş yürütülmesini atlar.

JIT'in amacı PHP'yi hızlandırmaktır.

PHP'ye JIT uygulama çabası 2011 yılına kadar uzanıyor ve arkasında Dmitry Stogov var. O zamandan beri 3 farklı uygulama denedi, ancak hiçbiri canlı PHP'ye girmedi ve bunun nedenleri şunlardı: sonuç hiçbir zaman tipik web uygulamaları için önemli bir performans artışı olmadı; PHP'nin bakımını zorlaştırıyor (yani Dmitry dışında kimse anlamıyor 😉); JIT kullanmadan performansı artırmanın başka yolları vardı.

PHP 7 sürümündeki sıçramalı performans artışı, paradoksal olarak dağıtımı gerçekleşmemiş olsa da JIT üzerindeki çalışmaların bir yan ürünüydü. Bu ancak PHP 8'de gerçekleşiyor. Ancak abartılı beklentileri hemen frenleyeceğim: muhtemelen herhangi bir hızlanma fark etmeyeceksiniz.

Öyleyse JIT neden PHP'ye giriyor? Birincisi, performansı artırmanın diğer yolları yavaş yavaş tükeniyor ve JIT basitçe sırada. Sıradan web uygulamalarında hızlanma sağlamasa da, örneğin matematiksel hesaplamaları önemli ölçüde hızlandırıyor. Bu, bu tür şeyleri PHP'de yazmaya başlama olasılığını açıyor. Ve aslında, şimdiye kadar hız nedeniyle doğrudan C'de uygulama gerektiren fonksiyonları doğrudan PHP'de uygulamak mümkün olabilirdi.

JIT, opcache uzantısının bir parçasıdır ve php.ini'de onunla birlikte etkinleştirilir (o dört basamaklı sayı için dokümantasyon 'u okuyun):

zend_extension=php_opcache.dll
opcache.jit=1205              ; OTRC dörtlüsü ile yapılandırma
opcache.enable_cli=1          ; CLI'da da çalışması için
opcache.jit_buffer_size=128M  ; derlenmiş kod için ayrılmış bellek

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

JIT, tüm değişkenlerin açıkça tanımlanmış tiplere sahip olduğu ve aynı kodun tekrarlanan çağrılarında değişemeyeceği durumlarda çok iyi çalışır. Bu yüzden, bir gün PHP'de değişkenler için de tipleri bildirip bildirmeyeceğimizi merak ediyorum: string $s = 'Merhaba, bu serinin sonu';